root/usr.bin/xlint/lint1/func.c
/*      $NetBSD: func.c,v 1.200 2026/06/16 05:27:57 rillig Exp $        */

/*
 * Copyright (c) 1994, 1995 Jochen Pohl
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Jochen Pohl for
 *      The NetBSD Project.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#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"

/*
 * Contains a pointer to the symbol table entry of the current function
 * definition.
 */
sym_t *funcsym;

/* Is set as long as a statement can be reached. Must be set at level 0. */
bool reached = true;

/*
 * Is true by default, can be cleared by NOTREACHED.
 * Is reset to true whenever 'reached' changes.
 */
bool warn_about_unreachable;

/*
 * In conjunction with 'reached', controls printing of "fallthrough on ..."
 * warnings.
 * Reset by each statement and set by FALLTHROUGH, stmt_switch_expr and
 * case_label.
 *
 * The control statements 'if', 'for', 'while' and 'switch' do not reset
 * suppress_fallthrough because this must be done by the controlled statement.
 * At least for 'if', this is important because ** FALLTHROUGH ** after "if
 * (expr) statement" is evaluated before the following token, which causes
 * reduction of above. This means that ** FALLTHROUGH ** after "if ..." would
 * always be ignored.
 */
bool suppress_fallthrough;

/* The innermost control statement */
static control_statement *cstmt;

/*
 * Number of parameters which will be checked for usage in following
 * function definition. -1 stands for all parameters.
 *
 * The position of the last ARGSUSED comment is stored in argsused_pos.
 */
int nargusg = -1;
pos_t argsused_pos;

/*
 * Number of parameters of the following function definition whose types
 * shall be checked by lint2. -1 stands for all parameters.
 *
 * The position of the last VARARGS comment is stored in vapos.
 */
int nvararg = -1;
pos_t vapos;

/*
 * Both printflike_argnum and scanflike_argnum contain the 1-based number
 * of the string parameter which shall be used to check the types of remaining
 * arguments (for PRINTFLIKE and SCANFLIKE).
 *
 * printflike_pos and scanflike_pos are the positions of the last PRINTFLIKE
 * or SCANFLIKE comment.
 */
int printflike_argnum = -1;
int scanflike_argnum = -1;
pos_t printflike_pos;
pos_t scanflike_pos;

/*
 * If both plibflg and llibflg are set, prototypes are written as function
 * definitions to the output file.
 */
bool plibflg;

/*
 * Whether a lint library shall be created. The effect of this flag is that
 * all defined symbols are treated as used.
 * (The LINTLIBRARY comment also resets vflag.)
 */
bool llibflg;

/*
 * Determines the warnings that are suppressed by a LINTED directive.  For
 * globally suppressed warnings, see 'msgset'.
 *
 * LWARN_ALL:   all warnings are enabled
 * LWARN_NONE:  all warnings are suppressed
 * n >= 0:      warning n is ignored, the others are active
 */
int lwarn = LWARN_ALL;

/* Temporarily suppress warnings about wrong types for bit-fields. */
bool suppress_bitfieldtype;

/* Temporarily suppress warnings about use of 'long long'. */
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;
}

/*
 * Prints a warning if a statement cannot be reached.
 */
void
check_statement_reachable(const char *stmt_kind)
{
        if (!reached && warn_about_unreachable) {
                /* '%s' statement not reached */
                warning(193, stmt_kind);
                warn_about_unreachable = false;
        }
}

/*
 * Called after a function declaration which introduces a function definition
 * and before an (optional) old-style parameter declaration list.
 *
 * Puts all symbols declared in the prototype or in an old-style parameter
 * list back to the symbol table.
 *
 * Does the usual checking of storage class, type (return value),
 * redeclaration, etc.
 */
void
begin_function(sym_t *fsym)
{
        funcsym = fsym;

        /*
         * Put all symbols declared in the parameter list back to the symbol
         * table.
         */
        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);
                }
        }

        /*
         * In old_style_function() we did not know whether it is an old style
         * function definition or only an old-style declaration, if there are
         * no parameters inside the parameter list ("f()").
         */
        if (!fsym->s_type->t_proto && fsym->u.s_old_style_params == NULL)
                fsym->s_osdef = true;

        check_type(fsym);

        /*
         * check_type() checks for almost all possible errors, but not for
         * incomplete return values (these are allowed in declarations)
         */
        if (fsym->s_type->t_subt->t_tspec != VOID &&
            is_incomplete(fsym->s_type->t_subt)) {
                /* cannot return incomplete type */
                error(67);
        }

        fsym->s_def = DEF;

        if (fsym->s_scl == TYPEDEF) {
                fsym->s_scl = EXTERN;
                /* invalid storage class */
                error(8);
        }

        if (dcs->d_inline)
                fsym->s_inline = true;

        /*
         * Parameters in new-style function declarations need a name. ('void'
         * is already removed from the list of parameters.)
         */
        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);
                        /* formal parameter #%d lacks name */
                        error(59, n);
                } else {
                        lint_assert(param->s_name != unnamed);
                }
                n++;
        }

        /*
         * We must also remember the position. s_def_pos is overwritten if this
         * is an old-style definition, and we had already a prototype.
         */
        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)) {

                        /*
                         * Print nothing if the newly defined function is
                         * defined in old style. A better warning will be
                         * printed in check_func_lint_directives().
                         */
                        if (dowarn && !fsym->s_osdef) {
                                /* TODO: error in C99 mode as well? */
                                if (!allow_trad && !allow_c99)
                                        /* redeclaration of '%s' */
                                        error(27, fsym->s_name);
                                else
                                        /* redeclaration of '%s' */
                                        warning(27, fsym->s_name);
                                print_previous_declaration(rdsym);
                        }

                        copy_usage_info(fsym, rdsym);

                        /*
                         * If the old symbol was a prototype and the new one is
                         * none, overtake the position of the declaration of
                         * the prototype.
                         */
                        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))
                        /* function definition for '%s' is not a prototype */
                        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;

        /* C99 5.1.2.2.3 "Program termination" p1 */
        if (allow_c99 && strcmp(funcsym->s_name, "main") == 0)
                return;

        /* function '%s' falls off bottom without returning value */
        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)
                /* function '%s' has 'return expr' and 'return' */
                warning(216, funcsym->s_name);

        /* Warn about unused parameters. */
        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);

        /* clean up after syntax errors, see test stmt_for.c. */
        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);

        /* must be set on level 0 */
        set_reached(true);

        funcsym = NULL;
}

void
named_label(sym_t *sym)
{

        if (sym->s_set)
                /* label '%s' redefined */
                error(194, sym->s_name);
        else
                mark_as_set(sym);

        /* XXX: Assuming that each label is reachable is wrong. */
        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)
                /* '%s' statement not reached */
                warning(193, "case");
}

static void
check_case_label_enum(const tnode_t *tn, const control_statement *cs)
{
        /* similar to typeok_enum in tree.c */

        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 /* not yet ready, see msg_130.c */
        /* enum type mismatch: '%s' '%s' '%s' */
        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))
                        /* duplicate case '%ju' in switch */
                        error(200, (uintmax_t)nv->u.integer);
                else
                        /* duplicate case '%jd' in switch */
                        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) {
                /* case not in switch */
                error(195);
                return;
        }

        if (tn == NULL)
                return;

        if (tn->tn_op != CON) {
                /* non-constant case expression */
                error(197);
                return;
        }

        if (!is_integer(tn->tn_type->t_tspec)) {
                /* non-integral case expression */
                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)
                        /* fallthrough on case statement */
                        warning(220);
        }

        tspec_t t = tn->tn_type->t_tspec;
        if ((t == LONG || t == ULONG || t == LLONG || t == ULLONG)
            && !allow_c90)
                /* case label must be of type 'int' in traditional C */
                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)
{
        /* find the innermost switch statement */
        control_statement *cs;
        for (cs = cstmt; cs != NULL && !cs->c_switch; cs = cs->c_surrounding)
                continue;

        if (cs == NULL)
                /* default outside switch */
                error(201);
        else if (cs->c_default)
                /* duplicate default in switch */
                error(202);
        else {
                if (reached && !suppress_fallthrough) {
                        if (hflag)
                                /* fallthrough on default statement */
                                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)) {
                /* C99 6.5.15p4 for the ?: operator; see typeok:QUEST */
                /* C99 6.8.4.1p1 for if statements */
                /* C99 6.8.5p2 for while, do and for loops */
                /* controlling expressions must have scalar type, not '%s' */
                error(204, type_name(tn->tn_type));
                return NULL;
        }

        if (tn != NULL && Tflag
            && !is_typeok_bool_compares_with_zero(tn, is_do_while)) {
                /* controlling expression must be bool, not '%s' */
                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) {
                /* XXX: what if inside 'if (0)'? */
                set_reached(constant_is_nonzero(tn));
                /* XXX: what about always_else? */
                cstmt->c_always_then = reached;
        }
}

void
stmt_if_then_stmt(void)
{
        cstmt->c_reached_end_of_then = reached;
        /* XXX: what if inside 'if (0)'? */
        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)) {
                /* switch expression must have integral type, not '%s' */
                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)
                        /* switch expression must be of type 'int' in ... */
                        warning(271);
        }

        /*
         * Remember the type of the expression. Because it's possible that
         * (*tp) is allocated on tree memory, the type must be duplicated. This
         * is not too complicated because it is only an integer type.
         */
        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;
        }

        /* leak the memory, for check_case_label_bitand */
        (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) {
                /*
                 * Warn if the number of case labels is different from the
                 * number of enumerators.
                 */
                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)
                        /* enumeration value(s) not handled in switch */
                        warning(206);
        }

        check_getopt_end_switch();

        if (cstmt->c_break) {
                /*
                 * The end of the switch statement is always reached since
                 * c_break is only set if a break statement can actually be
                 * reached.
                 */
                set_reached(true);
        } else if (cstmt->c_default ||
                   (hflag && cstmt->c_switch_type->t_is_enum &&
                    nenum == nclab)) {
                /*
                 * The end of the switch statement is reached if the end of the
                 * last statement inside it is reached.
                 */
        } else {
                /*
                 * There are possible values that are not handled in the switch
                 * statement.
                 */
                set_reached(true);
        }

        end_control_statement(CS_SWITCH);
}

void
stmt_while_expr(tnode_t *tn)
{
        if (!reached) {
                /* FIXME: that's plain wrong. */
                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)
                        /* continue in 'do ... while (0)' loop */
                        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 there is no initialization expression it is possible that it is
         * intended not to enter the loop at top.
         */
        if (tn1 != NULL && !reached)
                set_reached(true);

        begin_control_statement(CS_FOR);
        cstmt->c_loop = true;

        /*
         * Store the tree memory for the reinitialization expression. Also
         * remember this expression itself. We must check it at the end of the
         * loop to get "used but not set" warnings correct.
         */
        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);

        /* The tn3 expression is checked in stmt_for_exprs_stmt. */

        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;

        /* simply "statement not reached" would be confusing */
        if (!reached && warn_about_unreachable) {
                /* end-of-loop code not reached */
                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)
                /* break outside loop or switch */
                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)
                /* continue outside loop */
                error(209);
        else
                /* TODO: only if reachable, for symmetry with c_break */
                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))
                /* parenthesized return value */
                query_message(9);

        /* Create a temporary node for the left side */
        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;    /* better than nothing */

        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)
                        /* '%s' returns pointer to automatic object */
                        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) {
                /* syntax error '%s' */
                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) {
                /* void function '%s' cannot return value */
                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)
                        /* function '%s' expects to return value */
                        error(214, funcsym->s_name);
                else
                        /* function '%s' expects to return value */
                        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)
                        /* comment ** %s ** must precede function definition */
                        warning_at(282, &argsused_pos, "ARGSUSED");
                nargusg = -1;
        }
        if (nvararg != -1) {
                if (!silent)
                        /* comment ** %s ** must precede function definition */
                        warning_at(282, &vapos, "VARARGS");
                nvararg = -1;
        }
        if (printflike_argnum != -1) {
                if (!silent)
                        /* comment ** %s ** must precede function definition */
                        warning_at(282, &printflike_pos, "PRINTFLIKE");
                printflike_argnum = -1;
        }
        if (scanflike_argnum != -1) {
                if (!silent)
                        /* comment ** %s ** must precede function definition */
                        warning_at(282, &scanflike_pos, "SCANFLIKE");
                scanflike_argnum = -1;
        }

        dcs->d_asm = false;

        /*
         * Needed for BSD yacc in case of parse errors; GNU Bison 3.0.4 is
         * fine.  See test gcc_attribute.c, function_with_unknown_attribute.
         */
        in_gcc_attribute = false;
        while (dcs->d_enclosing != NULL)
                end_declaration_level();
}

/*
 * Only the first n parameters of the following function are checked for usage.
 * A missing argument is taken to be 0.
 */
static void
argsused(int n)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                warning(280, "ARGSUSED");
                return;
        }
        if (nargusg != -1)
                /* duplicate comment ** %s ** */
                warning(281, "ARGSUSED");
        nargusg = n != -1 ? n : 0;
        argsused_pos = curr_pos;
}

static void
varargs(int n)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                warning(280, "VARARGS");
                return;
        }
        if (nvararg != -1)
                /* duplicate comment ** %s ** */
                warning(281, "VARARGS");
        nvararg = n != -1 ? n : 0;
        vapos = curr_pos;
}

/*
 * Check all parameters until the (n-1)-th as usual. The n-th argument is
 * used to check the types of the remaining arguments.
 */
static void
printflike(int n)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                warning(280, "PRINTFLIKE");
                return;
        }
        if (printflike_argnum != -1)
                /* duplicate comment ** %s ** */
                warning(281, "PRINTFLIKE");
        printflike_argnum = n != -1 ? n : 0;
        printflike_pos = curr_pos;
}

/*
 * Check all parameters until the (n-1)-th as usual. The n-th argument is
 * used the check the types of remaining arguments.
 */
static void
scanflike(int n)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                warning(280, "SCANFLIKE");
                return;
        }
        if (scanflike_argnum != -1)
                /* duplicate comment ** %s ** */
                warning(281, "SCANFLIKE");
        scanflike_argnum = n != -1 ? n : 0;
        scanflike_pos = curr_pos;
}

static void
lintlib(void)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                warning(280, "LINTLIBRARY");
                return;
        }
        llibflg = true;
        vflag = true;
}

/*
 * PROTOLIB in conjunction with LINTLIBRARY can be used to handle
 * prototypes like function definitions. This is done if the argument
 * to PROTOLIB is nonzero. Otherwise, prototypes are handled normally.
 */
static void
protolib(int n)
{
        if (dcs->d_kind != DLK_EXTERN) {
                /* comment ** %s ** must be outside function */
                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;
        }
}