root/usr/src/tools/smatch/src/check_implicit_dependencies.c
#include "smatch.h"
#include "smatch_slist.h"

static int my_id;

/* If set, we ignore struct type symbols as implicit dependencies */
static int ignore_structs;

static struct symbol *cur_syscall;
/* note: cannot track return type and remove from implicit dependencies,
 * because every syscall returns a long, and we don't have a good way to know
 * whether or not this is a resource. The only example I can think of is open
 * returning a filedescriptor, so in the implicit dep parsing, we will just
 * blacklist struct fd --> file
 */
static struct symbol *cur_return_type;
static char *syscall_name;

static struct tracker_list *read_list;  // what fields does syscall branch on?
static struct tracker_list *write_list; // what fields does syscall modify?
static struct tracker_list *arg_list;   // what struct arguments does the syscall take?
static struct tracker_list *parsed_syscalls; // syscalls we have already checked

static inline void prefix(void)
{
        printf("%s:%d %s() ", get_filename(), get_lineno(), get_function());
}

static void match_syscall_definition(struct symbol *sym)
{
        struct symbol *arg;
        struct tracker *tracker;
        char *macro;
        char *name;
        int is_syscall = 0;

        macro = get_macro_name(sym->pos);
        if (macro &&
            (strncmp("SYSCALL_DEFINE", macro, strlen("SYSCALL_DEFINE")) == 0 ||
             strncmp("COMPAT_SYSCALL_DEFINE", macro, strlen("COMPAT_SYSCALL_DEFINE")) == 0))
                is_syscall = 1;

        name = get_function();

        if (name && strncmp(name, "sys_", 4) == 0)
                is_syscall = 1;

        if (name && strncmp(name, "compat_sys_", 11) == 0)
                is_syscall = 1;

        if (!is_syscall)
                return;

        FOR_EACH_PTR(parsed_syscalls, tracker) {
                if (tracker->sym == sym) // don't re-parse
                        return;
        } END_FOR_EACH_PTR(tracker);

        syscall_name = name;
        cur_syscall = sym;

        cur_return_type = cur_func_return_type();
        if (cur_return_type && cur_return_type->ident)
                sm_msg("return type: %s\n", cur_return_type->ident->name);


        FOR_EACH_PTR(sym->ctype.base_type->arguments, arg) {
                // set_state(my_id, arg->ident->name, arg, &user_data_set);
                sm_msg("=======check_impl: arguments for call %s=========\n", syscall_name);
                if (arg->type == SYM_STRUCT)
                        arg = get_real_base_type(arg);
                if (cur_return_type && cur_return_type->ident)
                        sm_msg("arg type: %s\n", cur_return_type->ident->name);
                // add_tracker(&arg_list, my_id, member, arg);
                sm_msg("=================================\n");
        } END_FOR_EACH_PTR(arg);
}

static void print_read_list(void)
{
    struct tracker *tracker;
    int i = 0;

    FOR_EACH_PTR(read_list, tracker) {
            if (i == 0)
                    sm_printf("%s read_list: [", syscall_name);
            sm_printf("%s, ", tracker->name);
            i++;
    } END_FOR_EACH_PTR(tracker);

    if (i > 0)
            sm_printf("]\n");
}

static void print_write_list(void)
{
        struct tracker *tracker;
        int i = 0;

        FOR_EACH_PTR(write_list, tracker) {
                if (i == 0)
                        sm_printf("%s write_list: [", syscall_name);
                sm_printf("%s, ", tracker->name);
                i++;
        } END_FOR_EACH_PTR(tracker);

        if (i > 0)
                sm_printf("]\n");
}

static void print_arg_list(void)
{
        struct tracker *tracker;
        int i = 0;

        FOR_EACH_PTR(write_list, tracker) {
                if (i == 0)
                        sm_printf("%s arg_list: [", syscall_name);
                sm_printf("%s, ", tracker->name);
                i++;
        } END_FOR_EACH_PTR(tracker);

        if (i > 0)
                sm_printf("]\n");
}

static void match_after_syscall(struct symbol *sym)
{
        if (!cur_syscall || sym != cur_syscall)
                return;
        // printf("\n"); prefix();
        // printf("exiting scope of syscall %s\n", get_function());
        // printf("-------------------------\n");
        print_read_list();
        print_write_list();
        print_arg_list();
        free_trackers_and_list(&read_list);
        free_trackers_and_list(&write_list);
        free_trackers_and_list(&arg_list);
        add_tracker(&parsed_syscalls, my_id, syscall_name, sym);
        cur_syscall = NULL;
        cur_return_type = NULL;
        syscall_name = NULL;
}

static void print_read_member_type(struct expression *expr)
{
        char *member;
        struct symbol *sym;
        struct symbol *member_sym;

        member = get_member_name(expr);
        if (!member)
                return;

        sym = get_type(expr->deref);
        member_sym = get_type(expr);

        if (member_sym->type == SYM_PTR)
                member_sym = get_real_base_type(member_sym);

        /*
        if (member_sym->type == SYM_STRUCT)
                printf("found struct type %s\n", member);
        else
                printf("found non-struct type %s with enum value%d\n", member, member_sym->type);
        */

        if (ignore_structs && member_sym->type == SYM_STRUCT) {
                // printf("ignoring %s\n", member);
                return;
        }

        add_tracker(&read_list, my_id, member, sym);
        // sm_msg("info: uses %s", member);
        // prefix();
        // printf("info: uses %s\n", member);
        free_string(member);
}

static void print_write_member_type(struct expression *expr)
{
        char *member;
        struct symbol *sym;
        struct symbol *member_sym;

        member = get_member_name(expr);
        if (!member)
                return;

        sym = get_type(expr->deref);
        member_sym = get_type(expr);

        if (member_sym->type == SYM_PTR)
                member_sym = get_real_base_type(member_sym);

        /*
        if (member_sym->type == SYM_STRUCT)
                printf("found struct type %s\n", member);
        else
                printf("found non-struct type %s with enum value%d\n", member, member_sym->type);
        */

        if (ignore_structs && member_sym->type == SYM_STRUCT) {
                // printf("ignoring %s\n", member);
                return;
        }

        add_tracker(&write_list, my_id, member, sym);
        free_string(member);
}

static void match_condition(struct expression *expr)
{
        struct expression *arg;

        if (!cur_syscall)
                return;

        // prefix(); printf("-- condition found\n");

        if (expr->type == EXPR_COMPARE ||
            expr->type == EXPR_BINOP ||
            expr->type == EXPR_LOGICAL ||
            expr->type == EXPR_ASSIGNMENT ||
            expr->type == EXPR_COMMA) {
                match_condition(expr->left);
                match_condition(expr->right);
                return;
        }

        if (expr->type == EXPR_CALL) {
                FOR_EACH_PTR(expr->args, arg) {
                        // if we find deref in conditional call,
                        // mark it as a read dependency
                        print_read_member_type(arg);
                } END_FOR_EACH_PTR(arg);
                return;
        }

        print_read_member_type(expr);
}


/* when we are parsing an inline function and can no longer nest,
 * assume that all struct fields passed to nested inline functions
 * are read dependencies
 */
static void match_call_info(struct expression *expr)
{
        struct expression *arg;
        int i;

        if (!__inline_fn || !cur_syscall)
                return;

        // prefix(); printf("fn: %s\n", expr->fn->symbol->ident->name);

        i = 0;
        FOR_EACH_PTR(expr->args, arg) {
                /*
                   if (arg->type == EXPR_DEREF)
                   printf("arg %d is deref\n", i);
                 */
                print_read_member_type(arg);
                i++;
        } END_FOR_EACH_PTR(arg);
}

static void match_assign_value(struct expression *expr)
{
        if (!cur_syscall)
                return;
        print_write_member_type(expr->left);
}

static void unop_expr(struct expression *expr)
{
        if (!cur_syscall)
                return;

        if (expr->op == SPECIAL_ADD_ASSIGN || expr->op == SPECIAL_INCREMENT ||
            expr->op == SPECIAL_SUB_ASSIGN || expr->op == SPECIAL_DECREMENT ||
            expr->op == SPECIAL_MUL_ASSIGN || expr->op == SPECIAL_DIV_ASSIGN ||
            expr->op == SPECIAL_MOD_ASSIGN || expr->op == SPECIAL_AND_ASSIGN ||
            expr->op == SPECIAL_OR_ASSIGN || expr->op == SPECIAL_XOR_ASSIGN ||
            expr->op == SPECIAL_SHL_ASSIGN || expr->op == SPECIAL_SHR_ASSIGN)
                print_write_member_type(strip_expr(expr->unop));
}

void check_implicit_dependencies(int id)
{
        my_id = id;
        ignore_structs = 0;

        if (option_project != PROJ_KERNEL)
                return;
        if (!option_info)
                return;

        add_hook(&match_syscall_definition, AFTER_DEF_HOOK);
        add_hook(&match_after_syscall, AFTER_FUNC_HOOK);
        add_hook(&match_condition, CONDITION_HOOK);
        add_hook(&match_call_info, FUNCTION_CALL_HOOK);

        /* hooks to track written fields */
        add_hook(&match_assign_value, ASSIGNMENT_HOOK_AFTER);
        add_hook(&unop_expr, OP_HOOK);
}