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

/*
 * Track how functions are saved as various struct members or passed as
 * parameters.
 *
 */

#include "scope.h"
#include "smatch.h"
#include "smatch_slist.h"

static int my_id;

static char *get_from__symbol_get(struct expression *expr)
{
        struct expression *arg;

        /*
         * typeof(&dib0070_attach) __a =
         * ((((typeof(&dib0070_attach)) (__symbol_get("dib0070_attach")))) ?:
         *  (__request_module(true, "symbol:" "dib0070_attach"), (((typeof(&dib0070_attach))(__symbol_get("dib0070_attach"))))));
         */

        expr = strip_expr(expr);

        if (expr->type != EXPR_CALL)
                return NULL;
        if (!sym_name_is("__symbol_get", expr->fn))
                return NULL;
        arg = get_argument_from_call_expr(expr->args, 0);
        if (!arg || arg->type != EXPR_STRING)
                return NULL;

        return alloc_string(arg->string->data);
}

static int xxx_is_array(struct expression *expr)
{
        struct symbol *type;

        expr = strip_expr(expr);
        if (!expr)
                return 0;

        if (expr->type == EXPR_PREOP && expr->op == '*') {
                expr = strip_expr(expr->unop);
                if (!expr)
                        return 0;
                if (expr->type == EXPR_BINOP && expr->op == '+')
                        return 1;
        }

        if (expr->type != EXPR_BINOP || expr->op != '+')
                return 0;

        type = get_type(expr->left);
        if (!type)
                return 0;
        if (type->type != SYM_ARRAY && type->type != SYM_PTR)
                return 0;

        return 1;
}

static struct expression *xxx_get_array_base(struct expression *expr)
{
        if (!xxx_is_array(expr))
                return NULL;
        expr = strip_expr(expr);
        if (expr->type == EXPR_PREOP && expr->op == '*')
                expr = strip_expr(expr->unop);
        if (expr->type != EXPR_BINOP || expr->op != '+')
                return NULL;
        return strip_parens(expr->left);
}

static char *get_array_ptr(struct expression *expr)
{
        struct expression *array;
        struct symbol *type;
        char *name;
        char buf[256];

        array = xxx_get_array_base(expr);

        if (array) {
                name = get_member_name(array);
                if (name)
                        return name;
        }

        /* FIXME:  is_array() should probably be is_array_element() */
        type = get_type(expr);
        if (!array && type && type->type == SYM_ARRAY)
                array = expr;
        if (array) {
                name = expr_to_var(array);
                if (!name)
                        return NULL;
                snprintf(buf, sizeof(buf), "%s[]", name);
                return alloc_string(buf);
        }

        expr = get_assigned_expr(expr);
        array = xxx_get_array_base(expr);
        if (!array)
                return NULL;
        name = expr_to_var(array);
        if (!name)
                return NULL;
        snprintf(buf, sizeof(buf), "%s[]", name);
        free_string(name);
        return alloc_string(buf);
}

static int is_local_symbol(struct symbol *sym)
{
        if (!sym ||
            !(sym->ctype.modifiers & MOD_TOPLEVEL))
                return 1;
        return 0;
}

static char *ptr_prefix(struct symbol *sym)
{
        static char buf[128];


        if (is_local_symbol(sym))
                snprintf(buf, sizeof(buf), "%s ptr", get_function());
        else if (sym && toplevel(sym->scope))
                snprintf(buf, sizeof(buf), "%s ptr", get_base_file());
        else
                snprintf(buf, sizeof(buf), "ptr");

        return buf;
}

char *get_returned_ptr(struct expression *expr)
{
        struct symbol *type;
        char *name;
        char buf[256];

        if (expr->type != EXPR_CALL)
                return NULL;
        if (!expr->fn || expr->fn->type != EXPR_SYMBOL)
                return NULL;

        type = get_type(expr);
        if (type && type->type == SYM_PTR)
                type = get_real_base_type(type);
        if (!type || type->type != SYM_FN)
                return NULL;

        name = expr_to_var(expr->fn);
        if (!name)
                return NULL;
        snprintf(buf, sizeof(buf), "r %s()", name);
        free_string(name);
        return alloc_string(buf);
}

char *get_fnptr_name(struct expression *expr)
{
        char *name;

        if (expr_is_zero(expr))
                return NULL;

        expr = strip_expr(expr);

        /* (*ptrs[0])(a, b, c) is the same as ptrs[0](a, b, c); */
        if (expr->type == EXPR_PREOP && expr->op == '*')
                expr = strip_expr(expr->unop);

        name = get_from__symbol_get(expr);
        if (name)
                return name;

        name = get_array_ptr(expr);
        if (name)
                return name;

        name = get_returned_ptr(expr);
        if (name)
                return name;

        name = get_member_name(expr);
        if (name)
                return name;

        if (expr->type == EXPR_SYMBOL) {
                int param;
                char buf[256];
                struct symbol *sym;
                struct symbol *type;

                param = get_param_num_from_sym(expr->symbol);
                if (param >= 0) {
                        snprintf(buf, sizeof(buf), "%s param %d", get_function(), param);
                        return alloc_string(buf);
                }

                name = expr_to_var_sym(expr, &sym);
                if (!name)
                        return NULL;
                type = get_type(expr);
                if (type && type->type == SYM_PTR) {
                        snprintf(buf, sizeof(buf), "%s %s", ptr_prefix(sym), name);
                        free_string(name);
                        return alloc_string(buf);
                }
                return name;
        }
        return expr_to_var(expr);
}

static void match_passes_function_pointer(struct expression *expr)
{
        struct expression *arg, *tmp;
        struct symbol *type;
        char *called_name;
        char *fn_name;
        char ptr_name[256];
        int i;


        i = -1;
        FOR_EACH_PTR(expr->args, arg) {
                i++;

                tmp = strip_expr(arg);
                if (tmp->type == EXPR_PREOP && tmp->op == '&')
                        tmp = strip_expr(tmp->unop);

                type = get_type(tmp);
                if (type && type->type == SYM_PTR)
                        type = get_real_base_type(type);
                if (!type || type->type != SYM_FN)
                        continue;

                called_name = expr_to_var(expr->fn);
                if (!called_name)
                        return;
                fn_name = get_fnptr_name(tmp);
                if (!fn_name)
                        goto free;

                snprintf(ptr_name, sizeof(ptr_name), "%s param %d", called_name, i);
                sql_insert_function_ptr(fn_name, ptr_name);
free:
                free_string(fn_name);
                free_string(called_name);
        } END_FOR_EACH_PTR(arg);

}

static int get_row_count(void *_row_count, int argc, char **argv, char **azColName)
{
        int *row_count = _row_count;

        *row_count = 0;
        if (argc != 1)
                return 0;
        *row_count = atoi(argv[0]);
        return 0;
}

static int can_hold_function_ptr(struct expression *expr)
{
        struct symbol *type;

        type = get_type(expr);
        if (!type)
                return 0;
        if (type->type == SYM_PTR || type->type == SYM_ARRAY) {
                type = get_real_base_type(type);
                if (!type)
                        return 0;
        }
        /* pointer to a pointer */
        if (type->type == SYM_PTR || type->type == SYM_ARRAY) {
                type = get_real_base_type(type);
                if (!type)
                        return 0;
        }
        if (type->type == SYM_FN)
                return 1;
        if (type == &ulong_ctype && expr->type == EXPR_DEREF)
                return 1;
        if (type == &void_ctype)
                return 1;
        return 0;
}

static void match_function_assign(struct expression *expr)
{
        struct expression *right;
        struct symbol *type;
        char *fn_name;
        char *ptr_name;

        if (__in_fake_assign)
                return;

        right = strip_expr(expr->right);
        if (right->type == EXPR_PREOP && right->op == '&')
                right = strip_expr(right->unop);

        if (right->type != EXPR_SYMBOL &&
            right->type != EXPR_DEREF &&
            right->type != EXPR_CALL)
                return;

        if (!can_hold_function_ptr(right) ||
            !can_hold_function_ptr(expr->left))
                return;

        fn_name = get_fnptr_name(right);
        ptr_name = get_fnptr_name(expr->left);
        if (!fn_name || !ptr_name)
                goto free;
        if (strcmp(fn_name, ptr_name) == 0)
                goto free;


        type = get_type(right);
        if (!type)
                return;
        if (type->type == SYM_PTR || type->type == SYM_ARRAY) {
                type = get_real_base_type(type);
                if (!type)
                        return;
        }
        if (type->type != SYM_FN) {
                int count = 0;

                /* look it up in function_ptr */
                run_sql(get_row_count, &count,
                        "select count(*) from function_ptr where ptr = '%s'",
                        fn_name);
                if (count == 0)
                        goto free;
        }

        sql_insert_function_ptr(fn_name, ptr_name);
free:
        free_string(fn_name);
        free_string(ptr_name);
}

static void match_returns_function_pointer(struct expression *expr)
{
        struct symbol *type;
        char *fn_name;
        char ptr_name[256];

        if (__inline_fn)
                return;

        type = get_real_base_type(cur_func_sym);
        if (!type || type->type != SYM_FN)
                return;
        type = get_real_base_type(type);
        if (!type || type->type != SYM_PTR)
                return;
        type = get_real_base_type(type);
        if (!type || type->type != SYM_FN)
                return;

        if (expr->type == EXPR_PREOP && expr->op == '&')
                expr = strip_expr(expr->unop);

        fn_name = get_fnptr_name(expr);
        if (!fn_name)
                return;
        snprintf(ptr_name, sizeof(ptr_name), "r %s()", get_function());
        sql_insert_function_ptr(fn_name, ptr_name);
}

static void print_initializer_list(struct expression_list *expr_list,
                struct symbol *struct_type)
{
        struct expression *expr;
        struct symbol *base_type;
        char struct_name[256];

        FOR_EACH_PTR(expr_list, expr) {
                if (expr->type == EXPR_INDEX && expr->idx_expression && expr->idx_expression->type == EXPR_INITIALIZER) {
                        print_initializer_list(expr->idx_expression->expr_list, struct_type);
                        continue;
                }
                if (expr->type != EXPR_IDENTIFIER)
                        continue;
                if (!expr->expr_ident)
                        continue;
                if (!expr->ident_expression ||
                    expr->ident_expression->type != EXPR_SYMBOL ||
                    !expr->ident_expression->symbol_name)
                        continue;
                base_type = get_type(expr->ident_expression);
                if (!base_type || base_type->type != SYM_FN)
                        continue;
                snprintf(struct_name, sizeof(struct_name), "(struct %s)->%s",
                         struct_type->ident->name, expr->expr_ident->name);
                sql_insert_function_ptr(expr->ident_expression->symbol_name->name,
                                        struct_name);
        } END_FOR_EACH_PTR(expr);
}

static void global_variable(struct symbol *sym)
{
        struct symbol *struct_type;

        if (!sym->ident)
                return;
        if (!sym->initializer || sym->initializer->type != EXPR_INITIALIZER)
                return;
        struct_type = get_base_type(sym);
        if (!struct_type)
                return;
        if (struct_type->type == SYM_ARRAY) {
                struct_type = get_base_type(struct_type);
                if (!struct_type)
                        return;
        }
        if (struct_type->type != SYM_STRUCT || !struct_type->ident)
                return;
        print_initializer_list(sym->initializer->expr_list, struct_type);
}

void register_function_ptrs(int id)
{
        my_id = id;

        if (!option_info)
                return;

        add_hook(&global_variable, BASE_HOOK);
        add_hook(&global_variable, DECLARATION_HOOK);
        add_hook(&match_passes_function_pointer, FUNCTION_CALL_HOOK);
        add_hook(&match_returns_function_pointer, RETURN_HOOK);
        add_hook(&match_function_assign, ASSIGNMENT_HOOK);
        add_hook(&match_function_assign, GLOBAL_ASSIGNMENT_HOOK);
}