#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/cdefs.h>
#if defined(__RCSID)
__RCSID("$NetBSD: err.c,v 1.284 2026/04/20 22:17:08 rillig Exp $");
#endif
#include <limits.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include "lint1.h"
bool seen_error;
bool seen_warning;
int sytxerr;
static const char *const msgs[] = {
"empty declaration",
"old-style declaration; add 'int'",
"empty declaration",
"'%s' declared in parameter declaration list",
"invalid type combination",
"modifying typedef with '%s'; only qualifiers allowed",
"use 'double' instead of 'long float'",
"only one storage class allowed",
"invalid storage class",
"only 'register' is valid as storage class in parameter",
"duplicate '%s'",
"bit-field initializer out of range",
"compiler takes size of function",
"incomplete enum type '%s'",
"",
"function returns invalid type '%s'",
"array of function is invalid",
"null dimension",
"invalid use of 'void'",
"void type for '%s'",
"negative array dimension (%d)",
"redeclaration of formal parameter '%s'",
"incomplete or misplaced function definition",
"undefined label '%s'",
"cannot initialize function '%s'",
"cannot initialize typedef '%s'",
"cannot initialize extern declaration '%s'",
"redeclaration of '%s'",
"redefinition of '%s'",
"'%s' was previously declared extern, becomes static",
"redeclaration of '%s'; C90 or later require static",
"'%s' has incomplete type '%s'",
"type of parameter '%s' defaults to 'int'",
"duplicate member name '%s'",
"nonportable bit-field type '%s'",
"invalid bit-field type '%s'",
"invalid bit-field size: %d",
"zero size bit-field",
"function invalid in structure or union",
"zero-sized array '%s' in struct requires C99 or later",
"",
"bit-field in union is very unusual",
"forward reference to enum type",
"redefinition of '%s' hides earlier one",
"declaration of '%s %s' introduces new type in C90 or later",
"base type is really '%s %s'",
"%s tag '%s' redeclared as %s",
"zero sized %s is a C99 feature",
"enumeration value '%s' overflows",
"anonymous struct/union members is a C11 feature",
"parameter '%s' has function type, should be pointer",
"parameter mismatch: %d declared, %d defined",
"cannot initialize parameter '%s'",
"declared parameter '%s' is missing",
"trailing ',' in enum declaration requires C99 or later",
"integral constant expression expected",
"constant %s too large for 'int'",
"enumeration constant '%s' hides parameter",
"type of '%s' does not match prototype",
"formal parameter #%d lacks name",
"void must be sole parameter",
"void parameter '%s' cannot have name",
"function prototype parameters must have types",
"prototype does not match old-style definition",
"()-less function definition",
"'%s' has no named members",
"",
"cannot return incomplete type",
"typedef already qualified with '%s'",
"inappropriate qualifiers with 'void'",
"",
"too many characters in character constant",
"typedef declares no type name",
"empty character constant",
"no hex digits follow \\x",
"overflow in hex escape",
"character escape does not fit in character",
"bad octal digit '%c'",
"",
"dubious escape \\%c",
"dubious escape \\%o",
"\\a requires C90 or later",
"\\x requires C90 or later",
"storage class after type is obsolescent",
"C90 to C17 require formal parameter before '...'",
"dubious tag declaration '%s %s'",
"automatic '%s' hides external declaration with type '%s'",
"static '%s' hides external declaration with type '%s'",
"typedef '%s' hides external declaration with type '%s'",
"typedef '%s' redeclared",
"inconsistent redeclaration of extern '%s'",
"declaration of '%s' hides parameter",
"inconsistent redeclaration of static '%s'",
"dubious static function '%s' at block level",
"function '%s' has invalid storage class",
"declaration of '%s' hides earlier one",
"cannot dereference non-pointer type '%s'",
"suffix 'U' requires C90 or later",
"suffixes 'F' or 'L' require C90 or later",
"'%s' undefined",
"unary '+' requires C90 or later",
"type '%s' does not have member '%s'",
"invalid use of member '%s'",
"left operand of '.' must be struct or union, not '%s'",
"left operand of '->' must be pointer to struct or union, not '%s'",
"non-unique member requires struct/union %s",
"left operand of '->' must be pointer",
"operands of '%s' have incompatible types '%s' and '%s'",
"operand of '%s' has invalid type '%s'",
"void type invalid in expression",
"pointer to function is not allowed here",
"unacceptable operand of '%s'",
"cannot take address of bit-field",
"cannot take address of register '%s'",
"%soperand of '%s' must be lvalue",
"%soperand of '%s' must be modifiable lvalue",
"invalid pointer subtraction",
"bitwise '%s' on signed '%s' possibly nonportable",
"'%s' %s '%s' differs between traditional C and C90",
"conversion of '%s' to '%s' is out of range",
"bitwise '%s' on signed '%s' nonportable",
"negative shift",
"shift amount %llu is greater than bit-size %llu of '%s'",
"invalid combination of %s '%s' and %s '%s', op '%s'",
"invalid combination of '%s' and '%s', op '%s'",
"pointers to functions can only be compared for equality",
"incompatible types '%s' and '%s' in conditional",
"",
"operator '%s' discards '%s' from '%s'",
"expression has null effect",
"enum type mismatch: '%s' '%s' '%s'",
"conversion to '%s' may sign-extend incorrectly",
"conversion from '%s' to '%s' may lose accuracy",
"conversion of pointer to '%s' loses bits",
"conversion of pointer to '%s' may lose bits",
"converting '%s' to '%s' increases alignment from %u to %u",
"cannot do pointer arithmetic on operand of unknown size",
"",
"unknown operand size, op '%s'",
"division by 0",
"modulus by 0",
"'%s' overflows '%s'",
"operator '%s' produces floating point overflow",
"cannot take size/alignment of incomplete type",
"cannot take size/alignment of function type '%s'",
"cannot take size/alignment of bit-field",
"cannot take size/alignment of void",
"invalid cast from '%s' to '%s'",
"improper cast of void expression",
"cannot call '%s', must be a function",
"argument mismatch: %d %s passed, %d expected",
"void expressions may not be arguments, arg #%d",
"argument cannot have unknown size, arg #%d",
"converting '%s' to incompatible '%s' for argument %d",
"invalid combination of %s '%s' and %s '%s', arg #%d",
"passing '%s' to incompatible '%s', arg #%d",
"function expects '%s', passing '%s' for arg #%d",
"C90 treats constant as unsigned",
"'%s' may be used before set",
"assignment in conditional context",
"operator '==' found where '=' was expected",
"",
"operator '%s' compares '%s' with '%s'",
"a cast does not yield an lvalue",
"assignment of negative constant %lld to unsigned type '%s'",
"constant truncated by assignment",
"precision lost in bit-field assignment",
"array subscript %jd cannot be negative",
"array subscript %ju cannot be > %d",
"possible precedence confusion between '%s' and '%s'",
"first operand of '?' must have scalar type",
"cannot assign to '%s' from '%s'",
"too many struct/union initializers",
"too many array initializers, expected %d",
"too many initializers for '%s'",
"initialization of incomplete type '%s'",
"",
"non-constant initializer",
"initializer does not fit",
"cannot initialize struct/union with no named member",
"bit-field initializer does not fit",
"{}-enclosed or constant initializer of type '%s' required",
"'%s' discards '%s' from '%s'",
"invalid combination of %s '%s' and %s '%s' for '%s'",
"invalid combination of '%s' and '%s'",
"cannot initialize '%s' from '%s'",
"bit-field initializer must be an integer in traditional C",
"string literal too long (%ju) for target array (%ju)",
"automatic aggregate initialization requires C90 or later",
"",
"empty array declaration for '%s'",
"'%s' set but not used in function '%s'",
"'%s' unused in function '%s'",
"'%s' statement not reached",
"label '%s' redefined",
"case not in switch",
"case label is converted from '%s' to '%s'",
"non-constant case expression",
"non-integral case expression",
"duplicate case '%jd' in switch",
"duplicate case '%ju' in switch",
"default outside switch",
"duplicate default in switch",
"case label must be of type 'int' in traditional C",
"controlling expressions must have scalar type, not '%s'",
"switch expression must have integral type, not '%s'",
"enumeration value(s) not handled in switch",
"",
"break outside loop or switch",
"continue outside loop",
"enum type mismatch between '%s' and '%s' in initialization",
"function has return type '%s' but returns '%s'",
"cannot return incomplete type",
"void function '%s' cannot return value",
"function '%s' expects to return value",
"function '%s' implicitly declared to return int",
"function '%s' has 'return expr' and 'return'",
"function '%s' falls off bottom without returning value",
"C90 treats constant as unsigned, op '%s'",
"concatenated strings require C90 or later",
"fallthrough on case statement",
"initialization of unsigned type '%s' with negative constant %lld",
"conversion of negative constant %lld to unsigned type '%s'",
"end-of-loop code not reached",
"cannot recover from previous errors",
"static function '%s' called but not defined",
"static variable '%s' unused",
"const object '%s' should have initializer",
"returning the type qualifier '%s' has no effect",
"converting '%s' to '%s' is questionable",
"nonportable character comparison '%s'",
"parameter '%s' unused in function '%s'",
"label '%s' unused in function '%s'",
"struct '%s' never defined",
"union '%s' never defined",
"enum '%s' never defined",
"static function '%s' unused",
"redeclaration of formal parameter '%s'",
"initialization of union requires C90 or later",
"",
"",
"dubious operation '%s' on enum",
"combination of '%s' and '%s', op '%s'",
"operator '%s' assumes that '%s' is ordered",
"invalid structure pointer combination",
"incompatible structure pointers: '%s' '%s' '%s'",
"dubious conversion of enum to '%s'",
"pointer cast from '%s' to unrelated '%s'",
"floating-point constant out of range",
"syntax error '%s'",
"unknown character \\%o",
"malformed integer constant",
"integer constant out of range",
"unterminated character constant",
"newline in string or char constant",
"undefined or invalid '#' directive",
"unterminated comment",
"extra characters in lint comment",
"unterminated string constant",
"argument %d is converted from '%s' to '%s' due to prototype",
"previous declaration of '%s'",
"previous definition of '%s'",
"\\\" inside a character constant requires C90 or later",
"\\? requires C90 or later",
"\\v requires C90 or later",
"%s does not support 'long long'",
"'long double' requires C90 or later",
"shift amount %u equals bit-size of '%s'",
"variable '%s' declared inline",
"parameter '%s' declared inline",
"function prototypes require C90 or later",
"switch expression must be of type 'int' in traditional C",
"empty translation unit",
"bit-field type '%s' invalid in C90 or later",
"C90 or later forbid comparison of %s with %s",
"cast discards 'const' from type '%s'",
"'__%s__' is invalid for type '%s'",
"initialization of '%s' with '%s'",
"combination of '%s' and '%s', arg #%d",
"combination of '%s' and '%s' in return",
"comment /* %s */ must be outside function",
"duplicate comment /* %s */",
"comment /* %s */ must precede function definition",
"parameter number mismatch in comment /* %s */",
"fallthrough on default statement",
"prototype declaration",
"function definition for '%s' is not a prototype",
"function declaration for '%s' is not a prototype",
"dubious use of /* VARARGS */ with /* %s */",
"/* PRINTFLIKE */ and /* SCANFLIKE */ cannot be combined",
"static function '%s' declared but not defined",
"invalid multibyte character",
"cannot concatenate wide and regular string literals",
"parameter %d must be 'char *' for PRINTFLIKE/SCANFLIKE",
"multi-character character constant",
"conversion of '%s' to '%s' is out of range, arg #%d",
"conversion of negative constant %lld to unsigned type '%s', arg #%d",
"conversion to '%s' may sign-extend incorrectly, arg #%d",
"conversion from '%s' to '%s' may lose accuracy, arg #%d",
"prototype does not match old-style definition, arg #%d",
"old-style definition",
"array of incomplete type",
"'%s' returns pointer to automatic object",
"conversion of %s to %s requires a cast",
"conversion of %s to %s requires a cast, arg #%d",
"conversion of %s to %s requires a cast, op %s",
"constant %s truncated by conversion, op '%s'",
"static variable '%s' set but not used",
"invalid type for _Complex",
"'%s' converts '%s' with its most significant bit being set to '%s'",
"symbol renaming can't be used on function parameters",
"symbol renaming can't be used on automatic variables",
"%s does not support '//' comments",
"struct or union member name in initializer is a C99 feature",
"",
"GCC style struct or union member name in initializer",
"__FUNCTION__/__PRETTY_FUNCTION__ is a GCC extension",
"__func__ is a C99 feature",
"variable array dimension is a C99/GCC extension",
"compound literals are a C99/GCC extension",
"'({ ... })' is a GCC extension",
"array initializer with designators is a C99 feature",
"zero sized array requires C99 or later",
"continue in 'do ... while (0)' loop",
"suggest cast from '%s' to '%s' on op '%s' to avoid overflow",
"variable declaration in for loop",
"attribute '%s' ignored for '%s'",
"declarations after statements is a C99 feature",
"union cast is a GCC extension",
"type '%s' is not a member of '%s'",
"operand of '%s' must be bool, not '%s'",
"left operand of '%s' must be bool, not '%s'",
"right operand of '%s' must be bool, not '%s'",
"controlling expression must be bool, not '%s'",
"parameter %d expects '%s', gets passed '%s'",
"operand of '%s' must not be bool",
"left operand of '%s' must not be bool",
"right operand of '%s' must not be bool",
"option '%c' should be handled in the switch",
"option '%c' should be listed in the options string",
"initialization with '[a...b]' is a GCC extension",
"argument to '%s' must be 'unsigned char' or EOF, not '%s'",
"argument to '%s' must be cast to 'unsigned char', not to '%s'",
"static array size requires C11 or later",
"bit-field of type plain 'int' has implementation-defined signedness",
"generic selection requires C11 or later",
"call to '%s' effectively discards 'const' from argument",
"redeclaration of '%s' with type '%s', expected '%s'",
"maximum value %d for '%s' of type '%s' does not match maximum array index %d",
"non type argument to alignof is a GCC extension",
"'_Atomic' requires C11 or later",
"missing%s header declaration for '%s'",
"nested 'extern' declaration of '%s'",
"empty initializer braces require C23 or later",
"'_Static_assert' requires C11 or later",
"'_Static_assert' without message requires C23 or later",
"short octal escape '%.*s' followed by digit '%c'",
"hex escape '%.*s' mixes uppercase and lowercase digits",
"hex escape '%.*s' has more than 2 digits",
"missing new-style '\\177' or old-style number base",
"missing new-style number base after '\\177'",
"number base '%.*s' is %ju, must be 8, 10 or 16",
"conversion '%.*s' should not be escaped",
"escaped character '%.*s' in description of conversion '%.*s'",
"missing bit position after '%.*s'",
"missing field width after '%.*s'",
"missing '\\0' at the end of '%.*s'",
"empty description in '%.*s'",
"missing comparison value after conversion '%.*s'",
"bit position '%.*s' in '%.*s' should be escaped as octal or hex",
"field width '%.*s' in '%.*s' should be escaped as octal or hex",
"bit position '%.*s' (%ju) in '%.*s' out of range %u..%u",
"field width '%.*s' (%ju) in '%.*s' out of range 0..64",
"bit field end %ju in '%.*s' out of range 0..64",
"unknown conversion '%.*s', must be one of 'bfF=:*'",
"comparison value '%.*s' (%ju) exceeds maximum field value %ju",
"'%.*s' overlaps earlier '%.*s' on bit %u",
"redundant '\\0' at the end of the format",
"conversion '%.*s' is unreachable by input value",
"comparing integer '%s' to floating point constant %Lg",
"lossy conversion of %Lg (%La) to '%s', arg #%d",
"lossy conversion of %Lg (%La) to '%s'",
"constant assignment of type '%s' in operand of '%s' always evaluates to '%s'",
"passing '%s' as argument %d to '%s' discards '%s'",
"function definition for '%s' with identifier list is obsolete in C23",
"do-while macro '%.*s' ends with semicolon",
"conversion '%.*s' from '%.*s' does not mix with '%c'",
};
static bool is_suppressed[sizeof(msgs) / sizeof(msgs[0])];
static struct include_level {
const char *filename;
int lineno;
struct include_level *by;
} *includes;
void
suppress_messages(const char *p)
{
char *end;
for (; ch_isdigit(*p); p = end + 1) {
unsigned long id = strtoul(p, &end, 10);
if ((*end != '\0' && *end != ',') ||
id >= sizeof(msgs) / sizeof(msgs[0]) ||
msgs[id][0] == '\0')
break;
is_suppressed[id] = true;
if (*end == '\0')
return;
}
errx(1, "invalid message ID '%.*s'", (int)strcspn(p, ","), p);
}
void
update_location(const char *filename, int lineno, bool is_begin, bool is_end)
{
struct include_level *top;
top = includes;
if (is_begin && top != NULL)
top->lineno = curr_pos.p_line;
if (top == NULL || is_begin) {
top = xmalloc(sizeof(*top));
top->filename = filename;
top->lineno = lineno;
top->by = includes;
includes = top;
} else {
if (is_end) {
includes = top->by;
free(top);
top = includes;
}
if (top != NULL) {
top->filename = filename;
top->lineno = lineno;
}
}
}
static void
print_stack_trace(void)
{
const struct include_level *top;
if ((top = includes) == NULL)
return;
for (top = top->by; top != NULL; top = top->by)
printf("\tincluded from %s(%d)\n", top->filename, top->lineno);
}
static const char *
lbasename(const char *path)
{
if (Fflag)
return path;
const char *base = path;
for (const char *p = path; *p != '\0'; p++)
if (*p == '/')
base = p + 1;
return base;
}
static FILE *
output_channel(void)
{
return yflag ? stderr : stdout;
}
static void
verror_at(int msgid, const pos_t *pos, va_list ap)
{
if (is_suppressed[msgid])
return;
FILE *out = output_channel();
(void)fprintf(out, "%s(%d): error: ",
lbasename(pos->p_file), pos->p_line);
(void)vfprintf(out, msgs[msgid], ap);
(void)fprintf(out, " [%d]\n", msgid);
seen_error = true;
print_stack_trace();
}
static void
vwarning_at(int msgid, const pos_t *pos, va_list ap)
{
if (is_suppressed[msgid])
return;
debug_step("%s: lwarn=%d msgid=%d", __func__, lwarn, msgid);
if (lwarn == LWARN_NONE || lwarn == msgid)
return;
FILE *out = output_channel();
(void)fprintf(out, "%s(%d): warning: ",
lbasename(pos->p_file), pos->p_line);
(void)vfprintf(out, msgs[msgid], ap);
(void)fprintf(out, " [%d]\n", msgid);
seen_warning = true;
print_stack_trace();
}
static void
vmessage_at(int msgid, const pos_t *pos, va_list ap)
{
if (is_suppressed[msgid])
return;
FILE *out = output_channel();
(void)fprintf(out, "%s(%d): ",
lbasename(pos->p_file), pos->p_line);
(void)vfprintf(out, msgs[msgid], ap);
(void)fprintf(out, " [%d]\n", msgid);
print_stack_trace();
}
void
(error_at)(int msgid, const pos_t *pos, ...)
{
va_list ap;
va_start(ap, pos);
verror_at(msgid, pos, ap);
va_end(ap);
}
void
(error)(int msgid, ...)
{
va_list ap;
va_start(ap, msgid);
verror_at(msgid, &curr_pos, ap);
va_end(ap);
}
void
assert_failed(const char *file, int line, const char *func, const char *cond)
{
#if LINT_FUZZING
if (sytxerr > 0)
norecover();
#endif
(void)fflush(stdout);
(void)fprintf(stderr,
"lint: assertion \"%s\" failed in %s at %s:%d near %s:%d\n",
cond, func, file, line,
lbasename(curr_pos.p_file), curr_pos.p_line);
print_stack_trace();
(void)fflush(stdout);
abort();
}
void
(warning_at)(int msgid, const pos_t *pos, ...)
{
va_list ap;
va_start(ap, pos);
vwarning_at(msgid, pos, ap);
va_end(ap);
}
void
(warning)(int msgid, ...)
{
va_list ap;
va_start(ap, msgid);
vwarning_at(msgid, &curr_pos, ap);
va_end(ap);
}
void
(message_at)(int msgid, const pos_t *pos, ...)
{
va_list ap;
va_start(ap, pos);
vmessage_at(msgid, pos, ap);
va_end(ap);
}
void
(c99ism)(int msgid, ...)
{
va_list ap;
if (allow_c99)
return;
va_start(ap, msgid);
int severity = (!allow_gcc ? 1 : 0) + (!allow_trad ? 1 : 0);
if (severity == 2)
verror_at(msgid, &curr_pos, ap);
if (severity == 1)
vwarning_at(msgid, &curr_pos, ap);
va_end(ap);
}
void
(c11ism)(int msgid, ...)
{
va_list ap;
if (allow_c11 || allow_gcc)
return;
va_start(ap, msgid);
verror_at(msgid, &curr_pos, ap);
va_end(ap);
}
void
(c23ism)(int msgid, ...)
{
va_list ap;
if (allow_c23)
return;
va_start(ap, msgid);
verror_at(msgid, &curr_pos, ap);
va_end(ap);
}
bool
(gnuism)(int msgid, ...)
{
va_list ap;
int severity = (!allow_gcc ? 1 : 0) +
(!allow_trad && !allow_c99 ? 1 : 0);
va_start(ap, msgid);
if (severity == 2)
verror_at(msgid, &curr_pos, ap);
if (severity == 1)
vwarning_at(msgid, &curr_pos, ap);
va_end(ap);
return severity > 0;
}
static const char *queries[] = {
"",
"implicit conversion from floating point '%s' to integer '%s'",
"cast from floating point '%s' to integer '%s'",
"implicit conversion changes sign from '%s' to '%s'",
"usual arithmetic conversion for '%s' from '%s' to '%s'",
"pointer addition has integer on the left-hand side",
"no-op cast from '%s' to '%s'",
"redundant cast from '%s' to '%s' before assignment",
"octal number '%.*s'",
"parenthesized return value",
"chained assignment with '%s' and '%s'",
"static variable '%s' in function",
"comma operator with types '%s' and '%s'",
"redundant 'extern' in function declaration of '%s'",
"comparison '%s' of 'char' with plain integer %d",
"implicit conversion from integer 0 to pointer '%s'",
"'%s' was declared 'static', now non-'static'",
"invisible character U+%04X in %s",
"const automatic variable '%s'",
"implicit conversion from integer '%s' to floating point '%s'",
"implicit narrowing conversion from void pointer to '%s'",
"typedef '%s' of struct type '%s'",
"typedef '%s' of union type '%s'",
"typedef '%s' of pointer to struct type '%s'",
"typedef '%s' of pointer to union type '%s'",
"negation of signed '%s'",
};
bool any_query_enabled;
bool is_query_enabled[sizeof(queries) / sizeof(queries[0])];
void
(query_message)(int query_id, ...)
{
if (!is_query_enabled[query_id])
return;
va_list ap;
FILE *out = output_channel();
(void)fprintf(out, "%s(%d): ",
lbasename(curr_pos.p_file), curr_pos.p_line);
va_start(ap, query_id);
(void)vfprintf(out, queries[query_id], ap);
va_end(ap);
(void)fprintf(out, " [Q%d]\n", query_id);
print_stack_trace();
}
void
enable_queries(const char *p)
{
char *end;
for (; ch_isdigit(*p); p = end + 1) {
unsigned long id = strtoul(p, &end, 10);
if ((*end != '\0' && *end != ',') ||
id >= sizeof(queries) / sizeof(queries[0]) ||
queries[id][0] == '\0')
break;
any_query_enabled = true;
is_query_enabled[id] = true;
if (*end == '\0')
return;
}
errx(1, "invalid query ID '%.*s'", (int)strcspn(p, ","), p);
}