1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
|
/* $NetBSD: d_c99_bool_strict_syshdr.c,v 1.20 2023/07/09 10:42:07 rillig Exp $ */
# 3 "d_c99_bool_strict_syshdr.c"
/*
* In strict bool mode, lint treats bool as incompatible with any other scalar
* types. This mode helps in migrating code from pre-C99 to C99.
*
* System headers, on the other hand, cannot be migrated if they need to stay
* compatible with pre-C99 code. Therefore, the checks for system headers are
* loosened. In contexts where a scalar expression is compared to 0, macros
* and functions from system headers may use int expressions as well.
*/
/* lint1-extra-flags: -T -X 351 */
extern const unsigned short *ctype_table;
extern void println(const char *);
/*
* On NetBSD 8, <sys/select.h> defines FD_ISSET by enclosing the statements
* in the well-known 'do { ... } while (CONSTCOND 0)' loop. The 0 in the
* controlling expression has type INT but should be allowed nevertheless
* since that header does not have a way to distinguish between bool and int.
* It just follows the C99 standard, unlike the lint-provided stdbool.h,
* which redefines 'false' to '__lint_false'.
*/
void
strict_bool_system_header_statement_macro(void)
{
do {
println("nothing");
} while (/*CONSTCOND*/0);
/* expect-1: error: controlling expression must be bool, not 'int' [333] */
# 38 "d_c99_bool_strict_syshdr.c" 3 4
do {
println("nothing");
} while (/*CONSTCOND*/0); /* ok */
# 43 "d_c99_bool_strict_syshdr.c"
do {
println("nothing");
} while (/*CONSTCOND*/0);
/* expect-1: error: controlling expression must be bool, not 'int' [333] */
}
/*
* The macros from <ctype.h> can be implemented in different ways. The C
* standard defines them as returning 'int'. In strict bool mode, the actual
* return type can be INT or BOOL, depending on whether the macros do the
* comparison against 0 themselves.
*
* Since that comparison is more code to write and in exceptional situations
* more code to execute, they will probably leave out the extra comparison,
* but both ways are possible.
*
* In strict bool mode, there must be a way to call these function-like macros
* portably, without triggering type errors, no matter whether they return
* BOOL or INT.
*
* The expressions from this example cross the boundary between system header
* and application code. They need to carry the information that they are
* half-BOOL, half-INT across to the enclosing expressions.
*/
void
strict_bool_system_header_ctype(int c)
{
/*
* The macro returns INT, which may be outside the range of a
* uint8_t variable, therefore it must not be assigned directly.
* All other combinations of type are safe from truncation.
*/
_Bool system_int_assigned_to_bool =
# 78 "d_c99_bool_strict_syshdr.c" 3 4
(int)((ctype_table + 1)[c] & 0x0040) /* INT */
# 80 "d_c99_bool_strict_syshdr.c"
;
/* expect-1: error: operands of 'init' have incompatible types '_Bool' and 'int' [107] */
int system_bool_assigned_to_int =
# 85 "d_c99_bool_strict_syshdr.c" 3 4
(int)((ctype_table + 1)[c] & 0x0040) != 0 /* BOOL */
# 87 "d_c99_bool_strict_syshdr.c"
;
if (
# 91 "d_c99_bool_strict_syshdr.c" 3 4
(int)((ctype_table + 1)[c] & 0x0040) /* INT */
# 93 "d_c99_bool_strict_syshdr.c"
)
println("system macro returning INT");
if (
# 98 "d_c99_bool_strict_syshdr.c" 3 4
((ctype_table + 1)[c] & 0x0040) != 0 /* BOOL */
# 100 "d_c99_bool_strict_syshdr.c"
)
println("system macro returning BOOL");
}
static inline _Bool
ch_isspace_sys_int(char c)
{
return
# 109 "d_c99_bool_strict_syshdr.c" 3 4
((ctype_table + 1)[c] & 0x0040)
# 111 "d_c99_bool_strict_syshdr.c"
!= 0;
}
/*
* isspace is defined to return an int. Comparing this int with 0 is the
* safe way to convert it to _Bool. This must be allowed even if isspace
* does the comparison itself.
*/
static inline _Bool
ch_isspace_sys_bool(char c)
{
return
# 124 "d_c99_bool_strict_syshdr.c" 3 4
((ctype_table + 1)[(unsigned char)c] & 0x0040) != 0
# 126 "d_c99_bool_strict_syshdr.c"
!= 0;
}
/*
* There are several functions from system headers that have return type
* int. For this return type there are many API conventions:
*
* * isspace: 0 means no, non-zero means yes
* * open: 0 means success, -1 means failure
* * main: 0 means success, non-zero means failure
* * strcmp: 0 means equal, < 0 means less than, > 0 means greater than
*
* Without a detailed list of individual functions, it's not possible to
* guess what the return value means. Therefore, in strict bool mode, the
* return value of these functions cannot be implicitly converted to bool,
* not even in a controlling expression. Allowing that would allow
* expressions like !strcmp(s1, s2), which is not correct since strcmp
* returns an "ordered comparison result", not a bool.
*/
# 1 "math.h" 3 4
extern int finite(double);
# 1 "string.h" 3 4
extern int strcmp(const char *, const char *);
# 151 "d_c99_bool_strict_syshdr.c"
/*ARGSUSED*/
_Bool
call_finite_bad(double d)
{
/* expect+1: error: function has return type '_Bool' but returns 'int' [211] */
return finite(d);
}
_Bool
call_finite_good(double d)
{
return finite(d) != 0;
}
/*ARGSUSED*/
_Bool
str_equal_bad(const char *s1, const char *s2)
{
/* expect+2: error: operand of '!' must be bool, not 'int' [330] */
/* expect+1: warning: function 'str_equal_bad' expects to return value [214] */
return !strcmp(s1, s2);
}
_Bool
str_equal_good(const char *s1, const char *s2)
{
return strcmp(s1, s2) == 0;
}
int read_char(void);
/*
* Between tree.c 1.395 from 2021-11-16 and ckbool.c 1.10 from 2021-12-22,
* lint wrongly complained that the controlling expression would have to be
* _Bool instead of int. Since the right-hand side of the ',' operator comes
* from a system header, this is OK though.
*/
void
controlling_expression_with_comma_operator(void)
{
int c;
while (c = read_char(),
# 197 "d_c99_bool_strict_syshdr.c" 3 4
((int)((ctype_table + 1)[(
# 199 "d_c99_bool_strict_syshdr.c"
c
# 201 "d_c99_bool_strict_syshdr.c" 3 4
)] & 0x0040 /* Space */))
# 203 "d_c99_bool_strict_syshdr.c"
)
continue;
}
void take_bool(_Bool);
/*
* On NetBSD, the header <curses.h> defines TRUE or FALSE as integer
* constants with a CONSTCOND comment. This comment suppresses legitimate
* warnings in user code; that's irrelevant for this test though.
*
* Several curses functions take bool as a parameter, for example keypad or
* leaveok. Before ckbool.c 1.14 from 2022-05-19, lint did not complain when
* these functions get 0 instead of 'false' as an argument. It did complain
* about 1 instead of 'true' though.
*/
void
pass_bool_to_function(void)
{
/* expect+5: error: argument 1 expects '_Bool', gets passed 'int' [334] */
take_bool(
# 227 "d_c99_bool_strict_syshdr.c" 3 4
(/*CONSTCOND*/1)
# 229 "d_c99_bool_strict_syshdr.c"
);
take_bool(
# 233 "d_c99_bool_strict_syshdr.c" 3 4
__lint_true
# 235 "d_c99_bool_strict_syshdr.c"
);
/* expect+5: error: argument 1 expects '_Bool', gets passed 'int' [334] */
take_bool(
# 240 "d_c99_bool_strict_syshdr.c" 3 4
(/*CONSTCOND*/0)
# 242 "d_c99_bool_strict_syshdr.c"
);
take_bool(
# 246 "d_c99_bool_strict_syshdr.c" 3 4
__lint_false
# 248 "d_c99_bool_strict_syshdr.c"
);
}
extern int *errno_location(void);
/*
* As of 2022-06-11, the rule for loosening the strict boolean check for
* expressions from system headers is flawed. That rule allows statements
* like 'if (NULL)' or 'if (errno)', even though these have pointer type or
* integer type.
*/
void
if_pointer_or_int(void)
{
/* if (NULL) */
if (
# 266 "d_c99_bool_strict_syshdr.c" 3 4
((void *)0)
# 268 "d_c99_bool_strict_syshdr.c"
)
return;
/* expect-1: warning: statement not reached [193] */
/* if (EXIT_SUCCESS) */
if (
# 275 "d_c99_bool_strict_syshdr.c" 3 4
0
# 277 "d_c99_bool_strict_syshdr.c"
)
return;
/* expect-1: warning: statement not reached [193] */
/* if (errno) */
if (
# 284 "d_c99_bool_strict_syshdr.c" 3 4
(*errno_location())
# 286 "d_c99_bool_strict_syshdr.c"
)
return;
}
|