root/usr/src/tools/smatch/src/smatch_param_to_mtag_data.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
 */

/*
 * Take a look at request_threaded_irq().  It takes thread_fn and dev_id.  Then
 * it does:
 *
 *      action = kzalloc(sizeof(struct irqaction), GFP_KERNEL);
 *      action->thread_fn = thread_fn;
 *      action->dev_id = dev_id;
 *
 * It doesn't ever pass action back to the higher levels, but instead registers
 * it with the lower levels.
 *
 * The kzalloc() allocation creates a new mtag.  We don't know at this point
 * what "thread_fn" and "dev_id" are because they come from many different
 * sources.
 *
 * So what we do is we pass the information back to the callers that thread_fn
 * and dev_id are stored as a specific mtag data.  Then when the callers *do*
 * know what values are passed they create an mtag_alias.  An mtag_alias is a
 * many to one relationship.  Then they store that in mtag_data using the
 * mtag_alias.
 *
 */

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

static int my_id;

struct tag_assign_info {
        mtag_t tag;
        int offset;
};
ALLOCATOR(tag_assign_info, "tag name offset");

static struct smatch_state *alloc_tag_data_state(mtag_t tag, char *name, int offset)
{
        struct smatch_state *state;
        struct tag_assign_info *data;

        data = __alloc_tag_assign_info(0);
        data->tag = tag;
        data->offset = offset;

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

struct smatch_state *merge_tag_info(struct smatch_state *s1, struct smatch_state *s2)
{
        /* Basically ignore undefined states */
        if (s1 == &undefined)
                return s2;
        if (s2 == &undefined)
                return s1;

        return &merged;
}

static void match_assign(struct expression *expr)
{
        struct expression *left;
        struct symbol *right_sym;
        char *name;
        mtag_t tag;
        int offset;
        int param;

        if (expr->op != '=')
                return;
        left = strip_expr(expr->left);
        if (is_local_variable(left))
                return;
        right_sym = expr_to_sym(expr->right);
        if (!right_sym)
                return;

        param = get_param_num_from_sym(right_sym);
        if (param < 0)
                return;
        // FIXME:  modify param_has_filter_data() to take a name/sym
        if (!expr_to_mtag_offset(left, &tag, &offset))
                return;
        name = expr_to_str(left);
        if (!name)
                return;
        set_state_expr(my_id, expr->right, alloc_tag_data_state(tag, name, offset));
        free_string(name);
}

static void propogate_assignment(struct expression *expr, mtag_t tag, int offset, int param, char *key)
{
        struct expression *arg;
        int orig_param;
        char buf[32];
        char *name;
        struct symbol *sym;

        arg = get_argument_from_call_expr(expr->args, param);
        if (!arg)
                return;
        name = get_variable_from_key(arg, key, &sym);
        if (!name || !sym)
                goto free;

        orig_param = get_param_num_from_sym(sym);
        if (orig_param < 0)
                goto free;

        snprintf(buf, sizeof(buf), "$->[%d]", offset);
        set_state(my_id, name, sym, alloc_tag_data_state(tag, buf, offset));
free:
        free_string(name);
}

static void assign_to_alias(struct expression *expr, int param, mtag_t tag, int offset, char *key)
{
        struct expression *arg, *gen_expr;
        struct range_list *rl;
        mtag_t arg_tag;
        mtag_t alias;
        int arg_offset;

        arg = get_argument_from_call_expr(expr->args, param);
        if (!arg)
                return;

        gen_expr = gen_expression_from_key(arg, key);
        if (!gen_expr)
                return;

        get_absolute_rl(gen_expr, &rl);

        if (!create_mtag_alias(tag, expr, &alias))
                return;

//      insert_mtag_data(alias, offset, rl);

        // FIXME:  is arg_offset handled correctly?
        if (expr_to_mtag_offset(gen_expr, &arg_tag, &arg_offset) &&
            arg_offset < MTAG_OFFSET_MASK)
                sql_insert_mtag_map(alias, offset, arg_tag, arg_offset);
}

static void call_does_mtag_assign(struct expression *expr, int param, char *key, char *value)
{
        char *p;
        mtag_t tag;
        int offset;

        while (expr->type == EXPR_ASSIGNMENT)
                expr = strip_expr(expr->right);
        if (expr->type != EXPR_CALL)
                return;

        tag = strtoul(value, NULL, 10);
        p = strchr(value, '+');
        if (!p)
                return;
        offset = atoi(p + 1);

//      save_mtag_to_map(expr, tag, offset, param, key, value);
        propogate_assignment(expr, tag, offset, param, key);
        assign_to_alias(expr, param, tag, offset, key);
}

static void print_stored_to_mtag(int return_id, char *return_ranges, struct expression *expr)
{
        struct sm_state *sm;
        struct tag_assign_info *data;
        char buf[256];
        const char *param_name;
        int param;

        FOR_EACH_MY_SM(my_id, __get_cur_stree(), sm) {
                if (!sm->state->data)
                        continue;

                param = get_param_num_from_sym(sm->sym);
                if (param < 0)
                        continue;
                param_name = get_param_name(sm);
                if (!param_name)
                        continue;

                data = sm->state->data;
                snprintf(buf, sizeof(buf), "%lld+%d", data->tag, data->offset);
                sql_insert_return_states(return_id, return_ranges, MTAG_ASSIGN, param, param_name, buf);
        } END_FOR_EACH_SM(sm);
}

void register_param_to_mtag_data(int id)
{
        my_id = id;

        set_dynamic_states(my_id);
        add_hook(&match_assign, ASSIGNMENT_HOOK);
        select_return_states_hook(MTAG_ASSIGN, &call_does_mtag_assign);
        add_merge_hook(my_id, &merge_tag_info);
        add_split_return_callback(&print_stored_to_mtag);
}