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