d_c99_bool_strict_syshdr.c revision 1.25 1 /* $NetBSD: d_c99_bool_strict_syshdr.c,v 1.25 2024/11/13 04:32:49 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
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 */
30 void
31 statement_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 */
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' 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
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 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
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: function has return type '_Bool' but returns '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: error: 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: 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
252 extern 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 */
260 void
261 if_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 /* expect+1: warning: 'return' statement not reached [193] */
270 return;
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 /* expect+1: warning: 'return' statement not reached [193] */
279 return;
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
301 typedef struct stdio_file {
302 int fd;
303 } FILE;
304 int ferror(FILE *);
305 FILE stdio_files[3];
306 FILE *stdio_stdout;
307 # 308 "d_c99_bool_strict_syshdr.c" 2
308 # 1 "string.h" 1 3 4
309 int strcmp(const char *, const char *);
310 # 311 "d_c99_bool_strict_syshdr.c" 2
311
312 void
313 controlling_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