1 /* $NetBSD: d_c99_bool_strict_syshdr.c,v 1.26 2024/11/20 23:01:52 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 (/*CONSTCOND*/0); 35 36 # 37 "d_c99_bool_strict_syshdr.c" 3 4 37 do { 38 println("nothing"); 39 } while (/*CONSTCOND*/0); 40 41 # 42 "d_c99_bool_strict_syshdr.c" 42 do { 43 println("nothing"); 44 } while (/*CONSTCOND*/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 with a CONSTCOND comment. This comment suppresses legitimate 211 * warnings in user code; that's irrelevant for this test though. 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 (/*CONSTCOND*/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 (/*CONSTCOND*/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