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

/*
 * This is for smatch_extra.c to use.  It sort of like check_assigned_expr.c but
 * more limited.  Say a function returns "64min-s64max[$0->data]" and the caller
 * does "struct whatever *p = get_data(dev);" then we want to record that p is
 * now the same as "dev->data".  Then if we update "p->foo" it means we can
 * update "dev->data->foo" as well.
 *
 */

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

static int my_id;
static int link_id;

static struct smatch_state *alloc_my_state(const char *name, struct symbol *sym)
{
        struct smatch_state *state;

        state = __alloc_smatch_state(0);
        state->name = alloc_sname(name);
        state->data = sym;
        return state;
}

static void undef(struct sm_state *sm, struct expression *mod_expr)
{
        if (__in_fake_parameter_assign)
                return;
        set_state(my_id, sm->name, sm->sym, &undefined);
}

char *map_call_to_other_name_sym(const char *name, struct symbol *sym, struct symbol **new_sym)
{
        struct smatch_state *state;
        int skip;
        char buf[256];

        /* skip 'foo->'.  This was checked in the caller. */
        skip = sym->ident->len + 2;

        state = get_state(my_id, sym->ident->name, sym);
        if (!state || !state->data)
                return NULL;

        snprintf(buf, sizeof(buf), "%s->%s", state->name, name + skip);
        *new_sym = state->data;
        return alloc_string(buf);
}

static char *map_my_state_long_to_short(struct sm_state *sm, const char *name, struct symbol *sym, struct symbol **new_sym, bool stack)
{
        int len;
        char buf[256];

        if (sm->state->data != sym)
                return NULL;
        len = strlen(sm->state->name);
        if (strncmp(name, sm->state->name, len) != 0)
                return NULL;

        if (name[len] == '.')
                return NULL;
        if (!stack && name[len] != '-')
                return NULL;
        snprintf(buf, sizeof(buf), "%s%s", sm->name, name + len);
        *new_sym = sm->sym;
        return alloc_string(buf);
}

/*
 * Normally, we expect people to consistently refer to variables by the shortest
 * name.  So they use "b->a" instead of "foo->bar.a" when both point to the
 * same memory location.  However, when we're dealing across function boundaries
 * then sometimes we pass frob(foo) which sets foo->bar.a.  In that case, we
 * translate it to the shorter name.  Smatch extra updates the shorter name,
 * which in turn updates the longer name.
 *
 */
char *map_long_to_short_name_sym(const char *name, struct symbol *sym, struct symbol **new_sym, bool use_stack)
{
        char *ret;
        struct sm_state *sm;

        *new_sym = NULL;

        FOR_EACH_SM(__get_cur_stree(), sm) {
                if (sm->owner == my_id) {
                        ret = map_my_state_long_to_short(sm, name, sym, new_sym, use_stack);
                        if (ret) {
                                if (local_debug)
                                        sm_msg("%s: my_state: name = '%s' sm = '%s'",
                                               __func__, name, show_sm(sm));
                                return ret;
                        }
                        continue;
                }
        } END_FOR_EACH_SM(sm);

        return NULL;
}

char *map_call_to_param_name_sym(struct expression *expr, struct symbol **sym)
{
        char *name;
        struct symbol *start_sym;
        struct smatch_state *state;

        *sym = NULL;

        name = expr_to_str_sym(expr, &start_sym);
        if (!name)
                return NULL;
        if (expr->type == EXPR_CALL)
                start_sym = expr_to_sym(expr->fn);

        state = get_state(my_id, name, start_sym);
        free_string(name);
        if (!state || !state->data)
                return NULL;

        *sym = state->data;
        return alloc_string(state->name);
}

static void store_mapping_helper(char *left_name, struct symbol *left_sym, struct expression *call, const char *return_string)
{
        const char *p = return_string;
        char *close;
        int param;
        struct expression *arg, *new;
        char *right_name;
        struct symbol *right_sym;
        char buf[256];

        while (*p && *p != '[')
                p++;
        if (!*p)
                return;
        p++;
        if (*p != '$')
                return;

        snprintf(buf, sizeof(buf), "%s", p);
        close = strchr(buf, ']');
        if (!close)
                return;
        *close = '\0';

        param = atoi(buf + 1);
        arg = get_argument_from_call_expr(call->args, param);
        if (!arg)
                return;

        new = gen_expression_from_key(arg, buf);
        if (!new)
                return;

        right_name = expr_to_var_sym(new, &right_sym);
        if (!right_name || !right_sym)
                goto free;

        set_state(my_id, left_name, left_sym, alloc_my_state(right_name, right_sym));
        store_link(link_id, right_name, right_sym, left_name, left_sym);

free:
        free_string(right_name);
}

void __add_return_to_param_mapping(struct expression *expr, const char *return_string)
{
        struct expression *call;
        char *left_name = NULL;
        struct symbol *left_sym;

        if (expr->type == EXPR_ASSIGNMENT) {
                left_name = expr_to_var_sym(expr->left, &left_sym);
                if (!left_name || !left_sym)
                        goto free;

                call = strip_expr(expr->right);
                if (call->type != EXPR_CALL)
                        goto free;

                store_mapping_helper(left_name, left_sym, call, return_string);
                goto free;
        }

        if (expr->type == EXPR_CALL &&
            expr_get_parent_stmt(expr) &&
            expr_get_parent_stmt(expr)->type == STMT_RETURN) {
                call = strip_expr(expr);
                left_sym = expr_to_sym(call->fn);
                if (!left_sym)
                        return;
                left_name = expr_to_str(call);
                if (!left_name)
                        return;

                store_mapping_helper(left_name, left_sym, call, return_string);
                goto free;

        }

free:
        free_string(left_name);
}

void register_return_to_param(int id)
{
        my_id = id;
        set_dynamic_states(my_id);
        add_modification_hook(my_id, &undef);
}

void register_return_to_param_links(int id)
{
        link_id = id;
        set_up_link_functions(my_id, link_id);
}