xref: /netbsd-src/tests/usr.bin/xlint/lint1/d_c99_bool_strict_syshdr.c (revision a623cf0b61440d709d496b4325d530e07f25fb94)
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