1/* $NetBSD: d_c99_bool_strict_syshdr.c,v 1.24 2024/05/12 12:28:35 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 16extern const unsigned short *ctype_table; 17 18extern void println(const char *); 19 20 21 22 23/* 24 * No matter whether the code is from a system header or not, the idiom 25 * 'do { ... } while (0)' is well known, and using the integer constant 0 26 * instead of the boolean constant 'false' neither creates any type confusion 27 * nor does its value take place in any conversions, as its scope is limited 28 * to the controlling expression of the loop. 29 */ 30void 31statement_macro(void) 32{ 33 34 do { 35 println("nothing"); 36 } while (/*CONSTCOND*/0); 37 38# 39 "d_c99_bool_strict_syshdr.c" 3 4 39 do { 40 println("nothing"); 41 } while (/*CONSTCOND*/0); 42 43# 44 "d_c99_bool_strict_syshdr.c" 44 do { 45 println("nothing"); 46 } while (/*CONSTCOND*/0); 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 */ 68void 69strict_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' and '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 104static inline _Bool 105ch_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 */ 119static inline _Bool 120ch_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 controlling expression. Allowing that would allow 142 * expressions like !strcmp(s1, s2), which is not correct since strcmp 143 * returns an "ordered comparison result", not a bool. 144 */ 145 146# 1 "math.h" 3 4 147extern int finite(double); 148# 1 "string.h" 3 4 149extern int strcmp(const char *, const char *); 150# 151 "d_c99_bool_strict_syshdr.c" 151 152/*ARGSUSED*/ 153_Bool 154call_finite_bad(double d) 155{ 156 /* expect+1: error: function has return type '_Bool' but returns 'int' [211] */ 157 return finite(d); 158} 159 160_Bool 161call_finite_good(double d) 162{ 163 return finite(d) != 0; 164} 165 166/*ARGSUSED*/ 167_Bool 168str_equal_bad(const char *s1, const char *s2) 169{ 170 /* expect+2: error: operand of '!' must be bool, not 'int' [330] */ 171 /* expect+1: error: function 'str_equal_bad' expects to return value [214] */ 172 return !strcmp(s1, s2); 173} 174 175_Bool 176str_equal_good(const char *s1, const char *s2) 177{ 178 return strcmp(s1, s2) == 0; 179} 180 181 182int 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 */ 190void 191controlling_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 208void 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 */ 220void 221pass_bool_to_function(void) 222{ 223 224 /* expect+5: error: parameter 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: parameter 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 251 252extern int *errno_location(void); 253 254/* 255 * As of 2022-06-11, the rule for loosening the strict boolean check for 256 * expressions from system headers is flawed. That rule allows statements 257 * like 'if (NULL)' or 'if (errno)', even though these have pointer type or 258 * integer type. 259 */ 260void 261if_pointer_or_int(void) 262{ 263 /* if (NULL) */ 264 if ( 265# 266 "d_c99_bool_strict_syshdr.c" 3 4 266 ((void *)0) 267# 268 "d_c99_bool_strict_syshdr.c" 268 ) 269 return; 270 /* expect-1: warning: statement not reached [193] */ 271 272 /* if (EXIT_SUCCESS) */ 273 if ( 274# 275 "d_c99_bool_strict_syshdr.c" 3 4 275 0 276# 277 "d_c99_bool_strict_syshdr.c" 277 ) 278 return; 279 /* expect-1: warning: statement not reached [193] */ 280 281 /* if (errno) */ 282 if ( 283# 284 "d_c99_bool_strict_syshdr.c" 3 4 284 (*errno_location()) 285# 286 "d_c99_bool_strict_syshdr.c" 286 ) 287 return; 288} 289 290 291/* 292 * For expressions that originate from a system header, the strict type rules 293 * are relaxed a bit, to allow for expressions like 'flags & FLAG', even 294 * though they are not strictly boolean. 295 * 296 * This shouldn't apply to function call expressions though since one of the 297 * goals of strict bool mode is to normalize all expressions calling 'strcmp' 298 * to be of the form 'strcmp(a, b) == 0' instead of '!strcmp(a, b)'. 299 */ 300# 1 "stdio.h" 1 3 4 301typedef struct stdio_file { 302 int fd; 303} FILE; 304int ferror(FILE *); 305FILE stdio_files[3]; 306FILE *stdio_stdout; 307# 308 "d_c99_bool_strict_syshdr.c" 2 308# 1 "string.h" 1 3 4 309int strcmp(const char *, const char *); 310# 311 "d_c99_bool_strict_syshdr.c" 2 311 312void 313controlling_expression(FILE *f, const char *a, const char *b) 314{ 315 /* expect+1: error: controlling expression must be bool, not 'int' [333] */ 316 if (ferror(f)) 317 return; 318 /* expect+1: error: controlling expression must be bool, not 'int' [333] */ 319 if (strcmp(a, b)) 320 return; 321 /* expect+1: error: operand of '!' must be bool, not 'int' [330] */ 322 if (!ferror(f)) 323 return; 324 /* expect+1: error: operand of '!' must be bool, not 'int' [330] */ 325 if (!strcmp(a, b)) 326 return; 327 328 /* 329 * Before tree.c 1.395 from 2021-11-16, the expression below didn't 330 * produce a warning since the expression 'stdio_files' came from a 331 * system header (via a macro), and this property was passed up to 332 * the expression 'ferror(stdio_files[1])'. 333 * 334 * That was wrong though since the type of a function call expression 335 * only depends on the function itself but not its arguments types. 336 * The old rule had allowed a raw condition 'strcmp(a, b)' without 337 * the comparison '!= 0', as long as one of its arguments came from a 338 * system header. 339 * 340 * Seen in bin/echo/echo.c, function main, call to ferror. 341 */ 342 /* expect+5: error: controlling expression must be bool, not 'int' [333] */ 343 if (ferror( 344# 345 "d_c99_bool_strict_syshdr.c" 3 4 345 &stdio_files[1] 346# 347 "d_c99_bool_strict_syshdr.c" 347 )) 348 return; 349 350 /* 351 * Before cgram.y 1.369 from 2021-11-16, at the end of parsing the 352 * name 'stdio_stdout', the parser already looked ahead to the next 353 * token, to see whether it was the '(' of a function call. 354 * 355 * At that point, the parser was no longer in a system header, 356 * therefore 'stdio_stdout' had tn_sys == false, and this information 357 * was pushed down to the whole function call expression (which was 358 * another bug that got fixed in tree.c 1.395 from 2021-11-16). 359 */ 360 /* expect+5: error: controlling expression must be bool, not 'int' [333] */ 361 if (ferror( 362# 363 "d_c99_bool_strict_syshdr.c" 3 4 363 stdio_stdout 364# 365 "d_c99_bool_strict_syshdr.c" 365 )) 366 return; 367 368 /* 369 * In this variant of the pattern, there is a token ')' after the 370 * name 'stdio_stdout', which even before tree.c 1.395 from 371 * 2021-11-16 had the effect that at the end of parsing the name, the 372 * parser was still in the system header, thus setting tn_sys (or 373 * rather tn_relaxed at that time) to true. 374 */ 375 /* expect+5: error: controlling expression must be bool, not 'int' [333] */ 376 if (ferror( 377# 378 "d_c99_bool_strict_syshdr.c" 3 4 378 (stdio_stdout) 379# 380 "d_c99_bool_strict_syshdr.c" 380 )) 381 return; 382 383 /* 384 * Before cgram.y 1.369 from 2021-11-16, the comment following 385 * 'stdio_stdout' did not prevent the search for '('. At the point 386 * where build_name called expr_alloc_tnode, the parser was already 387 * in the main file again, thus treating 'stdio_stdout' as not coming 388 * from a system header. 389 * 390 * This has been fixed in tree.c 1.395 from 2021-11-16. Before that, 391 * an expression had come from a system header if its operands came 392 * from a system header, but that was only close to the truth. In a 393 * case where both operands come from a system header but the 394 * operator comes from the main translation unit, the main 395 * translation unit still has control over the whole expression. So 396 * the correct approach is to focus on the operator, not the 397 * operands. There are a few corner cases where the operator is 398 * invisible (for implicit conversions) or synthetic (for translating 399 * 'arr[index]' to '*(arr + index)', but these are handled as well. 400 */ 401 /* expect+5: error: controlling expression must be bool, not 'int' [333] */ 402 if (ferror( 403# 404 "d_c99_bool_strict_syshdr.c" 3 4 404 stdio_stdout /* comment */ 405# 406 "d_c99_bool_strict_syshdr.c" 406 )) 407 return; 408} 409