root/usr/src/tools/smatch/src/smatch_mtag_data.c
/*
 * Copyright (C) 2016 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
 */

/*
 * What we're doing here is saving all the possible values for static variables.
 * Later on we might do globals as well.
 *
 */

#include "smatch.h"
#include "smatch_slist.h"
#include "smatch_extra.h"

static int my_id;
static struct stree *vals;

static int save_rl(void *_rl, int argc, char **argv, char **azColName)
{
        unsigned long *rl = _rl;

        *rl = strtoul(argv[0], NULL, 10);
        return 0;
}

static struct range_list *select_orig(mtag_t tag, int offset)
{
        struct range_list *rl = NULL;

        mem_sql(&save_rl, &rl, "select value from mtag_data where tag = %lld and offset = %d;",
                tag, offset);
        return rl;
}

static int is_kernel_param(const char *name)
{
        struct sm_state *tmp;
        char buf[256];

        /*
         * I'm ignoring these because otherwise Smatch thinks that kernel
         * parameters are always set to the default.
         *
         */

        if (option_project != PROJ_KERNEL)
                return 0;

        snprintf(buf, sizeof(buf), "__param_%s.arg", name);

        FOR_EACH_SM(vals, tmp) {
                if (strcmp(tmp->name, buf) == 0)
                        return 1;
        } END_FOR_EACH_SM(tmp);

        return 0;
}

static bool is_ignored_macro(struct expression *expr)
{
        char *macro;

        macro = get_macro_name(expr->pos);
        if (!macro)
                return false;
        if (strcmp(macro, "EXPORT_SYMBOL") == 0)
                return true;
        return false;
}

static bool is_head_next(struct expression *expr)
{
        struct symbol *type;

        /* Smatch thinks head->next == head is always true.  *sad face* */

        if (option_project != PROJ_KERNEL)
                return false;

        if (expr->type != EXPR_DEREF)
                return false;
        if (!expr->member || !expr->member->name ||
            strcmp(expr->member->name, "next") != 0)
                return false;

        type = get_type(expr->deref);
        if (!type)
                return false;
        if (type->type == SYM_PTR)
                type = get_real_base_type(type);
        if (type->type != SYM_STRUCT)
                return false;
        if (!type->ident || !type->ident->name ||
            strcmp(type->ident->name, "list_head") != 0)
                return false;
        return true;
}

mtag_t ignored_mtag;
static bool is_ignored_tag(mtag_t tag)
{
        if (tag == ignored_mtag)
                return true;
        return false;
}

static void insert_mtag_data(mtag_t tag, int offset, struct range_list *rl)
{
        if (is_ignored_tag(tag))
                return;

        rl = clone_rl_permanent(rl);

        mem_sql(NULL, NULL, "delete from mtag_data where tag = %lld and offset = %d and type = %d",
                tag, offset, DATA_VALUE);
        mem_sql(NULL, NULL, "insert into mtag_data values (%lld, %d, %d, '%lu');",
                tag, offset, DATA_VALUE, (unsigned long)rl);
}

static bool invalid_type(struct symbol *type)
{
        if (!type)
                return true;
        if (type == &void_ctype)
                return true;
        if (type->type == SYM_STRUCT ||
            type->type == SYM_ARRAY ||
            type->type == SYM_UNION)
                return true;
        return false;
}

static bool parent_is_fresh_alloc(struct expression *expr)
{
        struct symbol *sym;

        sym = expr_to_sym(expr);
        if (!sym || !sym->ident)
                return false;
        return is_fresh_alloc_var_sym(sym->ident->name, sym);
}

void update_mtag_data(struct expression *expr, struct smatch_state *state)
{
        struct range_list *orig, *new;
        struct symbol *type;
        char *name;
        mtag_t tag;
        int offset;

        if (!expr)
                return;
        if (is_local_variable(expr))
                return;
        if (is_ignored_macro(expr))
                return;
        if (is_head_next(expr))
                return;
        name = expr_to_var(expr);
        if (is_kernel_param(name)) {
                free_string(name);
                return;
        }
        free_string(name);

        if (!expr_to_mtag_offset(expr, &tag, &offset))
                return;

        type = get_type(expr);
        if (offset == 0 && invalid_type(type))
                return;

        if (parent_is_fresh_alloc(expr))
                orig = NULL;
        else
                orig = select_orig(tag, offset);
        new = rl_union(orig, estate_rl(state));
        insert_mtag_data(tag, offset, new);
}

static void match_global_assign(struct expression *expr)
{
        struct range_list *rl;
        mtag_t tag;
        int offset;
        char *name;

        if (is_ignored_macro(expr))
                return;
        if (is_head_next(expr->left))
                return;
        name = expr_to_var(expr->left);
        if (is_kernel_param(name)) {
                free_string(name);
                return;
        }
        free_string(name);

        if (!expr_to_mtag_offset(expr->left, &tag, &offset))
                return;

        get_absolute_rl(expr->right, &rl);
        insert_mtag_data(tag, offset, rl);
}

static int save_mtag_data(void *_unused, int argc, char **argv, char **azColName)
{
        struct range_list *rl;

        if (argc != 4) {
                sm_msg("Error saving mtag data");
                return 0;
        }
        if (!option_info)
                return 0;

        rl = (struct range_list *)strtoul(argv[3], NULL, 10);
        sm_msg("SQL: insert into mtag_data values ('%s', '%s', '%s', '%s');",
               argv[0], argv[1], argv[2], show_rl(rl));

        return 0;
}

static void match_end_file(struct symbol_list *sym_list)
{
        mem_sql(&save_mtag_data, NULL, "select * from mtag_data where type = %d;",
                DATA_VALUE);
}

struct db_info {
        struct symbol *type;
        struct range_list *rl;
};

static int get_vals(void *_db_info, int argc, char **argv, char **azColName)
{
        struct db_info *db_info = _db_info;
        struct range_list *tmp;

        str_to_rl(db_info->type, argv[0], &tmp);
        if (db_info->rl)
                db_info->rl = rl_union(db_info->rl, tmp);
        else
                db_info->rl = tmp;

        return 0;
}

struct db_cache_results {
        mtag_t tag;
        struct range_list *rl;
};
static struct db_cache_results cached_results[8];

static int get_rl_from_mtag_offset(mtag_t tag, int offset, struct symbol *type, struct range_list **rl)
{
        struct db_info db_info = {};
        mtag_t merged = tag | offset;
        static int idx;
        int ret;
        int i;

        for (i = 0; i < ARRAY_SIZE(cached_results); i++) {
                if (merged == cached_results[i].tag) {
                        if (cached_results[i].rl) {
                                *rl = cached_results[i].rl;
                                return 1;
                        }
                        return 0;
                }
        }

        db_info.type = type;

        run_sql(get_vals, &db_info,
                "select value from mtag_data where tag = %lld and offset = %d and type = %d;",
                tag, offset, DATA_VALUE);
        if (!db_info.rl || is_whole_rl(db_info.rl)) {
                db_info.rl = NULL;
                ret = 0;
                goto update_cache;
        }

        *rl = db_info.rl;
        ret = 1;

update_cache:
        cached_results[idx].tag = merged;
        cached_results[idx].rl = db_info.rl;
        idx = (idx + 1) % ARRAY_SIZE(cached_results);

        return ret;
}

static void clear_cache(struct symbol *sym)
{
        memset(cached_results, 0, sizeof(cached_results));
}

int get_mtag_rl(struct expression *expr, struct range_list **rl)
{
        struct symbol *type;
        mtag_t tag;
        int offset;

        if (is_local_variable(expr))
                return 0;
        if (!expr_to_mtag_offset(expr, &tag, &offset))
                return 0;
        if (offset >= MTAG_OFFSET_MASK)
                return 0;

        type = get_type(expr);
        if (invalid_type(type))
                return 0;

        return get_rl_from_mtag_offset(tag, offset, type, rl);
}

void register_mtag_data(int id)
{
        my_id = id;

        ignored_mtag = str_to_mtag("extern boot_params");
        add_hook(&clear_cache, FUNC_DEF_HOOK);

//      if (!option_info)
//              return;
        add_hook(&match_global_assign, GLOBAL_ASSIGNMENT_HOOK);
        add_hook(&match_end_file, END_FILE_HOOK);
}