/*	$NetBSD: d_c99_bool_strict_syshdr.c,v 1.27 2025/04/10 20:37:48 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 *);


/*
 * No matter whether the code is from a system header or not, the idiom
 * 'do { ... } while (0)' is well known, and using the integer constant 0
 * instead of the boolean constant 'false' neither creates any type confusion
 * nor does its value take place in any conversions, as its scope is limited
 * to the controlling expression of the loop.
 */
void
statement_macro(void)
{

	do {
		println("nothing");
	} while (0);

# 37 "d_c99_bool_strict_syshdr.c" 3 4
	do {
		println("nothing");
	} while (0);

# 42 "d_c99_bool_strict_syshdr.c"
	do {
		println("nothing");
	} while (0);
}


/*
 * 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 =
# 76 "d_c99_bool_strict_syshdr.c" 3 4
	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
# 78 "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 =
# 83 "d_c99_bool_strict_syshdr.c" 3 4
	    (int)((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
# 85 "d_c99_bool_strict_syshdr.c"
	;

	if (
# 89 "d_c99_bool_strict_syshdr.c" 3 4
	    (int)((ctype_table + 1)[c] & 0x0040)	/* INT */
# 91 "d_c99_bool_strict_syshdr.c"
	)
		println("system macro returning INT");

	if (
# 96 "d_c99_bool_strict_syshdr.c" 3 4
	    ((ctype_table + 1)[c] & 0x0040) != 0	/* BOOL */
# 98 "d_c99_bool_strict_syshdr.c"
	)
		println("system macro returning BOOL");
}

static inline _Bool
ch_isspace_sys_int(char c)
{
	return
# 107 "d_c99_bool_strict_syshdr.c" 3 4
	    ((ctype_table + 1)[c] & 0x0040)
# 109 "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
# 122 "d_c99_bool_strict_syshdr.c" 3 4
	    ((ctype_table + 1)[(unsigned char)c] & 0x0040) != 0
# 124 "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 *);
# 149 "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: error: 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(),
# 195 "d_c99_bool_strict_syshdr.c" 3 4
	    ((int)((ctype_table + 1)[(
# 197 "d_c99_bool_strict_syshdr.c"
		c
# 199 "d_c99_bool_strict_syshdr.c" 3 4
	    )] & 0x0040 /* Space     */))
# 201 "d_c99_bool_strict_syshdr.c"
	    )
		continue;
}


void take_bool(_Bool);

/*
 * On NetBSD, the header <curses.h> defines TRUE or FALSE as integer
 * constants, not as bool constants.
 *
 *
 * 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: parameter 1 expects '_Bool', gets passed 'int' [334] */
	take_bool(
# 225 "d_c99_bool_strict_syshdr.c" 3 4
	    (1)
# 227 "d_c99_bool_strict_syshdr.c"
	);

	take_bool(
# 231 "d_c99_bool_strict_syshdr.c" 3 4
	    __lint_true
# 233 "d_c99_bool_strict_syshdr.c"
	);

	/* expect+5: error: parameter 1 expects '_Bool', gets passed 'int' [334] */
	take_bool(
# 238 "d_c99_bool_strict_syshdr.c" 3 4
	    (0)
# 240 "d_c99_bool_strict_syshdr.c"
	);

	take_bool(
# 244 "d_c99_bool_strict_syshdr.c" 3 4
	    __lint_false
# 246 "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 (
# 264 "d_c99_bool_strict_syshdr.c" 3 4
	    ((void *)0)
# 266 "d_c99_bool_strict_syshdr.c"
		       )
		/* expect+1: warning: 'return' statement not reached [193] */
		return;

	/* if (EXIT_SUCCESS) */
	if (
# 273 "d_c99_bool_strict_syshdr.c" 3 4
	    0
# 275 "d_c99_bool_strict_syshdr.c"
		       )
		/* expect+1: warning: 'return' statement not reached [193] */
		return;

	/* if (errno) */
	if (
# 282 "d_c99_bool_strict_syshdr.c" 3 4
	    (*errno_location())
# 284 "d_c99_bool_strict_syshdr.c"
		       )
		return;
}


/*
 * For expressions that originate from a system header, the strict type rules
 * are relaxed a bit, to allow for expressions like 'flags & FLAG', even
 * though they are not strictly boolean.
 *
 * This shouldn't apply to function call expressions though since one of the
 * goals of strict bool mode is to normalize all expressions calling 'strcmp'
 * to be of the form 'strcmp(a, b) == 0' instead of '!strcmp(a, b)'.
 */
# 1 "stdio.h" 1 3 4
typedef struct stdio_file {
	int fd;
} FILE;
int ferror(FILE *);
FILE stdio_files[3];
FILE *stdio_stdout;
# 306 "d_c99_bool_strict_syshdr.c" 2
# 1 "string.h" 1 3 4
int strcmp(const char *, const char *);
# 309 "d_c99_bool_strict_syshdr.c" 2

void
controlling_expression(FILE *f, const char *a, const char *b)
{
	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
	if (ferror(f))
		return;
	/* expect+1: error: controlling expression must be bool, not 'int' [333] */
	if (strcmp(a, b))
		return;
	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
	if (!ferror(f))
		return;
	/* expect+1: error: operand of '!' must be bool, not 'int' [330] */
	if (!strcmp(a, b))
		return;

	/*
	 * Before tree.c 1.395 from 2021-11-16, the expression below didn't
	 * produce a warning since the expression 'stdio_files' came from a
	 * system header (via a macro), and this property was passed up to
	 * the expression 'ferror(stdio_files[1])'.
	 *
	 * That was wrong though since the type of a function call expression
	 * only depends on the function itself but not its arguments types.
	 * The old rule had allowed a raw condition 'strcmp(a, b)' without
	 * the comparison '!= 0', as long as one of its arguments came from a
	 * system header.
	 *
	 * Seen in bin/echo/echo.c, function main, call to ferror.
	 */
	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
	if (ferror(
# 343 "d_c99_bool_strict_syshdr.c" 3 4
	    &stdio_files[1]
# 345 "d_c99_bool_strict_syshdr.c"
	    ))
		return;

	/*
	 * Before cgram.y 1.369 from 2021-11-16, at the end of parsing the
	 * name 'stdio_stdout', the parser already looked ahead to the next
	 * token, to see whether it was the '(' of a function call.
	 *
	 * At that point, the parser was no longer in a system header,
	 * therefore 'stdio_stdout' had tn_sys == false, and this information
	 * was pushed down to the whole function call expression (which was
	 * another bug that got fixed in tree.c 1.395 from 2021-11-16).
	 */
	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
	if (ferror(
# 361 "d_c99_bool_strict_syshdr.c" 3 4
	    stdio_stdout
# 363 "d_c99_bool_strict_syshdr.c"
	    ))
		return;

	/*
	 * In this variant of the pattern, there is a token ')' after the
	 * name 'stdio_stdout', which even before tree.c 1.395 from
	 * 2021-11-16 had the effect that at the end of parsing the name, the
	 * parser was still in the system header, thus setting tn_sys (or
	 * rather tn_relaxed at that time) to true.
	 */
	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
	if (ferror(
# 376 "d_c99_bool_strict_syshdr.c" 3 4
	    (stdio_stdout)
# 378 "d_c99_bool_strict_syshdr.c"
	    ))
		return;

	/*
	 * Before cgram.y 1.369 from 2021-11-16, the comment following
	 * 'stdio_stdout' did not prevent the search for '('.  At the point
	 * where build_name called expr_alloc_tnode, the parser was already
	 * in the main file again, thus treating 'stdio_stdout' as not coming
	 * from a system header.
	 *
	 * This has been fixed in tree.c 1.395 from 2021-11-16.  Before that,
	 * an expression had come from a system header if its operands came
	 * from a system header, but that was only close to the truth.  In a
	 * case where both operands come from a system header but the
	 * operator comes from the main translation unit, the main
	 * translation unit still has control over the whole expression.  So
	 * the correct approach is to focus on the operator, not the
	 * operands.  There are a few corner cases where the operator is
	 * invisible (for implicit conversions) or synthetic (for translating
	 * 'arr[index]' to '*(arr + index)', but these are handled as well.
	 */
	/* expect+5: error: controlling expression must be bool, not 'int' [333] */
	if (ferror(
# 402 "d_c99_bool_strict_syshdr.c" 3 4
	    stdio_stdout /* comment */
# 404 "d_c99_bool_strict_syshdr.c"
	    ))
		return;
}
