Home | History | Annotate | Line # | Download | only in lint1
d_c99_bool_strict_syshdr.c revision 1.15
      1 /*	$NetBSD: d_c99_bool_strict_syshdr.c,v 1.15 2022/05/20 21:03:04 rillig Exp $	*/
      2 # 3 "d_c99_bool_strict_syshdr.c"
      3 
      4 /*
      5  * In strict bool mode, lint treats bool as incompatible with any other scalar
      6  * types.  This mode helps in migrating code from pre-C99 to C99.
      7  *
      8  * System headers, on the other hand, cannot be migrated if they need to stay
      9  * compatible with pre-C99 code.  Therefore, the checks for system headers are
     10  * loosened.  In contexts where a scalar expression is compared to 0, macros
     11  * and functions from system headers may use int expressions as well.
     12  */
     13 
     14 /* lint1-extra-flags: -T */
     15 
     16 extern const unsigned short *ctype_table;
     17 
     18 extern void println(const char *);
     19 
     20 /*
     21  * On NetBSD 8, <sys/select.h> defines FD_ISSET by enclosing the statements
     22  * in the well-known 'do { ... } while (CONSTCOND 0)' loop.  The 0 in the
     23  * controlling expression has type INT but should be allowed nevertheless
     24  * since that header does not have a way to distinguish between bool and int.
     25  * It just follows the C99 standard, unlike the lint-provided stdbool.h,
     26  * which redefines 'false' to '__lint_false'.
     27  */
     28 void
     29 strict_bool_system_header_statement_macro(void)
     30 {
     31 
     32 	do {
     33 		println("nothing");
     34 	} while (/*CONSTCOND*/0);
     35 	/* expect-1: error: controlling expression must be bool, not 'int' [333] */
     36 
     37 # 38 "d_c99_bool_strict_syshdr.c" 3 4
     38 	do {
     39 		println("nothing");
     40 	} while (/*CONSTCOND*/0);	/* ok */
     41 
     42 # 43 "d_c99_bool_strict_syshdr.c"
     43 	do {
     44 		println("nothing");
     45 	} while (/*CONSTCOND*/0);
     46 	/* expect-1: error: controlling expression must be bool, not 'int' [333] */
     47 }
     48 
     49 
     50 /*
     51  * The macros from <ctype.h> can be implemented in different ways.  The C
     52  * standard defines them as returning 'int'.  In strict bool mode, the actual
     53  * return type can be INT or BOOL, depending on whether the macros do the
     54  * comparison against 0 themselves.
     55  *
     56  * Since that comparison is more code to write and in exceptional situations
     57  * more code to execute, they will probably leave out the extra comparison,
     58  * but both ways are possible.
     59  *
     60  * In strict bool mode, there must be a way to call these function-like macros
     61  * portably, without triggering type errors, no matter whether they return
     62  * BOOL or INT.
     63  *
     64  * The expressions from this example cross the boundary between system header
     65  * and application code.  They need to carry the information that they are
     66  * half-BOOL, half-INT across to the enclosing expressions.
     67  */
     68 void
     69 strict_bool_system_header_ctype(int c)
     70 {
     71 	/*
     72 	 * The macro returns INT, which may be outside the range of a
     73 	 * uint8_t variable, therefore it must not be assigned directly.
     74 	 * All other combinations of type are safe from truncation.
     75 	 */
     76 	_Bool system_int_assigned_to_bool =
     77 # 78 "d_c99_bool_strict_syshdr.c" 3 4
     78 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     79 # 80 "d_c99_bool_strict_syshdr.c"
     80 	;
     81 	/* expect-1: error: operands of 'init' have incompatible types (_Bool != int) [107] */
     82 
     83 	int system_bool_assigned_to_int =
     84 # 85 "d_c99_bool_strict_syshdr.c" 3 4
     85 	    (int)((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     86 # 87 "d_c99_bool_strict_syshdr.c"
     87 	;
     88 
     89 	if (
     90 # 91 "d_c99_bool_strict_syshdr.c" 3 4
     91 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     92 # 93 "d_c99_bool_strict_syshdr.c"
     93 	)
     94 		println("system macro returning INT");
     95 
     96 	if (
     97 # 98 "d_c99_bool_strict_syshdr.c" 3 4
     98 	    ((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     99 # 100 "d_c99_bool_strict_syshdr.c"
    100 	)
    101 		println("system macro returning BOOL");
    102 }
    103 
    104 static inline _Bool
    105 ch_isspace_sys_int(char c)
    106 {
    107 	return
    108 # 109 "d_c99_bool_strict_syshdr.c" 3 4
    109 	    ((ctype_table + 1)[c] & 0x0040)
    110 # 111 "d_c99_bool_strict_syshdr.c"
    111 	    != 0;
    112 }
    113 
    114 /*
    115  * isspace is defined to return an int.  Comparing this int with 0 is the
    116  * safe way to convert it to _Bool.  This must be allowed even if isspace
    117  * does the comparison itself.
    118  */
    119 static inline _Bool
    120 ch_isspace_sys_bool(char c)
    121 {
    122 	return
    123 # 124 "d_c99_bool_strict_syshdr.c" 3 4
    124 	    ((ctype_table + 1)[(unsigned char)c] & 0x0040) != 0
    125 # 126 "d_c99_bool_strict_syshdr.c"
    126 	    != 0;
    127 }
    128 
    129 /*
    130  * There are several functions from system headers that have return type
    131  * int.  For this return type there are many API conventions:
    132  *
    133  *	* isspace: 0 means no, non-zero means yes
    134  *	* open: 0 means success, -1 means failure
    135  *	* main: 0 means success, non-zero means failure
    136  *	* strcmp: 0 means equal, < 0 means less than, > 0 means greater than
    137  *
    138  * Without a detailed list of individual functions, it's not possible to
    139  * guess what the return value means.  Therefore in strict bool mode, the
    140  * return value of these functions cannot be implicitly converted to bool,
    141  * not even in a context where the result is compared to 0.  Allowing that
    142  * would allow expressions like !strcmp(s1, s2), which is not correct since
    143  * strcmp returns an "ordered comparison result", not a bool.
    144  */
    145 
    146 # 1 "math.h" 3 4
    147 extern int finite(double);
    148 # 1 "string.h" 3 4
    149 extern int strcmp(const char *, const char *);
    150 # 151 "d_c99_bool_strict_syshdr.c"
    151 
    152 /*ARGSUSED*/
    153 _Bool
    154 call_finite_bad(double d)
    155 {
    156 	/* expect+1: error: return value type mismatch (_Bool) and (int) [211] */
    157 	return finite(d);
    158 }
    159 
    160 _Bool
    161 call_finite_good(double d)
    162 {
    163 	return finite(d) != 0;
    164 }
    165 
    166 /*ARGSUSED*/
    167 _Bool
    168 str_equal_bad(const char *s1, const char *s2)
    169 {
    170 	/* expect+2: error: operand of '!' must be bool, not 'int' [330] */
    171 	/* expect+1: warning: function 'str_equal_bad' expects to return value [214] */
    172 	return !strcmp(s1, s2);
    173 }
    174 
    175 _Bool
    176 str_equal_good(const char *s1, const char *s2)
    177 {
    178 	return strcmp(s1, s2) == 0;
    179 }
    180 
    181 
    182 int read_char(void);
    183 
    184 /*
    185  * Between tree.c 1.395 from 2021-11-16 and ckbool.c 1.10 from 2021-12-22,
    186  * lint wrongly complained that the controlling expression would have to be
    187  * _Bool instead of int.  Since the right-hand side of the ',' operator comes
    188  * from a system header, this is OK though.
    189  */
    190 void
    191 controlling_expression_with_comma_operator(void)
    192 {
    193 	int c;
    194 
    195 	while (c = read_char(),
    196 # 197 "d_c99_bool_strict_syshdr.c" 3 4
    197 	    ((int)((ctype_table + 1)[(
    198 # 199 "d_c99_bool_strict_syshdr.c"
    199 		c
    200 # 201 "d_c99_bool_strict_syshdr.c" 3 4
    201 	    )] & 0x0040 /* Space     */))
    202 # 203 "d_c99_bool_strict_syshdr.c"
    203 	    )
    204 		continue;
    205 }
    206 
    207 
    208 void take_bool(_Bool);
    209 
    210 /*
    211  * On NetBSD, the header <curses.h> defines TRUE or FALSE as integer
    212  * constants with a CONSTCOND comment.  This comment suppresses legitimate
    213  * warnings in user code; that's irrelevant for this test though.
    214  *
    215  * Several curses functions take bool as a parameter, for example keypad or
    216  * leaveok.  Before ckbool.c 1.14 from 2022-05-19, lint did not complain when
    217  * these functions get 0 instead of 'false' as an argument.  It did complain
    218  * about 1 instead of 'true' though.
    219  */
    220 void
    221 pass_bool_to_function(void)
    222 {
    223 
    224 	/* expect+5: error: argument #1 expects '_Bool', gets passed 'int' [334] */
    225 	take_bool(
    226 # 227 "d_c99_bool_strict_syshdr.c" 3 4
    227 	    (/*CONSTCOND*/1)
    228 # 229 "d_c99_bool_strict_syshdr.c"
    229 	);
    230 
    231 	take_bool(
    232 # 233 "d_c99_bool_strict_syshdr.c" 3 4
    233 	    __lint_true
    234 # 235 "d_c99_bool_strict_syshdr.c"
    235 	);
    236 
    237 	/* expect+5: error: argument #1 expects '_Bool', gets passed 'int' [334] */
    238 	take_bool(
    239 # 240 "d_c99_bool_strict_syshdr.c" 3 4
    240 	    (/*CONSTCOND*/0)
    241 # 242 "d_c99_bool_strict_syshdr.c"
    242 	);
    243 
    244 	take_bool(
    245 # 246 "d_c99_bool_strict_syshdr.c" 3 4
    246 	    __lint_false
    247 # 248 "d_c99_bool_strict_syshdr.c"
    248 	);
    249 }
    250