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

/*
 * There are a bunch of allocation functions where we allocate some memory,
 * set up some struct members and then return the allocated memory.  One
 * nice thing about this is that we just one pointer to the allocated memory
 * so what we can do is we can generate a mtag alias for it in the caller.
 */

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

static int my_id;

STATE(fresh);

struct alloc_info *alloc_funcs;

struct alloc_info kernel_allocation_funcs[] = {
        {"kmalloc", 0},
        {"kmalloc_node", 0},
        {"kzalloc", 0},
        {"kzalloc_node", 0},
        {"vmalloc", 0},
        {"__vmalloc", 0},
        {"kvmalloc", 0},
        {"kcalloc", 0, 1},
        {"kmalloc_array", 0, 1},
        {"sock_kmalloc", 1},
        {"kmemdup", 1},
        {"kmemdup_user", 1},
        {"dma_alloc_attrs", 1},
        {"pci_alloc_consistent", 1},
        {"pci_alloc_coherent", 1},
        {"devm_kmalloc", 1},
        {"devm_kzalloc", 1},
        {"krealloc", 1},
        {"__alloc_bootmem", 0},
        {"alloc_bootmem", 0},
        {"dma_alloc_contiguous", 1},
        {"dma_alloc_coherent", 1},
        {},
};

struct alloc_info general_allocation_funcs[] = {
        {"malloc", 0},
        {"calloc", 0, 1},
        {"memdup", 1},
        {"realloc", 1},
        {},
};

static void pre_merge_hook(struct sm_state *cur, struct sm_state *other)
{
        struct smatch_state *state;
        sval_t sval;

        state = get_state(SMATCH_EXTRA, cur->name, cur->sym);
        if (estate_get_single_value(state, &sval) && sval.value == 0)
                set_state(my_id, cur->name, cur->sym, &undefined);
}

static int fresh_callback(void *fresh, int argc, char **argv, char **azColName)
{
        *(int *)fresh = 1;
        return 0;
}

static int fresh_from_db(struct expression *call)
{
        int fresh = 0;

        /* for function pointers assume everything is used */
        if (call->fn->type != EXPR_SYMBOL)
                return 0;

        run_sql(&fresh_callback, &fresh,
                "select * from return_states where %s and type = %d and parameter = -1 and key = '$' limit 1;",
                get_static_filter(call->fn->symbol), FRESH_ALLOC);
        return fresh;
}

bool is_fresh_alloc_var_sym(const char *var, struct symbol *sym)
{
        return get_state(my_id, var, sym) == &fresh;
}

bool is_fresh_alloc(struct expression *expr)
{
        sval_t sval;
        int i;

        if (!expr)
                return false;

        if (get_implied_value_fast(expr, &sval) && sval.value == 0)
                return false;

        if (get_state_expr(my_id, expr) == &fresh)
                return true;

        if (expr->type != EXPR_CALL)
                return false;
        if (fresh_from_db(expr))
                return true;
        i = -1;
        while (alloc_funcs[++i].fn) {
                if (sym_name_is(kernel_allocation_funcs[i].fn, expr->fn))
                        return true;
        }
        return false;
}

static void record_alloc_func(int return_id, char *return_ranges, struct expression *expr)
{
        if (!is_fresh_alloc(expr))
                return;
        sql_insert_return_states(return_id, return_ranges, FRESH_ALLOC, -1, "$", "");
}

static void set_unfresh(struct expression *expr)
{
        struct sm_state *sm;

        sm = get_sm_state_expr(my_id, expr);
        if (!sm)
                return;
        if (!slist_has_state(sm->possible, &fresh))
                return;
        // TODO call unfresh hooks
        set_state_expr(my_id, expr, &undefined);
}

static void match_assign(struct expression *expr)
{
        set_unfresh(expr->right);
}

static void match_call(struct expression *expr)
{
        struct expression *arg;

        FOR_EACH_PTR(expr->args, arg) {
                set_unfresh(arg);
        } END_FOR_EACH_PTR(arg);
}

static struct expression *handled;
static void set_fresh(struct expression *expr)
{
        struct range_list *rl;

        expr = strip_expr(expr);
        if (expr->type != EXPR_SYMBOL)
                return;
        if (expr == handled)
                return;

        get_absolute_rl(expr, &rl);
        rl = rl_intersection(rl, valid_ptr_rl);
        if (!rl)
                return;
        set_state_expr(my_id, expr, &fresh);
        handled = expr;
}

static void returns_fresh_alloc(struct expression *expr, int param, char *key, char *value)
{
        if (param != -1 || !key || strcmp(key, "$") != 0)
                return;
        if (expr->type != EXPR_ASSIGNMENT)
                return;

        set_fresh(expr->left);
}

static void match_alloc(const char *fn, struct expression *expr, void *_size_arg)
{
        set_fresh(expr->left);
}

void register_fresh_alloc(int id)
{
        int i;

        my_id = id;

        if (option_project == PROJ_KERNEL)
                alloc_funcs = kernel_allocation_funcs;
        else
                alloc_funcs = general_allocation_funcs;

        i = -1;
        while (alloc_funcs[++i].fn)
                add_function_assign_hook(alloc_funcs[i].fn, &match_alloc, 0);

        add_split_return_callback(&record_alloc_func);
        select_return_states_hook(FRESH_ALLOC, &returns_fresh_alloc);
        add_hook(&match_assign, ASSIGNMENT_HOOK);
        add_hook(&match_call, FUNCTION_CALL_HOOK);

        add_pre_merge_hook(my_id, &pre_merge_hook);
}