root/usr/src/tools/smatch/src/dissect.c
/*
 * sparse/dissect.c
 *
 * Started by Oleg Nesterov <oleg@redhat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

#include "dissect.h"

#define U_VOID   0x00
#define U_SELF  ((1 << U_SHIFT) - 1)
#define U_MASK  (U_R_VAL | U_W_VAL | U_R_AOF)

#define DO_LIST(l__, p__, expr__)               \
        do {                                    \
                typeof(l__->list[0]) p__;       \
                FOR_EACH_PTR(l__, p__)          \
                        expr__;                 \
                END_FOR_EACH_PTR(p__);          \
        } while (0)

#define DO_2_LIST(l1__,l2__, p1__,p2__, expr__) \
        do {                                    \
                typeof(l1__->list[0]) p1__;     \
                typeof(l2__->list[0]) p2__;     \
                PREPARE_PTR_LIST(l1__, p1__);   \
                FOR_EACH_PTR(l2__, p2__)        \
                        expr__;                 \
                        NEXT_PTR_LIST(p1__);    \
                END_FOR_EACH_PTR(p2__);         \
                FINISH_PTR_LIST(p1__);          \
        } while (0)


typedef unsigned usage_t;

static struct reporter *reporter;
static struct symbol *return_type;

static void do_sym_list(struct symbol_list *list);

static struct symbol
        *base_type(struct symbol *sym),
        *do_initializer(struct symbol *type, struct expression *expr),
        *do_expression(usage_t mode, struct expression *expr),
        *do_statement(usage_t mode, struct statement *stmt);

static inline int is_ptr(struct symbol *type)
{
        return type->type == SYM_PTR || type->type == SYM_ARRAY;
}

static inline usage_t u_rval(usage_t mode)
{
        return mode & (U_R_VAL | (U_MASK << U_SHIFT))
                ? U_R_VAL : 0;
}

static inline usage_t u_addr(usage_t mode)
{
        return mode = mode & U_MASK
                ? U_R_AOF | (mode & U_W_AOF) : 0;
}

static usage_t u_lval(struct symbol *type)
{
        int wptr = is_ptr(type) && !(type->ctype.modifiers & MOD_CONST);
        return wptr || type == &bad_ctype
                ? U_W_AOF | U_R_VAL : U_R_VAL;
}

static usage_t fix_mode(struct symbol *type, usage_t mode)
{
        mode &= (U_SELF | (U_SELF << U_SHIFT));

        switch (type->type) {
                case SYM_BASETYPE:
                        if (!type->ctype.base_type)
                                break;
                case SYM_ENUM:
                case SYM_BITFIELD:
                        if (mode & U_MASK)
                                mode &= U_SELF;
                default:

                break; case SYM_FN:
                        if (mode & U_R_VAL)
                                mode |= U_R_AOF;
                        mode &= ~(U_R_VAL | U_W_AOF);

                break; case SYM_ARRAY:
                        if (mode & (U_MASK << U_SHIFT))
                                mode >>= U_SHIFT;
                        else if (mode != U_W_VAL)
                                mode = u_addr(mode);
        }

        if (!(mode & U_R_AOF))
                mode &= ~U_W_AOF;

        return mode;
}

static inline struct symbol *no_member(struct ident *name)
{
        static struct symbol sym = {
                .type = SYM_BAD,
        };

        sym.ctype.base_type = &bad_ctype;
        sym.ident = name;

        return &sym;
}

static struct symbol *report_member(usage_t mode, struct position *pos,
                                        struct symbol *type, struct symbol *mem)
{
        struct symbol *ret = mem->ctype.base_type;

        if (reporter->r_member)
                reporter->r_member(fix_mode(ret, mode), pos, type, mem);

        return ret;
}

static void report_implicit(usage_t mode, struct position *pos, struct symbol *type)
{
        if (type->type != SYM_STRUCT && type->type != SYM_UNION)
                return;

        if (!reporter->r_member)
                return;

        if (type->ident != NULL)
                reporter->r_member(mode, pos, type, NULL);

        DO_LIST(type->symbol_list, mem,
                report_implicit(mode, pos, base_type(mem)));
}

static inline struct symbol *expr_symbol(struct expression *expr)
{
        struct symbol *sym = expr->symbol;

        if (!sym) {
                sym = lookup_symbol(expr->symbol_name, NS_SYMBOL);

                if (!sym) {
                        sym = alloc_symbol(expr->pos, SYM_BAD);
                        bind_symbol(sym, expr->symbol_name, NS_SYMBOL);
                        sym->ctype.modifiers = MOD_EXTERN;
                }
        }

        if (!sym->ctype.base_type)
                sym->ctype.base_type = &bad_ctype;

        return sym;
}

static struct symbol *report_symbol(usage_t mode, struct expression *expr)
{
        struct symbol *sym = expr_symbol(expr);
        struct symbol *ret = base_type(sym);

        if (0 && ret->type == SYM_ENUM)
                return report_member(mode, &expr->pos, ret, expr->symbol);

        if (reporter->r_symbol)
                reporter->r_symbol(fix_mode(ret, mode), &expr->pos, sym);

        return ret;
}

static inline struct ident *mk_name(struct ident *root, struct ident *node)
{
        char name[256];

        snprintf(name, sizeof(name), "%.*s:%.*s",
                        root ? root->len : 0, root ? root->name : "",
                        node ? node->len : 0, node ? node->name : "");

        return built_in_ident(name);
}

static void examine_sym_node(struct symbol *node, struct ident *root)
{
        struct symbol *base;
        struct ident *name;

        if (node->examined)
                return;

        node->examined = 1;
        name = node->ident;

        while ((base = node->ctype.base_type) != NULL)
                switch (base->type) {
                case SYM_TYPEOF:
                        node->ctype.base_type =
                                do_expression(U_VOID, base->initializer);
                        break;

                case SYM_ARRAY:
                        do_expression(U_R_VAL, base->array_size);
                case SYM_PTR: case SYM_FN:
                        node = base;
                        break;

                case SYM_STRUCT: case SYM_UNION: //case SYM_ENUM:
                        if (base->evaluated)
                                return;
                        if (!base->symbol_list)
                                return;
                        base->evaluated = 1;

                        if (!base->ident && name)
                                base->ident = mk_name(root, name);
                        if (base->ident && reporter->r_symdef)
                                reporter->r_symdef(base);
                        DO_LIST(base->symbol_list, mem,
                                examine_sym_node(mem, base->ident ?: root));
                default:
                        return;
                }
}

static struct symbol *base_type(struct symbol *sym)
{
        if (!sym)
                return &bad_ctype;

        if (sym->type == SYM_NODE)
                examine_sym_node(sym, NULL);

        return sym->ctype.base_type     // builtin_fn_type
                ?: &bad_ctype;
}

static struct symbol *__lookup_member(struct symbol *type, struct ident *name, int *p_addr)
{
        struct symbol *node;
        int addr = 0;

        FOR_EACH_PTR(type->symbol_list, node)
                if (!name) {
                        if (addr == *p_addr)
                                return node;
                }
                else if (node->ident == NULL) {
                        node = __lookup_member(node->ctype.base_type, name, NULL);
                        if (node)
                                goto found;
                }
                else if (node->ident == name) {
found:
                        if (p_addr)
                                *p_addr = addr;
                        return node;
                }
                addr++;
        END_FOR_EACH_PTR(node);

        return NULL;
}

static struct symbol *lookup_member(struct symbol *type, struct ident *name, int *addr)
{
        return __lookup_member(type, name, addr)
                ?: no_member(name);
}

static struct expression *peek_preop(struct expression *expr, int op)
{
        do {
                if (expr->type != EXPR_PREOP)
                        break;
                if (expr->op == op)
                        return expr->unop;
                if (expr->op == '(')
                        expr = expr->unop;
                else
                        break;
        } while (expr);

        return NULL;
}

static struct symbol *do_expression(usage_t mode, struct expression *expr)
{
        struct symbol *ret = &int_ctype;

again:
        if (expr) switch (expr->type) {
        default:
                warning(expr->pos, "bad expr->type: %d", expr->type);

        case EXPR_TYPE:         // [struct T]; Why ???
        case EXPR_VALUE:
        case EXPR_FVALUE:

        break; case EXPR_LABEL:
                ret = &label_ctype;

        break; case EXPR_STRING:
                ret = &string_ctype;

        break; case EXPR_STATEMENT:
                ret = do_statement(mode, expr->statement);

        break; case EXPR_SIZEOF: case EXPR_ALIGNOF: case EXPR_PTRSIZEOF:
                do_expression(U_VOID, expr->cast_expression);

        break; case EXPR_COMMA:
                do_expression(U_VOID, expr->left);
                ret = do_expression(mode, expr->right);

        break; case EXPR_CAST: case EXPR_FORCE_CAST: //case EXPR_IMPLIED_CAST:
                ret = base_type(expr->cast_type);
                do_initializer(ret, expr->cast_expression);

        break; case EXPR_COMPARE: case EXPR_LOGICAL:
                mode = u_rval(mode);
                do_expression(mode, expr->left);
                do_expression(mode, expr->right);

        break; case EXPR_CONDITIONAL: //case EXPR_SELECT:
                do_expression(expr->cond_true
                                        ? U_R_VAL : U_R_VAL | mode,
                                expr->conditional);
                ret = do_expression(mode, expr->cond_true);
                ret = do_expression(mode, expr->cond_false);

        break; case EXPR_CALL:
                ret = do_expression(U_R_PTR, expr->fn);
                if (is_ptr(ret))
                        ret = ret->ctype.base_type;
                DO_2_LIST(ret->arguments, expr->args, arg, val,
                        do_expression(u_lval(base_type(arg)), val));
                ret = ret->type == SYM_FN ? base_type(ret)
                        : &bad_ctype;

        break; case EXPR_ASSIGNMENT:
                mode |= U_W_VAL | U_R_VAL;
                if (expr->op == '=')
                        mode &= ~U_R_VAL;
                ret = do_expression(mode, expr->left);
                report_implicit(mode, &expr->pos, ret);
                mode = expr->op == '='
                        ? u_lval(ret) : U_R_VAL;
                do_expression(mode, expr->right);

        break; case EXPR_BINOP: {
                struct symbol *l, *r;
                mode |= u_rval(mode);
                l = do_expression(mode, expr->left);
                r = do_expression(mode, expr->right);
                if (expr->op != '+' && expr->op != '-')
                        ;
                else if (!is_ptr_type(r))
                        ret = l;
                else if (!is_ptr_type(l))
                        ret = r;
        }

        break; case EXPR_PREOP: case EXPR_POSTOP: {
                struct expression *unop = expr->unop;

                switch (expr->op) {
                case SPECIAL_INCREMENT:
                case SPECIAL_DECREMENT:
                        mode |= U_W_VAL | U_R_VAL;
                default:
                        mode |= u_rval(mode);
                case '(':
                        ret = do_expression(mode, unop);

                break; case '&':
                        if ((expr = peek_preop(unop, '*')))
                                goto again;
                        ret = alloc_symbol(unop->pos, SYM_PTR);
                        ret->ctype.base_type =
                                do_expression(u_addr(mode), unop);

                break; case '*':
                        if ((expr = peek_preop(unop, '&')))
                                goto again;
                        if (mode & (U_MASK << U_SHIFT))
                                mode |= U_R_VAL;
                        mode <<= U_SHIFT;
                        if (mode & (U_R_AOF << U_SHIFT))
                                mode |= U_R_VAL;
                        if (mode & (U_W_VAL << U_SHIFT))
                                mode |= U_W_AOF;
                        ret = do_expression(mode, unop);
                        ret = is_ptr(ret) ? base_type(ret)
                                : &bad_ctype;
                }
        }

        break; case EXPR_DEREF: {
                struct symbol *p_type;
                usage_t p_mode;

                p_mode = mode & U_SELF;
                if (!(mode & U_MASK) && (mode & (U_MASK << U_SHIFT)))
                        p_mode = U_R_VAL;
                p_type = do_expression(p_mode, expr->deref);

                ret = report_member(mode, &expr->pos, p_type,
                        lookup_member(p_type, expr->member, NULL));
        }

        break; case EXPR_OFFSETOF: {
                struct symbol *in = base_type(expr->in);

                do {
                        if (expr->op == '.') {
                                in = report_member(U_VOID, &expr->pos, in,
                                        lookup_member(in, expr->ident, NULL));
                        } else {
                                do_expression(U_R_VAL, expr->index);
                                in = in->ctype.base_type;
                        }
                } while ((expr = expr->down));
        }

        break; case EXPR_SYMBOL:
                ret = report_symbol(mode, expr);
        }

        return ret;
}

static void do_asm_xputs(usage_t mode, struct expression_list *xputs)
{
        int nr = 0;

        DO_LIST(xputs, expr,
                if (++nr % 3 == 0)
                        do_expression(U_W_AOF | mode, expr));
}

static struct symbol *do_statement(usage_t mode, struct statement *stmt)
{
        struct symbol *ret = &void_ctype;

        if (stmt) switch (stmt->type) {
        default:
                warning(stmt->pos, "bad stmt->type: %d", stmt->type);

        case STMT_NONE:
        case STMT_RANGE:
        case STMT_CONTEXT:

        break; case STMT_DECLARATION:
                do_sym_list(stmt->declaration);

        break; case STMT_EXPRESSION:
                ret = do_expression(mode, stmt->expression);

        break; case STMT_RETURN:
                do_expression(u_lval(return_type), stmt->expression);

        break; case STMT_ASM:
                do_expression(U_R_VAL, stmt->asm_string);
                do_asm_xputs(U_W_VAL, stmt->asm_outputs);
                do_asm_xputs(U_R_VAL, stmt->asm_inputs);

        break; case STMT_COMPOUND: {
                int count;

                count = statement_list_size(stmt->stmts);
                DO_LIST(stmt->stmts, st,
                        ret = do_statement(--count ? U_VOID : mode, st));
        }

        break; case STMT_ITERATOR:
                do_sym_list(stmt->iterator_syms);
                do_statement(U_VOID, stmt->iterator_pre_statement);
                do_expression(U_R_VAL, stmt->iterator_pre_condition);
                do_statement(U_VOID, stmt->iterator_post_statement);
                do_statement(U_VOID, stmt->iterator_statement);
                do_expression(U_R_VAL, stmt->iterator_post_condition);

        break; case STMT_IF:
                do_expression(U_R_VAL, stmt->if_conditional);
                do_statement(U_VOID, stmt->if_true);
                do_statement(U_VOID, stmt->if_false);

        break; case STMT_SWITCH:
                do_expression(U_R_VAL, stmt->switch_expression);
                do_statement(U_VOID, stmt->switch_statement);

        break; case STMT_CASE:
                do_expression(U_R_VAL, stmt->case_expression);
                do_expression(U_R_VAL, stmt->case_to);
                do_statement(U_VOID, stmt->case_statement);

        break; case STMT_GOTO:
                do_expression(U_R_PTR, stmt->goto_expression);

        break; case STMT_LABEL:
                do_statement(mode, stmt->label_statement);

        }

        return ret;
}

static struct symbol *do_initializer(struct symbol *type, struct expression *expr)
{
        struct symbol *m_type;
        struct expression *m_expr;
        int m_addr;

        if (expr) switch (expr->type) {
        default:
                do_expression(u_lval(type), expr);

        break; case EXPR_INDEX:
                do_initializer(base_type(type), expr->idx_expression);

        break; case EXPR_INITIALIZER:
                m_addr = 0;
                FOR_EACH_PTR(expr->expr_list, m_expr) {
                        if (type->type == SYM_ARRAY) {
                                m_type = base_type(type);
                                if (m_expr->type == EXPR_INDEX)
                                        m_expr = m_expr->idx_expression;
                        } else {
                                int *m_atop = &m_addr;

                                m_type = type;
                                while (m_expr->type == EXPR_IDENTIFIER) {
                                        m_type = report_member(U_W_VAL, &m_expr->pos, m_type,
                                                        lookup_member(m_type, m_expr->expr_ident, m_atop));
                                        m_expr = m_expr->ident_expression;
                                        m_atop = NULL;
                                }

                                if (m_atop) {
                                        m_type = report_member(U_W_VAL, &m_expr->pos, m_type,
                                                        lookup_member(m_type, NULL, m_atop));
                                }

                                if (m_expr->type != EXPR_INITIALIZER)
                                        report_implicit(U_W_VAL, &m_expr->pos, m_type);
                        }
                        do_initializer(m_type, m_expr);
                        m_addr++;
                } END_FOR_EACH_PTR(m_expr);
        }

        return type;
}

static inline struct symbol *do_symbol(struct symbol *sym)
{
        struct symbol *type;

        type = base_type(sym);

        if (reporter->r_symdef)
                reporter->r_symdef(sym);

        switch (type->type) {
        default:
                if (!sym->initializer)
                        break;
                if (reporter->r_symbol)
                        reporter->r_symbol(U_W_VAL, &sym->pos, sym);
                do_initializer(type, sym->initializer);

        break; case SYM_FN:
                do_sym_list(type->arguments);
                return_type = base_type(type);
                do_statement(U_VOID, sym->ctype.modifiers & MOD_INLINE
                                        ? type->inline_stmt
                                        : type->stmt);
        }

        return type;
}

static void do_sym_list(struct symbol_list *list)
{
        DO_LIST(list, sym, do_symbol(sym));
}

void dissect(struct symbol_list *list, struct reporter *rep)
{
        reporter = rep;
        do_sym_list(list);
}