#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif
#include <sys/cdefs.h>
#if defined(__RCSID)
__RCSID("$NetBSD: func.c,v 1.200 2026/06/16 05:27:57 rillig Exp $");
#endif
#include <stdlib.h>
#include <string.h>
#include "lint1.h"
#include "cgram.h"
sym_t *funcsym;
bool reached = true;
bool warn_about_unreachable;
bool suppress_fallthrough;
static control_statement *cstmt;
int nargusg = -1;
pos_t argsused_pos;
int nvararg = -1;
pos_t vapos;
int printflike_argnum = -1;
int scanflike_argnum = -1;
pos_t printflike_pos;
pos_t scanflike_pos;
bool plibflg;
bool llibflg;
int lwarn = LWARN_ALL;
bool suppress_bitfieldtype;
bool suppress_longlong;
void
begin_control_statement(control_statement_kind kind)
{
control_statement *cs;
cs = xcalloc(1, sizeof(*cs));
cs->c_kind = kind;
cs->c_surrounding = cstmt;
cstmt = cs;
}
void
end_control_statement(control_statement_kind kind)
{
while (cstmt->c_kind != kind)
cstmt = cstmt->c_surrounding;
control_statement *cs = cstmt;
cstmt = cs->c_surrounding;
free(cs->c_case_labels.vals);
free(cs->c_switch_type);
free(cs);
}
static void
set_reached(bool new_reached)
{
debug_step("%s -> %s",
reached ? "reachable" : "unreachable",
new_reached ? "reachable" : "unreachable");
reached = new_reached;
warn_about_unreachable = true;
}
void
check_statement_reachable(const char *stmt_kind)
{
if (!reached && warn_about_unreachable) {
warning(193, stmt_kind);
warn_about_unreachable = false;
}
}
void
begin_function(sym_t *fsym)
{
funcsym = fsym;
for (sym_t *sym = dcs->d_func_proto_syms; sym != NULL;
sym = sym->s_level_next) {
if (sym->s_block_level != -1) {
lint_assert(sym->s_block_level == 1);
inssym(1, sym);
}
}
if (!fsym->s_type->t_proto && fsym->u.s_old_style_params == NULL)
fsym->s_osdef = true;
check_type(fsym);
if (fsym->s_type->t_subt->t_tspec != VOID &&
is_incomplete(fsym->s_type->t_subt)) {
error(67);
}
fsym->s_def = DEF;
if (fsym->s_scl == TYPEDEF) {
fsym->s_scl = EXTERN;
error(8);
}
if (dcs->d_inline)
fsym->s_inline = true;
int n = 1;
for (const sym_t *param = fsym->s_type->u.params;
param != NULL; param = param->s_next) {
if (param->s_scl == ABSTRACT) {
lint_assert(param->s_name == unnamed);
error(59, n);
} else {
lint_assert(param->s_name != unnamed);
}
n++;
}
dcs->d_func_def_pos = fsym->s_def_pos;
sym_t *rdsym = dcs->d_redeclared_symbol;
if (rdsym != NULL) {
bool dowarn = false;
if (!check_redeclaration(fsym, &dowarn)) {
if (dowarn && !fsym->s_osdef) {
if (!allow_trad && !allow_c99)
error(27, fsym->s_name);
else
warning(27, fsym->s_name);
print_previous_declaration(rdsym);
}
copy_usage_info(fsym, rdsym);
if (fsym->s_osdef && rdsym->s_type->t_proto)
fsym->s_def_pos = rdsym->s_def_pos;
complete_type(fsym, rdsym);
if (rdsym->s_inline)
fsym->s_inline = true;
}
symtab_remove_forever(rdsym);
}
if (fsym->s_osdef && !fsym->s_type->t_proto && !fsym->s_type->t_identifier_params) {
if (!allow_trad &&
(allow_c99 || strcmp(fsym->s_name, "main") != 0))
warning(286, fsym->s_name);
}
if (dcs->d_no_type_specifier)
fsym->s_return_type_implicit_int = true;
set_reached(true);
}
static void
check_missing_return_value(void)
{
if (funcsym->s_type->t_subt->t_tspec == VOID)
return;
if (funcsym->s_return_type_implicit_int)
return;
if (allow_c99 && strcmp(funcsym->s_name, "main") == 0)
return;
warning(217, funcsym->s_name);
}
void
end_function(void)
{
if (reached) {
cstmt->c_had_return_noval = true;
check_missing_return_value();
}
if (cstmt->c_had_return_noval && cstmt->c_had_return_value &&
funcsym->s_return_type_implicit_int)
warning(216, funcsym->s_name);
int n = nargusg;
nargusg = -1;
for (const sym_t *param = dcs->d_func_params;
param != NULL && n != 0; param = param->s_next, n--)
check_usage_sym(dcs->d_asm, param);
if (dcs->d_scl == EXTERN && funcsym->s_inline)
outsym(funcsym, funcsym->s_scl, DECL);
else
outfdef(funcsym, &dcs->d_func_def_pos,
cstmt->c_had_return_value, funcsym->s_osdef,
dcs->d_func_params);
while (dcs->d_enclosing != NULL)
dcs = dcs->d_enclosing;
lint_assert(dcs->d_enclosing == NULL);
lint_assert(dcs->d_kind == DLK_EXTERN);
symtab_remove_level(dcs->d_func_proto_syms);
set_reached(true);
funcsym = NULL;
}
void
named_label(sym_t *sym)
{
if (sym->s_set)
error(194, sym->s_name);
else
mark_as_set(sym);
set_reached(true);
}
static void
check_case_label_bitand(const tnode_t *case_expr, const tnode_t *switch_expr)
{
if (switch_expr == NULL ||
switch_expr->tn_op != BITAND ||
switch_expr->u.ops.right->tn_op != CON)
return;
lint_assert(case_expr->tn_op == CON);
uint64_t case_value = (uint64_t)case_expr->u.value.u.integer;
uint64_t mask = (uint64_t)switch_expr->u.ops.right->u.value.u.integer;
if ((case_value & ~mask) != 0)
warning(193, "case");
}
static void
check_case_label_enum(const tnode_t *tn, const control_statement *cs)
{
if (!(tn->tn_type->t_is_enum || cs->c_switch_type->t_is_enum))
return;
if (tn->tn_type->t_is_enum && cs->c_switch_type->t_is_enum &&
tn->tn_type->u.enumer == cs->c_switch_type->u.enumer)
return;
#if 0
warning(130, type_name(cs->c_switch_type), op_name(EQ),
type_name(tn->tn_type));
#endif
}
static bool
check_duplicate_case_label(control_statement *cs, const val_t *nv)
{
case_labels *labels = &cs->c_case_labels;
size_t i = 0, n = labels->len;
while (i < n && labels->vals[i].u.integer != nv->u.integer)
i++;
if (i < n) {
if (is_uinteger(nv->v_tspec))
error(200, (uintmax_t)nv->u.integer);
else
error(199, (intmax_t)nv->u.integer);
return false;
}
if (labels->len >= labels->cap) {
labels->cap = 16 + 2 * labels->cap;
labels->vals = xrealloc(labels->vals,
sizeof(*labels->vals) * labels->cap);
}
labels->vals[labels->len++] = *nv;
return true;
}
static void
check_case_label(tnode_t *tn)
{
control_statement *cs;
for (cs = cstmt; cs != NULL && !cs->c_switch; cs = cs->c_surrounding)
continue;
if (cs == NULL) {
error(195);
return;
}
if (tn == NULL)
return;
if (tn->tn_op != CON) {
error(197);
return;
}
if (!is_integer(tn->tn_type->t_tspec)) {
error(198);
return;
}
check_case_label_bitand(tn, cs->c_switch_expr);
check_case_label_enum(tn, cs);
lint_assert(cs->c_switch_type != NULL);
if (reached && !suppress_fallthrough) {
if (hflag)
warning(220);
}
tspec_t t = tn->tn_type->t_tspec;
if ((t == LONG || t == ULONG || t == LLONG || t == ULLONG)
&& !allow_c90)
warning(203);
val_t *v = integer_constant(tn, true);
val_t nv;
(void)memset(&nv, 0, sizeof(nv));
convert_constant(CASE, 0, cs->c_switch_type, &nv, v);
free(v);
if (check_duplicate_case_label(cs, &nv))
check_getopt_case_label(nv.u.integer);
}
void
case_label(tnode_t *tn)
{
check_case_label(tn);
expr_free_all();
set_reached(true);
}
void
default_label(void)
{
control_statement *cs;
for (cs = cstmt; cs != NULL && !cs->c_switch; cs = cs->c_surrounding)
continue;
if (cs == NULL)
error(201);
else if (cs->c_default)
error(202);
else {
if (reached && !suppress_fallthrough) {
if (hflag)
warning(284);
}
cs->c_default = true;
}
set_reached(true);
}
static tnode_t *
check_controlling_expression(tnode_t *tn, bool is_do_while)
{
tn = cconv(tn);
if (tn != NULL)
tn = promote(NOOP, false, tn);
if (tn != NULL && !is_scalar(tn->tn_type->t_tspec)) {
error(204, type_name(tn->tn_type));
return NULL;
}
if (tn != NULL && Tflag
&& !is_typeok_bool_compares_with_zero(tn, is_do_while)) {
error(333, tn->tn_type->t_is_enum ? expr_type_name(tn)
: tspec_name(tn->tn_type->t_tspec));
}
return tn;
}
void
stmt_if_expr(tnode_t *tn)
{
if (tn != NULL)
tn = check_controlling_expression(tn, false);
if (tn != NULL)
expr(tn, false, true, false, false, "if");
begin_control_statement(CS_IF);
if (tn != NULL && tn->tn_op == CON && !tn->tn_system_dependent) {
set_reached(constant_is_nonzero(tn));
cstmt->c_always_then = reached;
}
}
void
stmt_if_then_stmt(void)
{
cstmt->c_reached_end_of_then = reached;
set_reached(!cstmt->c_always_then);
}
void
stmt_if_else_stmt(bool els)
{
if (cstmt->c_reached_end_of_then)
set_reached(true);
else if (cstmt->c_always_then)
set_reached(false);
else if (!els)
set_reached(true);
end_control_statement(CS_IF);
}
void
stmt_switch_expr(tnode_t *tn)
{
if (tn != NULL)
tn = cconv(tn);
if (tn != NULL)
tn = promote(NOOP, false, tn);
if (tn != NULL && !is_integer(tn->tn_type->t_tspec)) {
error(205, type_name(tn->tn_type));
tn = NULL;
}
if (tn != NULL && !allow_c90) {
tspec_t t = tn->tn_type->t_tspec;
if (t == LONG || t == ULONG || t == LLONG || t == ULLONG)
warning(271);
}
type_t *tp = xcalloc(1, sizeof(*tp));
if (tn != NULL) {
tp->t_tspec = tn->tn_type->t_tspec;
if ((tp->t_is_enum = tn->tn_type->t_is_enum) != false)
tp->u.enumer = tn->tn_type->u.enumer;
} else {
tp->t_tspec = INT;
}
(void)expr_save_memory();
check_getopt_begin_switch();
expr(tn, true, false, false, false, "switch");
begin_control_statement(CS_SWITCH);
cstmt->c_switch = true;
cstmt->c_switch_type = tp;
cstmt->c_switch_expr = tn;
set_reached(false);
suppress_fallthrough = true;
}
void
stmt_switch_expr_stmt(void)
{
int nenum = 0, nclab = 0;
sym_t *esym;
lint_assert(cstmt->c_switch_type != NULL);
if (cstmt->c_switch_type->t_is_enum) {
nenum = nclab = 0;
lint_assert(cstmt->c_switch_type->u.enumer != NULL);
for (esym = cstmt->c_switch_type->u.enumer->en_first_enumerator;
esym != NULL; esym = esym->s_next) {
nenum++;
}
nclab = (int)cstmt->c_case_labels.len;
if (hflag && eflag && nclab < nenum && !cstmt->c_default)
warning(206);
}
check_getopt_end_switch();
if (cstmt->c_break) {
set_reached(true);
} else if (cstmt->c_default ||
(hflag && cstmt->c_switch_type->t_is_enum &&
nenum == nclab)) {
} else {
set_reached(true);
}
end_control_statement(CS_SWITCH);
}
void
stmt_while_expr(tnode_t *tn)
{
if (!reached) {
set_reached(true);
}
if (tn != NULL)
tn = check_controlling_expression(tn, false);
begin_control_statement(CS_WHILE);
cstmt->c_loop = true;
cstmt->c_maybe_endless = is_nonzero(tn);
bool body_reached = !is_zero(tn);
check_getopt_begin_while(tn);
expr(tn, false, true, true, false, "while");
set_reached(body_reached);
}
void
stmt_while_expr_stmt(void)
{
set_reached(!cstmt->c_maybe_endless || cstmt->c_break);
check_getopt_end_while();
end_control_statement(CS_WHILE);
}
void
stmt_do(void)
{
if (!reached)
set_reached(true);
begin_control_statement(CS_DO_WHILE);
cstmt->c_loop = true;
}
void
stmt_do_while_expr(tnode_t *tn)
{
if (cstmt->c_continue)
set_reached(true);
if (tn != NULL)
tn = check_controlling_expression(tn, true);
if (tn != NULL && tn->tn_op == CON) {
cstmt->c_maybe_endless = constant_is_nonzero(tn);
if (!cstmt->c_maybe_endless && cstmt->c_continue)
error(323);
}
expr(tn, false, true, true, true, "do-while");
if (cstmt->c_maybe_endless)
set_reached(false);
if (cstmt->c_break)
set_reached(true);
end_control_statement(CS_DO_WHILE);
}
void
stmt_for_exprs(tnode_t *tn1, tnode_t *tn2, tnode_t *tn3)
{
if (tn1 != NULL && !reached)
set_reached(true);
begin_control_statement(CS_FOR);
cstmt->c_loop = true;
cstmt->c_for_expr3_mem = expr_save_memory();
cstmt->c_for_expr3 = tn3;
cstmt->c_for_expr3_pos = curr_pos;
cstmt->c_for_expr3_csrc_pos = csrc_pos;
if (tn1 != NULL)
expr(tn1, false, false, true, false, "for");
if (tn2 != NULL)
tn2 = check_controlling_expression(tn2, false);
if (tn2 != NULL)
expr(tn2, false, true, true, false, "for");
cstmt->c_maybe_endless = tn2 == NULL || is_nonzero(tn2);
set_reached(!is_zero(tn2));
}
void
stmt_for_exprs_stmt(void)
{
if (cstmt->c_continue)
set_reached(true);
expr_restore_memory(cstmt->c_for_expr3_mem);
tnode_t *tn3 = cstmt->c_for_expr3;
pos_t saved_curr_pos = curr_pos;
pos_t saved_csrc_pos = csrc_pos;
curr_pos = cstmt->c_for_expr3_pos;
csrc_pos = cstmt->c_for_expr3_csrc_pos;
if (!reached && warn_about_unreachable) {
warning(223);
set_reached(true);
}
if (tn3 != NULL)
expr(tn3, false, false, true, false, "for continuation");
else
expr_free_all();
curr_pos = saved_curr_pos;
csrc_pos = saved_csrc_pos;
set_reached(cstmt->c_break || !cstmt->c_maybe_endless);
end_control_statement(CS_FOR);
}
void
stmt_goto(sym_t *lab)
{
mark_as_used(lab, false, false);
check_statement_reachable("goto");
set_reached(false);
}
void
stmt_break(void)
{
control_statement *cs = cstmt;
while (cs != NULL && !cs->c_loop && !cs->c_switch)
cs = cs->c_surrounding;
if (cs == NULL)
error(208);
else if (reached)
cs->c_break = true;
if (bflag)
check_statement_reachable("break");
set_reached(false);
}
void
stmt_continue(void)
{
control_statement *cs;
for (cs = cstmt; cs != NULL && !cs->c_loop; cs = cs->c_surrounding)
continue;
if (cs == NULL)
error(209);
else
cs->c_continue = true;
check_statement_reachable("continue");
set_reached(false);
}
void
stmt_call_noreturn(void)
{
set_reached(false);
}
static bool
is_parenthesized(const tnode_t *tn)
{
while (!tn->tn_parenthesized && tn->tn_op == COMMA)
tn = tn->u.ops.right;
return tn->tn_parenthesized && !tn->tn_sys;
}
static void
check_return_value(bool sys, tnode_t *tn)
{
if (any_query_enabled && is_parenthesized(tn))
query_message(9);
tnode_t *ln = expr_zero_alloc(sizeof(*ln), "tnode");
ln->tn_op = NAME;
ln->tn_type = expr_unqualified_type(funcsym->s_type->t_subt);
ln->tn_lvalue = true;
ln->u.sym = funcsym;
tnode_t *retn = build_binary(ln, RETURN, sys, tn);
if (retn != NULL) {
const tnode_t *rn = retn->u.ops.right;
while (rn->tn_op == CVT || rn->tn_op == PLUS)
rn = rn->u.ops.left;
if (rn->tn_op == ADDR && rn->u.ops.left->tn_op == NAME &&
rn->u.ops.left->u.sym->s_scl == AUTO)
warning(302, funcsym->s_name);
}
expr(retn, true, false, true, false, "return");
}
void
stmt_return(bool sys, tnode_t *tn)
{
control_statement *cs = cstmt;
if (cs == NULL) {
error(249, "return outside function");
return;
}
for (; cs->c_surrounding != NULL; cs = cs->c_surrounding)
continue;
if (tn != NULL)
cs->c_had_return_value = true;
else
cs->c_had_return_noval = true;
if (tn != NULL && funcsym->s_type->t_subt->t_tspec == VOID) {
error(213, funcsym->s_name);
expr_free_all();
tn = NULL;
}
if (tn == NULL && funcsym->s_type->t_subt->t_tspec != VOID
&& !funcsym->s_return_type_implicit_int) {
if (allow_c99)
error(214, funcsym->s_name);
else
warning(214, funcsym->s_name);
}
if (tn != NULL)
check_return_value(sys, tn);
else
check_statement_reachable("return");
set_reached(false);
}
void
global_clean_up_decl(bool silent)
{
if (nargusg != -1) {
if (!silent)
warning_at(282, &argsused_pos, "ARGSUSED");
nargusg = -1;
}
if (nvararg != -1) {
if (!silent)
warning_at(282, &vapos, "VARARGS");
nvararg = -1;
}
if (printflike_argnum != -1) {
if (!silent)
warning_at(282, &printflike_pos, "PRINTFLIKE");
printflike_argnum = -1;
}
if (scanflike_argnum != -1) {
if (!silent)
warning_at(282, &scanflike_pos, "SCANFLIKE");
scanflike_argnum = -1;
}
dcs->d_asm = false;
in_gcc_attribute = false;
while (dcs->d_enclosing != NULL)
end_declaration_level();
}
static void
argsused(int n)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "ARGSUSED");
return;
}
if (nargusg != -1)
warning(281, "ARGSUSED");
nargusg = n != -1 ? n : 0;
argsused_pos = curr_pos;
}
static void
varargs(int n)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "VARARGS");
return;
}
if (nvararg != -1)
warning(281, "VARARGS");
nvararg = n != -1 ? n : 0;
vapos = curr_pos;
}
static void
printflike(int n)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "PRINTFLIKE");
return;
}
if (printflike_argnum != -1)
warning(281, "PRINTFLIKE");
printflike_argnum = n != -1 ? n : 0;
printflike_pos = curr_pos;
}
static void
scanflike(int n)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "SCANFLIKE");
return;
}
if (scanflike_argnum != -1)
warning(281, "SCANFLIKE");
scanflike_argnum = n != -1 ? n : 0;
scanflike_pos = curr_pos;
}
static void
lintlib(void)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "LINTLIBRARY");
return;
}
llibflg = true;
vflag = true;
}
static void
protolib(int n)
{
if (dcs->d_kind != DLK_EXTERN) {
warning(280, "PROTOLIB");
return;
}
plibflg = n != 0;
}
void
handle_lint_comment(lint_comment comment, int arg)
{
switch (comment) {
case LC_ARGSUSED: argsused(arg); break;
case LC_BITFIELDTYPE: suppress_bitfieldtype = true; break;
case LC_FALLTHROUGH: suppress_fallthrough = true; break;
case LC_LINTLIBRARY: lintlib(); break;
case LC_LINTED: debug_step("set lwarn %d", arg);
lwarn = arg; break;
case LC_LONGLONG: suppress_longlong = true; break;
case LC_NOTREACHED: set_reached(false);
warn_about_unreachable = false; break;
case LC_PRINTFLIKE: printflike(arg); break;
case LC_PROTOLIB: protolib(arg); break;
case LC_SCANFLIKE: scanflike(arg); break;
case LC_VARARGS: varargs(arg); break;
}
}