root/usr/src/tools/smatch/src/check_unreachable.c
/*
 * Copyright (C) 2014 Oracle.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see http://www.gnu.org/copyleft/gpl.txt
 */

#include "smatch.h"

static int my_id;

static int print_unreached = 1;
static struct string_list *turn_off_names;
static struct string_list *ignore_names;

static int empty_statement(struct statement *stmt)
{
        if (!stmt)
                return 0;
        if (stmt->type == STMT_EXPRESSION && !stmt->expression)
                return 1;
        return 0;
}

static int is_last_stmt(struct statement *cur_stmt)
{
        struct symbol *fn = get_base_type(cur_func_sym);
        struct statement *stmt;

        if (!fn)
                return 0;
        stmt = fn->stmt;
        if (!stmt)
                stmt = fn->inline_stmt;
        if (!stmt || stmt->type != STMT_COMPOUND)
                return 0;
        stmt = last_ptr_list((struct ptr_list *)stmt->stmts);
        if (stmt == cur_stmt)
                return 1;
        return 0;
}

static void print_unreached_initializers(struct symbol_list *sym_list)
{
        struct symbol *sym;

        FOR_EACH_PTR(sym_list, sym) {
                if (sym->initializer && !(sym->ctype.modifiers & MOD_STATIC))
                        sm_msg("info: '%s' is not actually initialized (unreached code).",
                                (sym->ident ? sym->ident->name : "this variable"));
        } END_FOR_EACH_PTR(sym);
}

static int is_ignored_macro(struct statement *stmt)
{
        char *name;
        char *tmp;

        name = get_macro_name(stmt->pos);
        if (!name)
                return 0;

        FOR_EACH_PTR(ignore_names, tmp) {
                if (strcmp(tmp, name) == 0)
                        return 1;
        } END_FOR_EACH_PTR(tmp);

        return 0;
}

static int prev_line_was_endif(struct statement *stmt)
{
        struct token *token;
        struct position pos = stmt->pos;

        pos.line--;
        pos.pos = 2;

        token = pos_get_token(pos);
        if (token && token_type(token) == TOKEN_IDENT &&
            strcmp(show_ident(token->ident), "endif") == 0)
                return 1;

        pos.line--;
        token = pos_get_token(pos);
        if (token && token_type(token) == TOKEN_IDENT &&
            strcmp(show_ident(token->ident), "endif") == 0)
                return 1;

        return 0;
}

static int we_jumped_into_the_middle_of_a_loop(struct statement *stmt)
{
        struct statement *prev;

        /*
         * Smatch doesn't handle loops correctly and this is a hack.  What we
         * do is that if the first unreachable statement is a loop and the
         * previous statement was a goto then it's probably code like this:
         *      goto first;
         *      for (;;) {
         *              frob();
         * first:
         *              more_frob();
         *      }
         * Every statement is reachable but only on the second iteration.
         */

        if (stmt->type != STMT_ITERATOR)
                return 0;
        prev = get_prev_statement();
        if (prev && prev->type == STMT_GOTO)
                return 1;
        return 0;
}

static void unreachable_stmt(struct statement *stmt)
{

        if (__inline_fn)
                return;

        if (!__path_is_null()) {
                print_unreached = 1;
                return;
        }

        /* if we hit a label then assume there is a matching goto */
        if (stmt->type == STMT_LABEL)
                print_unreached = 0;
        if (prev_line_was_endif(stmt))
                print_unreached = 0;
        if (we_jumped_into_the_middle_of_a_loop(stmt))
                print_unreached = 0;

        if (!print_unreached)
                return;
        if (empty_statement(stmt))
                return;
        if (is_ignored_macro(stmt))
                return;

        switch (stmt->type) {
        case STMT_COMPOUND: /* after a switch before a case stmt */
        case STMT_RANGE:
        case STMT_CASE:
                return;
        case STMT_DECLARATION: /* switch (x) { int a; case foo: ... */
                print_unreached_initializers(stmt->declaration);
                return;
        case STMT_RETURN: /* gcc complains if you don't have a return statement */
                if (is_last_stmt(stmt))
                        return;
                break;
        case STMT_GOTO:
                /* people put extra breaks inside switch statements */
                if (stmt->goto_label && stmt->goto_label->type == SYM_NODE &&
                    strcmp(stmt->goto_label->ident->name, "break") == 0)
                        return;
                break;
        default:
                break;
        }
        sm_warning("ignoring unreachable code.");
        print_unreached = 0;
}

static int is_turn_off(char *name)
{
        char *tmp;

        if (!name)
                return 0;

        FOR_EACH_PTR(turn_off_names, tmp) {
                if (strcmp(tmp, name) == 0)
                        return 1;
        } END_FOR_EACH_PTR(tmp);

        return 0;
}

static char *get_function_name(struct statement *stmt)
{
        struct expression *expr;

        if (stmt->type != STMT_EXPRESSION)
                return NULL;
        expr = stmt->expression;
        if (!expr || expr->type != EXPR_CALL)
                return NULL;
        if (expr->fn->type != EXPR_SYMBOL || !expr->fn->symbol_name)
                return NULL;
        return expr->fn->symbol_name->name;
}

static void turn_off_unreachable(struct statement *stmt)
{
        char *name;

        name = get_macro_name(stmt->pos);
        if (is_turn_off(name)) {
                print_unreached = 0;
                return;
        }

        if (stmt->type == STMT_IF &&
            known_condition_true(stmt->if_conditional) &&  __path_is_null()) {
                print_unreached = 0;
                return;
        }

        name = get_function_name(stmt);
        if (is_turn_off(name))
                print_unreached = 0;
}

static void register_turn_off_macros(void)
{
        struct token *token;
        char *macro;
        char name[256];

        if (option_project == PROJ_NONE)
                strcpy(name, "unreachable.turn_off");
        else
                snprintf(name, 256, "%s.unreachable.turn_off", option_project_str);

        token = get_tokens_file(name);
        if (!token)
                return;
        if (token_type(token) != TOKEN_STREAMBEGIN)
                return;
        token = token->next;
        while (token_type(token) != TOKEN_STREAMEND) {
                if (token_type(token) != TOKEN_IDENT)
                        return;
                macro = alloc_string(show_ident(token->ident));
                add_ptr_list(&turn_off_names, macro);
                token = token->next;
        }
        clear_token_alloc();
}

static void register_ignored_macros(void)
{
        struct token *token;
        char *macro;
        char name[256];

        if (option_project == PROJ_NONE)
                strcpy(name, "unreachable.ignore");
        else
                snprintf(name, 256, "%s.unreachable.ignore", option_project_str);

        token = get_tokens_file(name);
        if (!token)
                return;
        if (token_type(token) != TOKEN_STREAMBEGIN)
                return;
        token = token->next;
        while (token_type(token) != TOKEN_STREAMEND) {
                if (token_type(token) != TOKEN_IDENT)
                        return;
                macro = alloc_string(show_ident(token->ident));
                add_ptr_list(&ignore_names, macro);
                token = token->next;
        }
        clear_token_alloc();
}

void check_unreachable(int id)
{
        my_id = id;

        register_turn_off_macros();
        register_ignored_macros();
        add_hook(&unreachable_stmt, STMT_HOOK);
        add_hook(&turn_off_unreachable, STMT_HOOK_AFTER);
}