root/usr/src/tools/smatch/src/smatch_equiv.c
/*
 * Copyright (C) 2010 Dan Carpenter.
 *
 * 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
 */

/*
 * smatch_equiv.c is for tracking how variables are the same
 *
 * if (a == b) {
 * Or
 * x = y;
 *
 * When a variable gets modified all the old relationships are
 * deleted.  remove_equiv(expr);
 *
 */

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

ALLOCATOR(relation, "related variables");

static struct relation *alloc_relation(const char *name, struct symbol *sym)
{
        struct relation *tmp;

        tmp = __alloc_relation(0);
        tmp->name = alloc_string(name);
        tmp->sym = sym;
        return tmp;
}

struct related_list *clone_related_list(struct related_list *related)
{
        struct relation *rel;
        struct related_list *to_list = NULL;

        FOR_EACH_PTR(related, rel) {
                add_ptr_list(&to_list, rel);
        } END_FOR_EACH_PTR(rel);

        return to_list;
}

static int cmp_relation(struct relation *a, struct relation *b)
{
        int ret;

        if (a == b)
                return 0;

        if (a->sym > b->sym)
                return -1;
        if (a->sym < b->sym)
                return 1;

        ret = strcmp(a->name, b->name);
        if (ret)
                return ret;

        return 0;
}

struct related_list *get_shared_relations(struct related_list *one,
                                              struct related_list *two)
{
        struct related_list *ret = NULL;
        struct relation *one_rel;
        struct relation *two_rel;

        PREPARE_PTR_LIST(one, one_rel);
        PREPARE_PTR_LIST(two, two_rel);
        for (;;) {
                if (!one_rel || !two_rel)
                        break;
                if (cmp_relation(one_rel, two_rel) < 0) {
                        NEXT_PTR_LIST(one_rel);
                } else if (cmp_relation(one_rel, two_rel) == 0) {
                        add_ptr_list(&ret, one_rel);
                        NEXT_PTR_LIST(one_rel);
                        NEXT_PTR_LIST(two_rel);
                } else {
                        NEXT_PTR_LIST(two_rel);
                }
        }
        FINISH_PTR_LIST(two_rel);
        FINISH_PTR_LIST(one_rel);

        return ret;
}

static void add_related(struct related_list **rlist, const char *name, struct symbol *sym)
{
        struct relation *rel;
        struct relation *new;
        struct relation tmp = {
                .name = (char *)name,
                .sym = sym
        };

        FOR_EACH_PTR(*rlist, rel) {
                if (cmp_relation(rel, &tmp) < 0)
                        continue;
                if (cmp_relation(rel, &tmp) == 0)
                        return;
                new = alloc_relation(name, sym);
                INSERT_CURRENT(new, rel);
                return;
        } END_FOR_EACH_PTR(rel);
        new = alloc_relation(name, sym);
        add_ptr_list(rlist, new);
}

static struct related_list *del_related(struct smatch_state *state, const char *name, struct symbol *sym)
{
        struct relation *tmp;
        struct relation remove = {
                .name = (char *)name,
                .sym = sym,
        };
        struct related_list *ret = NULL;

        FOR_EACH_PTR(estate_related(state), tmp) {
                if (cmp_relation(tmp, &remove) != 0)
                        add_ptr_list(&ret, tmp);
        } END_FOR_EACH_PTR(tmp);

        return ret;
}

void remove_from_equiv(const char *name, struct symbol *sym)
{
        struct sm_state *orig_sm;
        struct relation *rel;
        struct smatch_state *state;
        struct related_list *to_update;

        orig_sm = get_sm_state(SMATCH_EXTRA, name, sym);
        if (!orig_sm || !get_dinfo(orig_sm->state)->related)
                return;

        state = clone_estate(orig_sm->state);
        to_update = del_related(state, name, sym);

        FOR_EACH_PTR(to_update, rel) {
                struct sm_state *old_sm, *new_sm;

                old_sm = get_sm_state(SMATCH_EXTRA, rel->name, rel->sym);
                if (!old_sm)
                        continue;

                new_sm = clone_sm(old_sm);
                get_dinfo(new_sm->state)->related = to_update;
                __set_sm(new_sm);
        } END_FOR_EACH_PTR(rel);
}

void remove_from_equiv_expr(struct expression *expr)
{
        char *name;
        struct symbol *sym;

        name = expr_to_var_sym(expr, &sym);
        if (!name || !sym)
                goto free;
        remove_from_equiv(name, sym);
free:
        free_string(name);
}

void set_related(struct smatch_state *estate, struct related_list *rlist)
{
        if (!estate_related(estate) && !rlist)
                return;
        get_dinfo(estate)->related = rlist;
}

/*
 * set_equiv() is only used for assignments where we set one variable
 * equal to the other.  a = b;.  It's not used for if conditions where
 * a == b.
 */
void set_equiv(struct expression *left, struct expression *right)
{
        struct sm_state *right_sm, *left_sm, *other_sm;
        struct relation *rel;
        char *left_name;
        struct symbol *left_sym;
        struct related_list *rlist;
        char *other_name;
        struct symbol *other_sym;

        left_name = expr_to_var_sym(left, &left_sym);
        if (!left_name || !left_sym)
                goto free;

        other_name = get_other_name_sym(left_name, left_sym, &other_sym);

        right_sm = get_sm_state_expr(SMATCH_EXTRA, right);
        if (!right_sm) {
                struct range_list *rl;

                if (!get_implied_rl(right, &rl))
                        rl = alloc_whole_rl(get_type(right));
                right_sm = set_state_expr(SMATCH_EXTRA, right, alloc_estate_rl(rl));
        }
        if (!right_sm)
                goto free;

        /* This block is because we want to preserve the implications. */
        left_sm = clone_sm(right_sm);
        left_sm->name = alloc_string(left_name);
        left_sm->sym = left_sym;
        left_sm->state = clone_estate_cast(get_type(left), right_sm->state);
        /* FIXME: The expression we're passing is wrong */
        set_extra_mod_helper(left_name, left_sym, left, left_sm->state);
        __set_sm(left_sm);

        if (other_name && other_sym) {
                other_sm = clone_sm(right_sm);
                other_sm->name = alloc_string(other_name);
                other_sm->sym = other_sym;
                other_sm->state = clone_estate_cast(get_type(left), left_sm->state);
                set_extra_mod_helper(other_name, other_sym, NULL, other_sm->state);
                __set_sm(other_sm);
        }

        rlist = clone_related_list(estate_related(right_sm->state));
        add_related(&rlist, right_sm->name, right_sm->sym);
        add_related(&rlist, left_name, left_sym);
        if (other_name && other_sym)
                add_related(&rlist, other_name, other_sym);

        FOR_EACH_PTR(rlist, rel) {
                struct sm_state *old_sm, *new_sm;

                old_sm = get_sm_state(SMATCH_EXTRA, rel->name, rel->sym);
                if (!old_sm)  /* shouldn't happen */
                        continue;
                new_sm = clone_sm(old_sm);
                new_sm->state = clone_estate(old_sm->state);
                get_dinfo(new_sm->state)->related = rlist;
                __set_sm(new_sm);
        } END_FOR_EACH_PTR(rel);
free:
        free_string(left_name);
}

void set_equiv_state_expr(int id, struct expression *expr, struct smatch_state *state)
{
        struct relation *rel;
        struct smatch_state *estate;

        estate = get_state_expr(SMATCH_EXTRA, expr);

        if (!estate)
                return;

        FOR_EACH_PTR(get_dinfo(estate)->related, rel) {
                set_state(id, rel->name, rel->sym, state);
        } END_FOR_EACH_PTR(rel);
}