/*
 * Copyright (C) 2017 Oracle.  All rights reserved.
 *
 * 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
 */

/*
 * One problem that I have is that it's really hard to track how pointers are
 * passed around.  For example, it would be nice to know that the probe() and
 * remove() functions get the same pci_dev pointer.  It would be good to know
 * what pointers we're passing to the open() and close() functions.  But that
 * information gets lost in a call tree full of function pointer calls.
 *
 * I think the first step is to start naming specific pointers.  So when a
 * pointer is allocated, then it gets a tag.  So calls to kmalloc() generate a
 * tag.  But we might not use that, because there might be a better name like
 * framebuffer_alloc(). The framebuffer_alloc() is interesting because there is
 * one per driver and it's passed around to all the file operations.
 *
 * Perhaps we could make a list of functions like framebuffer_alloc() which take
 * a size and say that those are the interesting alloc functions.
 *
 * Another place where we would maybe name the pointer is when they are passed
 * to the probe().  Because that's an important pointer, since there is one
 * per driver (sort of).
 *
 * My vision is that you could take a pointer and trace it back to a global.  So
 * I'm going to track that pointer_tag - 28 bytes takes you to another pointer
 * tag.  You could follow that one back and so on.  Also when we pass a pointer
 * to a function that would be recorded as sort of a link or path or something.
 *
 */

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

#include <openssl/md5.h>

static int my_id;

mtag_t str_to_mtag(const char *str)
{
	unsigned char c[MD5_DIGEST_LENGTH];
	unsigned long long *tag = (unsigned long long *)&c;
	MD5_CTX mdContext;
	int len;

	len = strlen(str);
	MD5_Init(&mdContext);
	MD5_Update(&mdContext, str, len);
	MD5_Final(c, &mdContext);

	*tag &= ~MTAG_ALIAS_BIT;
	*tag &= ~MTAG_OFFSET_MASK;

	return *tag;
}

static int save_allocator(void *_allocator, int argc, char **argv, char **azColName)
{
	char **allocator = _allocator;

	if (*allocator) {
		if (strcmp(*allocator, argv[0]) == 0)
			return 0;
		/* should be impossible */
		free_string(*allocator);
		*allocator = alloc_string("unknown");
		return 0;
	}
	*allocator = alloc_string(argv[0]);
	return 0;
}

char *get_allocator_info_from_tag(mtag_t tag)
{
	char *allocator = NULL;

	run_sql(save_allocator, &allocator,
		"select value from mtag_info where tag = %lld and type = %d;",
		tag, ALLOCATOR);

	return allocator;
}

static char *get_allocator_info(struct expression *expr, struct smatch_state *state)
{
	sval_t sval;

	if (expr->type != EXPR_ASSIGNMENT)
		return NULL;
	if (estate_get_single_value(state, &sval))
		return get_allocator_info_from_tag(sval.value);

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

static void update_mtag_info(struct expression *expr, mtag_t tag,
			     const char *left_name, const char *tag_info,
			     struct smatch_state *state)
{
	char *allocator;

	sql_insert_mtag_about(tag, left_name, tag_info);

	allocator = get_allocator_info(expr, state);
	if (allocator)
		sql_insert_mtag_info(tag, ALLOCATOR, allocator);
}

struct smatch_state *get_mtag_return(struct expression *expr, struct smatch_state *state)
{
	struct expression *left, *right;
	char *left_name, *right_name;
	struct symbol *left_sym;
	struct range_list *rl;
	char buf[256];
	mtag_t tag;
	sval_t tag_sval;

	if (!expr || expr->type != EXPR_ASSIGNMENT || expr->op != '=')
		return NULL;
	if (!is_fresh_alloc(expr->right))
		return NULL;
	if (!rl_intersection(estate_rl(state), valid_ptr_rl))
		return NULL;

	left = strip_expr(expr->left);
	right = strip_expr(expr->right);

	left_name = expr_to_str_sym(left, &left_sym);
	if (!left_name || !left_sym)
		return NULL;
	right_name = expr_to_str(right);

	snprintf(buf, sizeof(buf), "%s %s %s %s", get_filename(), get_function(),
		 left_name, right_name);
	tag = str_to_mtag(buf);
	tag_sval.type = estate_type(state);
	tag_sval.uvalue = tag;

	rl = rl_filter(estate_rl(state), valid_ptr_rl);
	rl = clone_rl(rl);
	add_range(&rl, tag_sval, tag_sval);

	update_mtag_info(expr, tag, left_name, buf, state);

	free_string(left_name);
	free_string(right_name);

	return alloc_estate_rl(rl);
}

int get_string_mtag(struct expression *expr, mtag_t *tag)
{
	mtag_t xor;

	if (expr->type != EXPR_STRING || !expr->string)
		return 0;

	/* I was worried about collisions so I added a xor */
	xor = str_to_mtag("__smatch string");
	*tag = str_to_mtag(expr->string->data);
	*tag = *tag ^ xor;

	return 1;
}

int get_toplevel_mtag(struct symbol *sym, mtag_t *tag)
{
	char buf[256];

	if (!sym)
		return 0;

	if (!sym->ident ||
	    !(sym->ctype.modifiers & MOD_TOPLEVEL))
		return 0;

	snprintf(buf, sizeof(buf), "%s %s",
		 (sym->ctype.modifiers & MOD_STATIC) ? get_filename() : "extern",
		 sym->ident->name);
	*tag = str_to_mtag(buf);
	return 1;
}

bool get_symbol_mtag(struct symbol *sym, mtag_t *tag)
{
	char buf[256];

	if (!sym || !sym->ident)
		return false;

	if (get_toplevel_mtag(sym, tag))
		return true;

	if (get_param_num_from_sym(sym) >= 0)
		return false;

	snprintf(buf, sizeof(buf), "%s %s %s",
		 get_filename(), get_function(), sym->ident->name);
	*tag = str_to_mtag(buf);
	return true;
}

static void global_variable(struct symbol *sym)
{
	mtag_t tag;

	if (!get_toplevel_mtag(sym, &tag))
		return;

	sql_insert_mtag_about(tag,
			      sym->ident->name,
			      (sym->ctype.modifiers & MOD_STATIC) ? get_filename() : "extern");
}

static int get_array_mtag_offset(struct expression *expr, mtag_t *tag, int *offset)
{
	struct expression *array, *offset_expr;
	struct symbol *type;
	sval_t sval;
	int start_offset;

	if (!is_array(expr))
		return 0;

	array = get_array_base(expr);
	if (!array)
		return 0;
	type = get_type(array);
	if (!type || type->type != SYM_ARRAY)
		return 0;
	type = get_real_base_type(type);
	if (!type_bytes(type))
		return 0;

	if (!expr_to_mtag_offset(array, tag, &start_offset))
		return 0;

	offset_expr = get_array_offset(expr);
	if (!get_value(offset_expr, &sval))
		return 0;
	*offset = start_offset + sval.value * type_bytes(type);

	return 1;
}

struct range_list *swap_mtag_seed(struct expression *expr, struct range_list *rl)
{
	char buf[256];
	char *name;
	sval_t sval;
	mtag_t tag;

	if (!rl_to_sval(rl, &sval))
		return rl;
	if (sval.type->type != SYM_PTR || sval.uvalue != MTAG_SEED)
		return rl;

	name = expr_to_str(expr);
	snprintf(buf, sizeof(buf), "%s %s %s", get_filename(), get_function(), name);
	free_string(name);
	tag = str_to_mtag(buf);
	sval.value = tag;
	return alloc_rl(sval, sval);
}

int create_mtag_alias(mtag_t tag, struct expression *expr, mtag_t *new)
{
	char buf[256];
	int lines_from_start;
	char *str;

	/*
	 * We need the alias to be unique.  It's not totally required that it
	 * be the same from one DB build to then next, but it makes debugging
	 * a bit simpler.
	 *
	 */

	if (!cur_func_sym)
		return 0;

	lines_from_start = expr->pos.line - cur_func_sym->pos.line;
	str = expr_to_str(expr);
	snprintf(buf, sizeof(buf), "%lld %d %s", tag, lines_from_start, str);
	free_string(str);

	*new = str_to_mtag(buf);
	sql_insert_mtag_alias(tag, *new);

	return 1;
}

static int get_implied_mtag_offset(struct expression *expr, mtag_t *tag, int *offset)
{
	struct smatch_state *state;
	struct symbol *type;
	sval_t sval;

	type = get_type(expr);
	if (!type_is_ptr(type))
		return 0;
	state = get_extra_state(expr);
	if (!state || !estate_get_single_value(state, &sval) || sval.value == 0)
		return 0;

	*tag = sval.uvalue & ~MTAG_OFFSET_MASK;
	*offset = sval.uvalue & MTAG_OFFSET_MASK;
	return 1;
}

/*
 * The point of this function is to give you the mtag and the offset so
 * you can look up the data in the DB.  It takes an expression.
 *
 * So say you give it "foo->bar".  Then it would give you the offset of "bar"
 * and the implied value of "foo".  Or if you lookup "*foo" then the offset is
 * zero and we look up the implied value of "foo.  But if the expression is
 * foo, then if "foo" is a global variable, then we get the mtag and the offset
 * is zero.  If "foo" is a local variable, then there is nothing to look up in
 * the mtag_data table because that's handled by smatch_extra.c to this returns
 * false.
 *
 */
int expr_to_mtag_offset(struct expression *expr, mtag_t *tag, int *offset)
{
	*tag = 0;
	*offset = 0;

	if (bits_in_pointer != 64)
		return 0;

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

	if (is_array(expr))
		return get_array_mtag_offset(expr, tag, offset);

	if (expr->type == EXPR_PREOP && expr->op == '*') {
		expr = strip_expr(expr->unop);
		return get_implied_mtag_offset(expr, tag, offset);
	} else if (expr->type == EXPR_DEREF) {
		int tmp, tmp_offset = 0;

		while (expr->type == EXPR_DEREF) {
			tmp = get_member_offset_from_deref(expr);
			if (tmp < 0)
				return 0;
			tmp_offset += tmp;
			expr = strip_expr(expr->deref);
		}
		*offset = tmp_offset;
		if (expr->type == EXPR_PREOP && expr->op == '*') {
			expr = strip_expr(expr->unop);

			if (get_implied_mtag_offset(expr, tag, &tmp_offset)) {
				// FIXME:  look it up recursively?
				if (tmp_offset)
					return 0;
				return 1;
			}
			return 0;
		} else if (expr->type == EXPR_SYMBOL) {
			return get_symbol_mtag(expr->symbol, tag);
		}
		return 0;
	} else if (expr->type == EXPR_SYMBOL) {
		return get_symbol_mtag(expr->symbol, tag);
	}
	return 0;
}

/*
 * This function takes an address and returns an sval.  Let's take some
 * example things you might pass to it:
 * foo->bar:
 *   If we were only called from smatch_math, we wouldn't need to bother with
 *   this because it's already been looked up in smatch_extra.c but this is
 *   also called from other places so we have to check smatch_extra.c.
 * &foo
 *   If "foo" is global return the mtag for "foo".
 * &foo.bar
 *   If "foo" is global return the mtag for "foo" + the offset of ".bar".
 * It also handles string literals.
 *
 */
int get_mtag_sval(struct expression *expr, sval_t *sval)
{
	struct symbol *type;
	mtag_t tag;
	int offset = 0;

	if (bits_in_pointer != 64)
		return 0;

	expr = strip_expr(expr);

	type = get_type(expr);
	if (!type_is_ptr(type))
		return 0;
	/*
	 * There are several options:
	 *
	 * If the expr is a string literal, that's an address/mtag.
	 * SYM_ARRAY and SYM_FN are mtags.  There are "&foo" type addresses.
	 * And there are saved pointers "p = &foo;"
	 *
	 */

	if (expr->type == EXPR_STRING && get_string_mtag(expr, &tag))
		goto found;

	if (expr->type == EXPR_SYMBOL &&
	    (type->type == SYM_ARRAY || type->type == SYM_FN) &&
	    get_toplevel_mtag(expr->symbol, &tag))
		goto found;

	if (expr->type == EXPR_PREOP && expr->op == '&') {
		expr = strip_expr(expr->unop);
		if (expr_to_mtag_offset(expr, &tag, &offset))
			goto found;
		return 0;
	}

	if (get_implied_mtag_offset(expr, &tag, &offset))
		goto found;

	return 0;
found:
	if (offset >= MTAG_OFFSET_MASK)
		return 0;

	sval->type = type;
	sval->uvalue = tag | offset;

	return 1;
}

void register_mtag(int id)
{
	my_id = id;


	/*
	 * The mtag stuff only works on 64 systems because we store the
	 * information in the pointer itself.
	 * bit 63   : set for alias mtags
	 * bit 62-12: mtag hash
	 * bit 11-0 : offset
	 *
	 */

	add_hook(&global_variable, BASE_HOOK);
}
