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