Home | History | Annotate | Line # | Download | only in lint1
      1 /*	$NetBSD: d_c99_bool_strict_syshdr.c,v 1.27 2025/04/10 20:37:48 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 -X 351 */
     15 
     16 extern const unsigned short *ctype_table;
     17 
     18 extern void println(const char *);
     19 
     20 
     21 /*
     22  * No matter whether the code is from a system header or not, the idiom
     23  * 'do { ... } while (0)' is well known, and using the integer constant 0
     24  * instead of the boolean constant 'false' neither creates any type confusion
     25  * nor does its value take place in any conversions, as its scope is limited
     26  * to the controlling expression of the loop.
     27  */
     28 void
     29 statement_macro(void)
     30 {
     31 
     32 	do {
     33 		println("nothing");
     34 	} while (0);
     35 
     36 # 37 "d_c99_bool_strict_syshdr.c" 3 4
     37 	do {
     38 		println("nothing");
     39 	} while (0);
     40 
     41 # 42 "d_c99_bool_strict_syshdr.c"
     42 	do {
     43 		println("nothing");
     44 	} while (0);
     45 }
     46 
     47 
     48 /*
     49  * The macros from <ctype.h> can be implemented in different ways.  The C
     50  * standard defines them as returning 'int'.  In strict bool mode, the actual
     51  * return type can be INT or BOOL, depending on whether the macros do the
     52  * comparison against 0 themselves.
     53  *
     54  * Since that comparison is more code to write and in exceptional situations
     55  * more code to execute, they will probably leave out the extra comparison,
     56  * but both ways are possible.
     57  *
     58  * In strict bool mode, there must be a way to call these function-like macros
     59  * portably, without triggering type errors, no matter whether they return
     60  * BOOL or INT.
     61  *
     62  * The expressions from this example cross the boundary between system header
     63  * and application code.  They need to carry the information that they are
     64  * half-BOOL, half-INT across to the enclosing expressions.
     65  */
     66 void
     67 strict_bool_system_header_ctype(int c)
     68 {
     69 	/*
     70 	 * The macro returns INT, which may be outside the range of a
     71 	 * uint8_t variable, therefore it must not be assigned directly.
     72 	 * All other combinations of type are safe from truncation.
     73 	 */
     74 	_Bool system_int_assigned_to_bool =
     75 # 76 "d_c99_bool_strict_syshdr.c" 3 4
     76 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     77 # 78 "d_c99_bool_strict_syshdr.c"
     78 	;
     79 	/* expect-1: error: operands of 'init' have incompatible types '_Bool' and 'int' [107] */
     80 
     81 	int system_bool_assigned_to_int =
     82 # 83 "d_c99_bool_strict_syshdr.c" 3 4
     83 	    (int)((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     84 # 85 "d_c99_bool_strict_syshdr.c"
     85 	;
     86 
     87 	if (
     88 # 89 "d_c99_bool_strict_syshdr.c" 3 4
     89 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     90 # 91 "d_c99_bool_strict_syshdr.c"
     91 	)
     92 		println("system macro returning INT");
     93 
     94 	if (
     95 # 96 "d_c99_bool_strict_syshdr.c" 3 4
     96 	    ((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     97 # 98 "d_c99_bool_strict_syshdr.c"
     98 	)
     99 		println("system macro returning BOOL");
    100 }
    101 
    102 static inline _Bool
    103 ch_isspace_sys_int(char c)
    104 {
    105 	return
    106 # 107 "d_c99_bool_strict_syshdr.c" 3 4
    107 	    ((ctype_table + 1)[c] & 0x0040)
    108 # 109 "d_c99_bool_strict_syshdr.c"
    109 	    != 0;
    110 }
    111 
    112 /*
    113  * isspace is defined to return an int.  Comparing this int with 0 is the
    114  * safe way to convert it to _Bool.  This must be allowed even if isspace
    115  * does the comparison itself.
    116  */
    117 static inline _Bool
    118 ch_isspace_sys_bool(char c)
    119 {
    120 	return
    121 # 122 "d_c99_bool_strict_syshdr.c" 3 4
    122 	    ((ctype_table + 1)[(unsigned char)c] & 0x0040) != 0
    123 # 124 "d_c99_bool_strict_syshdr.c"
    124 	    != 0;
    125 }
    126 
    127 /*
    128  * There are several functions from system headers that have return type
    129  * int.  For this return type there are many API conventions:
    130  *
    131  *	* isspace: 0 means no, non-zero means yes
    132  *	* open: 0 means success, -1 means failure
    133  *	* main: 0 means success, non-zero means failure
    134  *	* strcmp: 0 means equal, < 0 means less than, > 0 means greater than
    135  *
    136  * Without a detailed list of individual functions, it's not possible to
    137  * guess what the return value means.  Therefore, in strict bool mode, the
    138  * return value of these functions cannot be implicitly converted to bool,
    139  * not even in a controlling expression.  Allowing that would allow
    140  * expressions like !strcmp(s1, s2), which is not correct since strcmp
    141  * returns an "ordered comparison result", not a bool.
    142  */
    143 
    144 # 1 "math.h" 3 4
    145 extern int finite(double);
    146 # 1 "string.h" 3 4
    147 extern int strcmp(const char *, const char *);
    148 # 149 "d_c99_bool_strict_syshdr.c"
    149 
    150 /*ARGSUSED*/
    151 _Bool
    152 call_finite_bad(double d)
    153 {
    154 	/* expect+1: error: function has return type '_Bool' but returns 'int' [211] */
    155 	return finite(d);
    156 }
    157 
    158 _Bool
    159 call_finite_good(double d)
    160 {
    161 	return finite(d) != 0;
    162 }
    163 
    164 /*ARGSUSED*/
    165 _Bool
    166 str_equal_bad(const char *s1, const char *s2)
    167 {
    168 	/* expect+2: error: operand of '!' must be bool, not 'int' [330] */
    169 	/* expect+1: error: function 'str_equal_bad' expects to return value [214] */
    170 	return !strcmp(s1, s2);
    171 }
    172 
    173 _Bool
    174 str_equal_good(const char *s1, const char *s2)
    175 {
    176 	return strcmp(s1, s2) == 0;
    177 }
    178 
    179 
    180 int read_char(void);
    181 
    182 /*
    183  * Between tree.c 1.395 from 2021-11-16 and ckbool.c 1.10 from 2021-12-22,
    184  * lint wrongly complained that the controlling expression would have to be
    185  * _Bool instead of int.  Since the right-hand side of the ',' operator comes
    186  * from a system header, this is OK though.
    187  */
    188 void
    189 controlling_expression_with_comma_operator(void)
    190 {
    191 	int c;
    192 
    193 	while (c = read_char(),
    194 # 195 "d_c99_bool_strict_syshdr.c" 3 4
    195 	    ((int)((ctype_table + 1)[(
    196 # 197 "d_c99_bool_strict_syshdr.c"
    197 		c
    198 # 199 "d_c99_bool_strict_syshdr.c" 3 4
    199 	    )] & 0x0040 /* Space     */))
    200 # 201 "d_c99_bool_strict_syshdr.c"
    201 	    )
    202 		continue;
    203 }
    204 
    205 
    206 void take_bool(_Bool);
    207 
    208 /*
    209  * On NetBSD, the header <curses.h> defines TRUE or FALSE as integer
    210  * constants, not as bool constants.
    211  *
    212  *
    213  * Several curses functions take bool as a parameter, for example keypad or
    214  * leaveok.  Before ckbool.c 1.14 from 2022-05-19, lint did not complain when
    215  * these functions get 0 instead of 'false' as an argument.  It did complain
    216  * about 1 instead of 'true' though.
    217  */
    218 void
    219 pass_bool_to_function(void)
    220 {
    221 
    222 	/* expect+5: error: parameter 1 expects '_Bool', gets passed 'int' [334] */
    223 	take_bool(
    224 # 225 "d_c99_bool_strict_syshdr.c" 3 4
    225 	    (1)
    226 # 227 "d_c99_bool_strict_syshdr.c"
    227 	);
    228 
    229 	take_bool(
    230 # 231 "d_c99_bool_strict_syshdr.c" 3 4
    231 	    __lint_true
    232 # 233 "d_c99_bool_strict_syshdr.c"
    233 	);
    234 
    235 	/* expect+5: error: parameter 1 expects '_Bool', gets passed 'int' [334] */
    236 	take_bool(
    237 # 238 "d_c99_bool_strict_syshdr.c" 3 4
    238 	    (0)
    239 # 240 "d_c99_bool_strict_syshdr.c"
    240 	);
    241 
    242 	take_bool(
    243 # 244 "d_c99_bool_strict_syshdr.c" 3 4
    244 	    __lint_false
    245 # 246 "d_c99_bool_strict_syshdr.c"
    246 	);
    247 }
    248 
    249 
    250 extern int *errno_location(void);
    251 
    252 /*
    253  * As of 2022-06-11, the rule for loosening the strict boolean check for
    254  * expressions from system headers is flawed.  That rule allows statements
    255  * like 'if (NULL)' or 'if (errno)', even though these have pointer type or
    256  * integer type.
    257  */
    258 void
    259 if_pointer_or_int(void)
    260 {
    261 	/* if (NULL) */
    262 	if (
    263 # 264 "d_c99_bool_strict_syshdr.c" 3 4
    264 	    ((void *)0)
    265 # 266 "d_c99_bool_strict_syshdr.c"
    266 		       )
    267 		/* expect+1: warning: 'return' statement not reached [193] */
    268 		return;
    269 
    270 	/* if (EXIT_SUCCESS) */
    271 	if (
    272 # 273 "d_c99_bool_strict_syshdr.c" 3 4
    273 	    0
    274 # 275 "d_c99_bool_strict_syshdr.c"
    275 		       )
    276 		/* expect+1: warning: 'return' statement not reached [193] */
    277 		return;
    278 
    279 	/* if (errno) */
    280 	if (
    281 # 282 "d_c99_bool_strict_syshdr.c" 3 4
    282 	    (*errno_location())
    283 # 284 "d_c99_bool_strict_syshdr.c"
    284 		       )
    285 		return;
    286 }
    287 
    288 
    289 /*
    290  * For expressions that originate from a system header, the strict type rules
    291  * are relaxed a bit, to allow for expressions like 'flags & FLAG', even
    292  * though they are not strictly boolean.
    293  *
    294  * This shouldn't apply to function call expressions though since one of the
    295  * goals of strict bool mode is to normalize all expressions calling 'strcmp'
    296  * to be of the form 'strcmp(a, b) == 0' instead of '!strcmp(a, b)'.
    297  */
    298 # 1 "stdio.h" 1 3 4
    299 typedef struct stdio_file {
    300 	int fd;
    301 } FILE;
    302 int ferror(FILE *);
    303 FILE stdio_files[3];
    304 FILE *stdio_stdout;
    305 # 306 "d_c99_bool_strict_syshdr.c" 2
    306 # 1 "string.h" 1 3 4
    307 int strcmp(const char *, const char *);
    308 # 309 "d_c99_bool_strict_syshdr.c" 2
    309 
    310 void
    311 controlling_expression(FILE *f, const char *a, const char *b)
    312 {
    313 	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
    314 	if (ferror(f))
    315 		return;
    316 	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
    317 	if (strcmp(a, b))
    318 		return;
    319 	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
    320 	if (!ferror(f))
    321 		return;
    322 	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
    323 	if (!strcmp(a, b))
    324 		return;
    325 
    326 	/*
    327 	 * Before tree.c 1.395 from 2021-11-16, the expression below didn't
    328 	 * produce a warning since the expression 'stdio_files' came from a
    329 	 * system header (via a macro), and this property was passed up to
    330 	 * the expression 'ferror(stdio_files[1])'.
    331 	 *
    332 	 * That was wrong though since the type of a function call expression
    333 	 * only depends on the function itself but not its arguments types.
    334 	 * The old rule had allowed a raw condition 'strcmp(a, b)' without
    335 	 * the comparison '!= 0', as long as one of its arguments came from a
    336 	 * system header.
    337 	 *
    338 	 * Seen in bin/echo/echo.c, function main, call to ferror.
    339 	 */
    340 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    341 	if (ferror(
    342 # 343 "d_c99_bool_strict_syshdr.c" 3 4
    343 	    &stdio_files[1]
    344 # 345 "d_c99_bool_strict_syshdr.c"
    345 	    ))
    346 		return;
    347 
    348 	/*
    349 	 * Before cgram.y 1.369 from 2021-11-16, at the end of parsing the
    350 	 * name 'stdio_stdout', the parser already looked ahead to the next
    351 	 * token, to see whether it was the '(' of a function call.
    352 	 *
    353 	 * At that point, the parser was no longer in a system header,
    354 	 * therefore 'stdio_stdout' had tn_sys == false, and this information
    355 	 * was pushed down to the whole function call expression (which was
    356 	 * another bug that got fixed in tree.c 1.395 from 2021-11-16).
    357 	 */
    358 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    359 	if (ferror(
    360 # 361 "d_c99_bool_strict_syshdr.c" 3 4
    361 	    stdio_stdout
    362 # 363 "d_c99_bool_strict_syshdr.c"
    363 	    ))
    364 		return;
    365 
    366 	/*
    367 	 * In this variant of the pattern, there is a token ')' after the
    368 	 * name 'stdio_stdout', which even before tree.c 1.395 from
    369 	 * 2021-11-16 had the effect that at the end of parsing the name, the
    370 	 * parser was still in the system header, thus setting tn_sys (or
    371 	 * rather tn_relaxed at that time) to true.
    372 	 */
    373 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    374 	if (ferror(
    375 # 376 "d_c99_bool_strict_syshdr.c" 3 4
    376 	    (stdio_stdout)
    377 # 378 "d_c99_bool_strict_syshdr.c"
    378 	    ))
    379 		return;
    380 
    381 	/*
    382 	 * Before cgram.y 1.369 from 2021-11-16, the comment following
    383 	 * 'stdio_stdout' did not prevent the search for '('.  At the point
    384 	 * where build_name called expr_alloc_tnode, the parser was already
    385 	 * in the main file again, thus treating 'stdio_stdout' as not coming
    386 	 * from a system header.
    387 	 *
    388 	 * This has been fixed in tree.c 1.395 from 2021-11-16.  Before that,
    389 	 * an expression had come from a system header if its operands came
    390 	 * from a system header, but that was only close to the truth.  In a
    391 	 * case where both operands come from a system header but the
    392 	 * operator comes from the main translation unit, the main
    393 	 * translation unit still has control over the whole expression.  So
    394 	 * the correct approach is to focus on the operator, not the
    395 	 * operands.  There are a few corner cases where the operator is
    396 	 * invisible (for implicit conversions) or synthetic (for translating
    397 	 * 'arr[index]' to '*(arr + index)', but these are handled as well.
    398 	 */
    399 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    400 	if (ferror(
    401 # 402 "d_c99_bool_strict_syshdr.c" 3 4
    402 	    stdio_stdout /* comment */
    403 # 404 "d_c99_bool_strict_syshdr.c"
    404 	    ))
    405 		return;
    406 }
    407