Home | History | Annotate | Line # | Download | only in lint1
      1  1.27  rillig /*	$NetBSD: d_c99_bool_strict_syshdr.c,v 1.27 2025/04/10 20:37:48 rillig Exp $	*/
      2   1.1  rillig # 3 "d_c99_bool_strict_syshdr.c"
      3   1.1  rillig 
      4   1.1  rillig /*
      5   1.8  rillig  * In strict bool mode, lint treats bool as incompatible with any other scalar
      6   1.8  rillig  * types.  This mode helps in migrating code from pre-C99 to C99.
      7   1.8  rillig  *
      8   1.8  rillig  * System headers, on the other hand, cannot be migrated if they need to stay
      9   1.8  rillig  * compatible with pre-C99 code.  Therefore, the checks for system headers are
     10   1.8  rillig  * loosened.  In contexts where a scalar expression is compared to 0, macros
     11   1.8  rillig  * and functions from system headers may use int expressions as well.
     12   1.1  rillig  */
     13   1.1  rillig 
     14  1.19  rillig /* lint1-extra-flags: -T -X 351 */
     15   1.1  rillig 
     16   1.3  rillig extern const unsigned short *ctype_table;
     17   1.3  rillig 
     18   1.3  rillig extern void println(const char *);
     19   1.3  rillig 
     20  1.24  rillig 
     21   1.1  rillig /*
     22  1.24  rillig  * No matter whether the code is from a system header or not, the idiom
     23  1.24  rillig  * 'do { ... } while (0)' is well known, and using the integer constant 0
     24  1.24  rillig  * instead of the boolean constant 'false' neither creates any type confusion
     25  1.24  rillig  * nor does its value take place in any conversions, as its scope is limited
     26  1.24  rillig  * to the controlling expression of the loop.
     27   1.1  rillig  */
     28   1.1  rillig void
     29  1.24  rillig statement_macro(void)
     30   1.1  rillig {
     31   1.1  rillig 
     32   1.1  rillig 	do {
     33   1.1  rillig 		println("nothing");
     34  1.27  rillig 	} while (0);
     35   1.1  rillig 
     36  1.26  rillig # 37 "d_c99_bool_strict_syshdr.c" 3 4
     37   1.1  rillig 	do {
     38   1.1  rillig 		println("nothing");
     39  1.27  rillig 	} while (0);
     40   1.1  rillig 
     41  1.26  rillig # 42 "d_c99_bool_strict_syshdr.c"
     42   1.1  rillig 	do {
     43   1.1  rillig 		println("nothing");
     44  1.27  rillig 	} while (0);
     45   1.1  rillig }
     46   1.1  rillig 
     47   1.1  rillig 
     48   1.1  rillig /*
     49   1.1  rillig  * The macros from <ctype.h> can be implemented in different ways.  The C
     50   1.1  rillig  * standard defines them as returning 'int'.  In strict bool mode, the actual
     51   1.1  rillig  * return type can be INT or BOOL, depending on whether the macros do the
     52   1.1  rillig  * comparison against 0 themselves.
     53   1.1  rillig  *
     54   1.3  rillig  * Since that comparison is more code to write and in exceptional situations
     55   1.3  rillig  * more code to execute, they will probably leave out the extra comparison,
     56   1.3  rillig  * but both ways are possible.
     57   1.1  rillig  *
     58   1.3  rillig  * In strict bool mode, there must be a way to call these function-like macros
     59   1.1  rillig  * portably, without triggering type errors, no matter whether they return
     60   1.1  rillig  * BOOL or INT.
     61   1.1  rillig  *
     62   1.1  rillig  * The expressions from this example cross the boundary between system header
     63   1.1  rillig  * and application code.  They need to carry the information that they are
     64   1.3  rillig  * half-BOOL, half-INT across to the enclosing expressions.
     65   1.1  rillig  */
     66   1.1  rillig void
     67   1.1  rillig strict_bool_system_header_ctype(int c)
     68   1.1  rillig {
     69   1.1  rillig 	/*
     70   1.1  rillig 	 * The macro returns INT, which may be outside the range of a
     71   1.1  rillig 	 * uint8_t variable, therefore it must not be assigned directly.
     72   1.1  rillig 	 * All other combinations of type are safe from truncation.
     73   1.1  rillig 	 */
     74   1.1  rillig 	_Bool system_int_assigned_to_bool =
     75  1.26  rillig # 76 "d_c99_bool_strict_syshdr.c" 3 4
     76   1.1  rillig 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     77  1.26  rillig # 78 "d_c99_bool_strict_syshdr.c"
     78  1.10  rillig 	;
     79  1.17  rillig 	/* expect-1: error: operands of 'init' have incompatible types '_Bool' and 'int' [107] */
     80   1.1  rillig 
     81   1.1  rillig 	int system_bool_assigned_to_int =
     82  1.26  rillig # 83 "d_c99_bool_strict_syshdr.c" 3 4
     83   1.1  rillig 	    (int)((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     84  1.26  rillig # 85 "d_c99_bool_strict_syshdr.c"
     85   1.4  rillig 	;
     86   1.1  rillig 
     87   1.1  rillig 	if (
     88  1.26  rillig # 89 "d_c99_bool_strict_syshdr.c" 3 4
     89   1.1  rillig 	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
     90  1.26  rillig # 91 "d_c99_bool_strict_syshdr.c"
     91   1.4  rillig 	)
     92   1.3  rillig 		println("system macro returning INT");
     93   1.1  rillig 
     94   1.1  rillig 	if (
     95  1.26  rillig # 96 "d_c99_bool_strict_syshdr.c" 3 4
     96   1.1  rillig 	    ((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
     97  1.26  rillig # 98 "d_c99_bool_strict_syshdr.c"
     98   1.1  rillig 	)
     99   1.3  rillig 		println("system macro returning BOOL");
    100   1.3  rillig }
    101   1.3  rillig 
    102   1.3  rillig static inline _Bool
    103   1.3  rillig ch_isspace_sys_int(char c)
    104   1.3  rillig {
    105   1.3  rillig 	return
    106  1.26  rillig # 107 "d_c99_bool_strict_syshdr.c" 3 4
    107   1.3  rillig 	    ((ctype_table + 1)[c] & 0x0040)
    108  1.26  rillig # 109 "d_c99_bool_strict_syshdr.c"
    109   1.3  rillig 	    != 0;
    110   1.3  rillig }
    111   1.3  rillig 
    112   1.3  rillig /*
    113   1.3  rillig  * isspace is defined to return an int.  Comparing this int with 0 is the
    114   1.3  rillig  * safe way to convert it to _Bool.  This must be allowed even if isspace
    115   1.3  rillig  * does the comparison itself.
    116   1.3  rillig  */
    117   1.3  rillig static inline _Bool
    118   1.5  rillig ch_isspace_sys_bool(char c)
    119   1.3  rillig {
    120   1.3  rillig 	return
    121  1.26  rillig # 122 "d_c99_bool_strict_syshdr.c" 3 4
    122   1.3  rillig 	    ((ctype_table + 1)[(unsigned char)c] & 0x0040) != 0
    123  1.26  rillig # 124 "d_c99_bool_strict_syshdr.c"
    124   1.5  rillig 	    != 0;
    125   1.1  rillig }
    126   1.6  rillig 
    127   1.6  rillig /*
    128   1.7  rillig  * There are several functions from system headers that have return type
    129   1.7  rillig  * int.  For this return type there are many API conventions:
    130   1.7  rillig  *
    131   1.7  rillig  *	* isspace: 0 means no, non-zero means yes
    132   1.7  rillig  *	* open: 0 means success, -1 means failure
    133   1.7  rillig  *	* main: 0 means success, non-zero means failure
    134   1.7  rillig  *	* strcmp: 0 means equal, < 0 means less than, > 0 means greater than
    135   1.7  rillig  *
    136   1.7  rillig  * Without a detailed list of individual functions, it's not possible to
    137  1.16  rillig  * guess what the return value means.  Therefore, in strict bool mode, the
    138   1.7  rillig  * return value of these functions cannot be implicitly converted to bool,
    139  1.16  rillig  * not even in a controlling expression.  Allowing that would allow
    140  1.16  rillig  * expressions like !strcmp(s1, s2), which is not correct since strcmp
    141  1.16  rillig  * returns an "ordered comparison result", not a bool.
    142   1.6  rillig  */
    143   1.6  rillig 
    144   1.6  rillig # 1 "math.h" 3 4
    145   1.6  rillig extern int finite(double);
    146   1.7  rillig # 1 "string.h" 3 4
    147   1.7  rillig extern int strcmp(const char *, const char *);
    148  1.26  rillig # 149 "d_c99_bool_strict_syshdr.c"
    149   1.7  rillig 
    150   1.7  rillig /*ARGSUSED*/
    151   1.7  rillig _Bool
    152   1.7  rillig call_finite_bad(double d)
    153   1.7  rillig {
    154  1.18  rillig 	/* expect+1: error: function has return type '_Bool' but returns 'int' [211] */
    155  1.10  rillig 	return finite(d);
    156   1.7  rillig }
    157   1.7  rillig 
    158   1.7  rillig _Bool
    159   1.7  rillig call_finite_good(double d)
    160   1.7  rillig {
    161   1.7  rillig 	return finite(d) != 0;
    162   1.7  rillig }
    163   1.7  rillig 
    164   1.7  rillig /*ARGSUSED*/
    165   1.7  rillig _Bool
    166   1.7  rillig str_equal_bad(const char *s1, const char *s2)
    167   1.7  rillig {
    168  1.10  rillig 	/* expect+2: error: operand of '!' must be bool, not 'int' [330] */
    169  1.22  rillig 	/* expect+1: error: function 'str_equal_bad' expects to return value [214] */
    170  1.10  rillig 	return !strcmp(s1, s2);
    171   1.7  rillig }
    172   1.6  rillig 
    173   1.6  rillig _Bool
    174   1.7  rillig str_equal_good(const char *s1, const char *s2)
    175   1.6  rillig {
    176   1.7  rillig 	return strcmp(s1, s2) == 0;
    177   1.6  rillig }
    178  1.11  rillig 
    179  1.11  rillig 
    180  1.11  rillig int read_char(void);
    181  1.11  rillig 
    182  1.12  rillig /*
    183  1.12  rillig  * Between tree.c 1.395 from 2021-11-16 and ckbool.c 1.10 from 2021-12-22,
    184  1.12  rillig  * lint wrongly complained that the controlling expression would have to be
    185  1.12  rillig  * _Bool instead of int.  Since the right-hand side of the ',' operator comes
    186  1.12  rillig  * from a system header, this is OK though.
    187  1.12  rillig  */
    188  1.11  rillig void
    189  1.11  rillig controlling_expression_with_comma_operator(void)
    190  1.11  rillig {
    191  1.11  rillig 	int c;
    192  1.11  rillig 
    193  1.11  rillig 	while (c = read_char(),
    194  1.26  rillig # 195 "d_c99_bool_strict_syshdr.c" 3 4
    195  1.11  rillig 	    ((int)((ctype_table + 1)[(
    196  1.26  rillig # 197 "d_c99_bool_strict_syshdr.c"
    197  1.11  rillig 		c
    198  1.26  rillig # 199 "d_c99_bool_strict_syshdr.c" 3 4
    199  1.11  rillig 	    )] & 0x0040 /* Space     */))
    200  1.26  rillig # 201 "d_c99_bool_strict_syshdr.c"
    201  1.11  rillig 	    )
    202  1.11  rillig 		continue;
    203  1.11  rillig }
    204  1.13  rillig 
    205  1.13  rillig 
    206  1.13  rillig void take_bool(_Bool);
    207  1.13  rillig 
    208  1.13  rillig /*
    209  1.13  rillig  * On NetBSD, the header <curses.h> defines TRUE or FALSE as integer
    210  1.27  rillig  * constants, not as bool constants.
    211  1.27  rillig  *
    212  1.13  rillig  *
    213  1.13  rillig  * Several curses functions take bool as a parameter, for example keypad or
    214  1.15  rillig  * leaveok.  Before ckbool.c 1.14 from 2022-05-19, lint did not complain when
    215  1.15  rillig  * these functions get 0 instead of 'false' as an argument.  It did complain
    216  1.15  rillig  * about 1 instead of 'true' though.
    217  1.13  rillig  */
    218  1.13  rillig void
    219  1.13  rillig pass_bool_to_function(void)
    220  1.13  rillig {
    221  1.13  rillig 
    222  1.21  rillig 	/* expect+5: error: parameter 1 expects '_Bool', gets passed 'int' [334] */
    223  1.13  rillig 	take_bool(
    224  1.26  rillig # 225 "d_c99_bool_strict_syshdr.c" 3 4
    225  1.27  rillig 	    (1)
    226  1.26  rillig # 227 "d_c99_bool_strict_syshdr.c"
    227  1.13  rillig 	);
    228  1.13  rillig 
    229  1.13  rillig 	take_bool(
    230  1.26  rillig # 231 "d_c99_bool_strict_syshdr.c" 3 4
    231  1.13  rillig 	    __lint_true
    232  1.26  rillig # 233 "d_c99_bool_strict_syshdr.c"
    233  1.13  rillig 	);
    234  1.13  rillig 
    235  1.21  rillig 	/* expect+5: error: parameter 1 expects '_Bool', gets passed 'int' [334] */
    236  1.13  rillig 	take_bool(
    237  1.26  rillig # 238 "d_c99_bool_strict_syshdr.c" 3 4
    238  1.27  rillig 	    (0)
    239  1.26  rillig # 240 "d_c99_bool_strict_syshdr.c"
    240  1.13  rillig 	);
    241  1.13  rillig 
    242  1.13  rillig 	take_bool(
    243  1.26  rillig # 244 "d_c99_bool_strict_syshdr.c" 3 4
    244  1.13  rillig 	    __lint_false
    245  1.26  rillig # 246 "d_c99_bool_strict_syshdr.c"
    246  1.13  rillig 	);
    247  1.13  rillig }
    248  1.16  rillig 
    249  1.16  rillig 
    250  1.16  rillig extern int *errno_location(void);
    251  1.16  rillig 
    252  1.16  rillig /*
    253  1.16  rillig  * As of 2022-06-11, the rule for loosening the strict boolean check for
    254  1.16  rillig  * expressions from system headers is flawed.  That rule allows statements
    255  1.16  rillig  * like 'if (NULL)' or 'if (errno)', even though these have pointer type or
    256  1.16  rillig  * integer type.
    257  1.16  rillig  */
    258  1.16  rillig void
    259  1.16  rillig if_pointer_or_int(void)
    260  1.16  rillig {
    261  1.16  rillig 	/* if (NULL) */
    262  1.16  rillig 	if (
    263  1.26  rillig # 264 "d_c99_bool_strict_syshdr.c" 3 4
    264  1.16  rillig 	    ((void *)0)
    265  1.26  rillig # 266 "d_c99_bool_strict_syshdr.c"
    266  1.16  rillig 		       )
    267  1.25  rillig 		/* expect+1: warning: 'return' statement not reached [193] */
    268  1.16  rillig 		return;
    269  1.16  rillig 
    270  1.16  rillig 	/* if (EXIT_SUCCESS) */
    271  1.16  rillig 	if (
    272  1.26  rillig # 273 "d_c99_bool_strict_syshdr.c" 3 4
    273  1.16  rillig 	    0
    274  1.26  rillig # 275 "d_c99_bool_strict_syshdr.c"
    275  1.16  rillig 		       )
    276  1.25  rillig 		/* expect+1: warning: 'return' statement not reached [193] */
    277  1.16  rillig 		return;
    278  1.16  rillig 
    279  1.16  rillig 	/* if (errno) */
    280  1.16  rillig 	if (
    281  1.26  rillig # 282 "d_c99_bool_strict_syshdr.c" 3 4
    282  1.16  rillig 	    (*errno_location())
    283  1.26  rillig # 284 "d_c99_bool_strict_syshdr.c"
    284  1.16  rillig 		       )
    285  1.16  rillig 		return;
    286  1.16  rillig }
    287  1.23  rillig 
    288  1.23  rillig 
    289  1.23  rillig /*
    290  1.23  rillig  * For expressions that originate from a system header, the strict type rules
    291  1.23  rillig  * are relaxed a bit, to allow for expressions like 'flags & FLAG', even
    292  1.23  rillig  * though they are not strictly boolean.
    293  1.23  rillig  *
    294  1.23  rillig  * This shouldn't apply to function call expressions though since one of the
    295  1.23  rillig  * goals of strict bool mode is to normalize all expressions calling 'strcmp'
    296  1.23  rillig  * to be of the form 'strcmp(a, b) == 0' instead of '!strcmp(a, b)'.
    297  1.23  rillig  */
    298  1.23  rillig # 1 "stdio.h" 1 3 4
    299  1.23  rillig typedef struct stdio_file {
    300  1.23  rillig 	int fd;
    301  1.23  rillig } FILE;
    302  1.23  rillig int ferror(FILE *);
    303  1.23  rillig FILE stdio_files[3];
    304  1.23  rillig FILE *stdio_stdout;
    305  1.26  rillig # 306 "d_c99_bool_strict_syshdr.c" 2
    306  1.23  rillig # 1 "string.h" 1 3 4
    307  1.23  rillig int strcmp(const char *, const char *);
    308  1.26  rillig # 309 "d_c99_bool_strict_syshdr.c" 2
    309  1.23  rillig 
    310  1.23  rillig void
    311  1.23  rillig controlling_expression(FILE *f, const char *a, const char *b)
    312  1.23  rillig {
    313  1.23  rillig 	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
    314  1.23  rillig 	if (ferror(f))
    315  1.23  rillig 		return;
    316  1.23  rillig 	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
    317  1.23  rillig 	if (strcmp(a, b))
    318  1.23  rillig 		return;
    319  1.23  rillig 	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
    320  1.23  rillig 	if (!ferror(f))
    321  1.23  rillig 		return;
    322  1.23  rillig 	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
    323  1.23  rillig 	if (!strcmp(a, b))
    324  1.23  rillig 		return;
    325  1.23  rillig 
    326  1.23  rillig 	/*
    327  1.23  rillig 	 * Before tree.c 1.395 from 2021-11-16, the expression below didn't
    328  1.23  rillig 	 * produce a warning since the expression 'stdio_files' came from a
    329  1.23  rillig 	 * system header (via a macro), and this property was passed up to
    330  1.23  rillig 	 * the expression 'ferror(stdio_files[1])'.
    331  1.23  rillig 	 *
    332  1.23  rillig 	 * That was wrong though since the type of a function call expression
    333  1.23  rillig 	 * only depends on the function itself but not its arguments types.
    334  1.23  rillig 	 * The old rule had allowed a raw condition 'strcmp(a, b)' without
    335  1.23  rillig 	 * the comparison '!= 0', as long as one of its arguments came from a
    336  1.23  rillig 	 * system header.
    337  1.23  rillig 	 *
    338  1.23  rillig 	 * Seen in bin/echo/echo.c, function main, call to ferror.
    339  1.23  rillig 	 */
    340  1.23  rillig 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    341  1.23  rillig 	if (ferror(
    342  1.26  rillig # 343 "d_c99_bool_strict_syshdr.c" 3 4
    343  1.23  rillig 	    &stdio_files[1]
    344  1.26  rillig # 345 "d_c99_bool_strict_syshdr.c"
    345  1.23  rillig 	    ))
    346  1.23  rillig 		return;
    347  1.23  rillig 
    348  1.23  rillig 	/*
    349  1.23  rillig 	 * Before cgram.y 1.369 from 2021-11-16, at the end of parsing the
    350  1.23  rillig 	 * name 'stdio_stdout', the parser already looked ahead to the next
    351  1.23  rillig 	 * token, to see whether it was the '(' of a function call.
    352  1.23  rillig 	 *
    353  1.23  rillig 	 * At that point, the parser was no longer in a system header,
    354  1.23  rillig 	 * therefore 'stdio_stdout' had tn_sys == false, and this information
    355  1.23  rillig 	 * was pushed down to the whole function call expression (which was
    356  1.23  rillig 	 * another bug that got fixed in tree.c 1.395 from 2021-11-16).
    357  1.23  rillig 	 */
    358  1.23  rillig 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    359  1.23  rillig 	if (ferror(
    360  1.26  rillig # 361 "d_c99_bool_strict_syshdr.c" 3 4
    361  1.23  rillig 	    stdio_stdout
    362  1.26  rillig # 363 "d_c99_bool_strict_syshdr.c"
    363  1.23  rillig 	    ))
    364  1.23  rillig 		return;
    365  1.23  rillig 
    366  1.23  rillig 	/*
    367  1.23  rillig 	 * In this variant of the pattern, there is a token ')' after the
    368  1.23  rillig 	 * name 'stdio_stdout', which even before tree.c 1.395 from
    369  1.23  rillig 	 * 2021-11-16 had the effect that at the end of parsing the name, the
    370  1.23  rillig 	 * parser was still in the system header, thus setting tn_sys (or
    371  1.23  rillig 	 * rather tn_relaxed at that time) to true.
    372  1.23  rillig 	 */
    373  1.23  rillig 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    374  1.23  rillig 	if (ferror(
    375  1.26  rillig # 376 "d_c99_bool_strict_syshdr.c" 3 4
    376  1.23  rillig 	    (stdio_stdout)
    377  1.26  rillig # 378 "d_c99_bool_strict_syshdr.c"
    378  1.23  rillig 	    ))
    379  1.23  rillig 		return;
    380  1.23  rillig 
    381  1.23  rillig 	/*
    382  1.23  rillig 	 * Before cgram.y 1.369 from 2021-11-16, the comment following
    383  1.23  rillig 	 * 'stdio_stdout' did not prevent the search for '('.  At the point
    384  1.23  rillig 	 * where build_name called expr_alloc_tnode, the parser was already
    385  1.23  rillig 	 * in the main file again, thus treating 'stdio_stdout' as not coming
    386  1.23  rillig 	 * from a system header.
    387  1.23  rillig 	 *
    388  1.23  rillig 	 * This has been fixed in tree.c 1.395 from 2021-11-16.  Before that,
    389  1.23  rillig 	 * an expression had come from a system header if its operands came
    390  1.23  rillig 	 * from a system header, but that was only close to the truth.  In a
    391  1.23  rillig 	 * case where both operands come from a system header but the
    392  1.23  rillig 	 * operator comes from the main translation unit, the main
    393  1.23  rillig 	 * translation unit still has control over the whole expression.  So
    394  1.23  rillig 	 * the correct approach is to focus on the operator, not the
    395  1.23  rillig 	 * operands.  There are a few corner cases where the operator is
    396  1.23  rillig 	 * invisible (for implicit conversions) or synthetic (for translating
    397  1.23  rillig 	 * 'arr[index]' to '*(arr + index)', but these are handled as well.
    398  1.23  rillig 	 */
    399  1.23  rillig 	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
    400  1.23  rillig 	if (ferror(
    401  1.26  rillig # 402 "d_c99_bool_strict_syshdr.c" 3 4
    402  1.23  rillig 	    stdio_stdout /* comment */
    403  1.26  rillig # 404 "d_c99_bool_strict_syshdr.c"
    404  1.23  rillig 	    ))
    405  1.23  rillig 		return;
    406  1.23  rillig }
    407