/*
 * Copyright (C) 2012 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
 */

/*
 * Basically the point of sval is that it can hold both ULLONG_MAX and
 * LLONG_MIN.  If it is an unsigned type then we use sval.uvalue or if it is
 * signed we use sval.value.
 *
 * I considered just using one bit to store whether the value was signed vs
 * unsigned but I think it might help to have the type information so we know
 * how to do type promotion.
 *
 */

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

__ALLOCATOR(sval_t, "svals", sval);

sval_t *sval_alloc(sval_t sval)
{
	sval_t *ret;

	ret = __alloc_sval(0);
	*ret = sval;
	return ret;
}

sval_t *sval_alloc_permanent(sval_t sval)
{
	sval_t *ret;

	ret = malloc(sizeof(*ret));
	*ret = sval;
	return ret;
}

sval_t sval_blank(struct expression *expr)
{
	sval_t ret;

	ret.type = get_type(expr);
	if (!ret.type)
		ret.type = &int_ctype;
	ret.value = 123456789;

	return ret;
}

sval_t sval_type_val(struct symbol *type, long long val)
{
	sval_t ret;

	if (!type)
		type = &llong_ctype;

	ret.type = type;
	ret.value = val;
	return ret;
}

sval_t sval_type_fval(struct symbol *type, long double fval)
{
	sval_t ret;

	ret.type = &ldouble_ctype;
	ret.ldvalue = fval;
	return sval_cast(type, ret);
}

sval_t sval_from_val(struct expression *expr, long long val)
{
	sval_t ret;

	ret = sval_blank(expr);
	ret.value = val;
	ret = sval_cast(get_type(expr), ret);

	return ret;
}

sval_t sval_from_fval(struct expression *expr, long double fval)
{
	sval_t ret;

	ret.type = &ldouble_ctype;
	ret.ldvalue = fval;
	ret = sval_cast(get_type(expr), ret);

	return ret;
}

int sval_is_ptr(sval_t sval)
{
	if (!sval.type)
		return 0;
	return (sval.type->type == SYM_PTR || sval.type->type == SYM_ARRAY);
}

bool sval_is_fp(sval_t sval)
{
	return type_is_fp(sval.type);
}

int sval_unsigned(sval_t sval)
{
	if (is_ptr_type(sval.type))
		return true;
	return type_unsigned(sval.type);
}

int sval_signed(sval_t sval)
{
	return !type_unsigned(sval.type);
}

int sval_bits(sval_t sval)
{
	return type_bits(sval.type);
}

int sval_bits_used(sval_t sval)
{
	int i;

	for (i = 64; i >= 1; i--) {
		if (sval.uvalue & (1ULL << (i - 1)))
			return i;
	}
	return 0;
}

int sval_is_negative(sval_t sval)
{
	if (type_unsigned(sval.type))
		return 0;
	if (sval.value < 0)
		return 1;
	return 0;
}

int sval_is_positive(sval_t sval)
{
	return !sval_is_negative(sval);
}

static bool fp_is_min(sval_t sval)
{
	if (sval.type == &float_ctype)
		return sval.fvalue == -FLT_MAX;
	if (sval.type == &double_ctype)
		return sval.dvalue == -DBL_MAX;
	if (sval.type == &ldouble_ctype)
		return sval.ldvalue == -LDBL_MAX;
	sm_perror("%s: bad type: '%s'", __func__, type_to_str(sval.type));
	return false;
}

int sval_is_min(sval_t sval)
{
	sval_t min = sval_type_min(sval.type);

	if (sval_is_fp(sval))
		return fp_is_min(sval);

	if (sval_unsigned(sval)) {
		if (sval.uvalue == 0)
			return 1;
		return 0;
	}
	/* return true for less than min as well */
	return (sval.value <= min.value);
}

static bool fp_is_max(sval_t sval)
{
	if (sval.type == &float_ctype)
		return sval.fvalue == FLT_MAX;
	if (sval.type == &double_ctype)
		return sval.dvalue == DBL_MAX;
	if (sval.type == &ldouble_ctype)
		return sval.ldvalue == LDBL_MAX;
	sm_perror("%s: bad type: '%s'", __func__, type_to_str(sval.type));
	return false;
}

int sval_is_max(sval_t sval)
{
	sval_t max = sval_type_max(sval.type);

	if (sval_is_fp(sval))
		return fp_is_max(sval);

	if (sval_unsigned(sval))
		return (sval.uvalue >= max.value);
	return (sval.value >= max.value);
}

int sval_is_a_min(sval_t sval)
{
	if (sval_is_min(sval))
		return 1;

	if (sval_is_fp(sval))
		return 0;

	if (sval_signed(sval) && sval.value == SHRT_MIN)
		return 1;
	if (sval_signed(sval) && sval.value == INT_MIN)
		return 1;
	if (sval_signed(sval) && sval.value == LLONG_MIN)
		return 1;
	return 0;
}

int sval_is_a_max(sval_t sval)
{
	if (sval_is_max(sval))
		return 1;

	if (sval_is_fp(sval))
		return 0;

	if (sval.uvalue == SHRT_MAX)
		return 1;
	if (sval.uvalue == INT_MAX)
		return 1;
	if (sval.uvalue == LLONG_MAX)
		return 1;
	if (sval.uvalue == USHRT_MAX)
		return 1;
	if (sval.uvalue == UINT_MAX)
		return 1;
	if (sval_unsigned(sval) && sval.uvalue == ULLONG_MAX)
		return 1;
	if (sval.value > valid_ptr_max - 1000 &&
	    sval.value < valid_ptr_max + 1000)
		return 1;
	return 0;
}

int sval_is_negative_min(sval_t sval)
{
	if (sval_is_fp(sval))
		return 0;

	if (!sval_is_negative(sval))
		return 0;
	return sval_is_min(sval);
}

int sval_cmp_t(struct symbol *type, sval_t one, sval_t two)
{
	sval_t one_cast, two_cast;

	one_cast = sval_cast(type, one);
	two_cast = sval_cast(type, two);
	return sval_cmp(one_cast, two_cast);
}

int sval_cmp_val(sval_t one, long long val)
{
	sval_t sval;

	sval = sval_type_val(&llong_ctype, val);
	return sval_cmp(one, sval);
}

sval_t sval_min(sval_t one, sval_t two)
{
	if (sval_cmp(one, two) > 0)
		return two;
	return one;
}

sval_t sval_min_nonneg(sval_t one, sval_t two)
{
	sval_t ret = one;

	if (sval_is_negative(one) && sval_is_negative(two)) {
		ret.value = 0;
		return ret;
	}
	if (sval_is_negative(one))
		return two;
	if (sval_is_negative(two))
		return one;

	return sval_min(one, two);
}

sval_t sval_max(sval_t one, sval_t two)
{
	if (sval_cmp(one, two) < 0)
		return two;
	return one;
}

int sval_too_low(struct symbol *type, sval_t sval)
{
	if (sval_is_negative(sval) && type_unsigned(type))
		return 1;
	if (type_signed(type) && sval_unsigned(sval))
		return 0;
	if (type_signed(sval.type) &&
	    sval.value < sval_type_min(type).value)
		return 1;
	if (sval_cmp(sval, sval_type_min(type)) < 0)
		return 1;
	return 0;
}

int sval_too_high(struct symbol *type, sval_t sval)
{
	if (sval_is_negative(sval))
		return 0;
	if (sval.uvalue > sval_type_max(type).uvalue)
		return 1;
	return 0;
}

int sval_fits(struct symbol *type, sval_t sval)
{
	/* everything fits into floating point */
	if (type_is_fp(type))
		return 1;
	/* floating points don't fit into int */
	if (type_is_fp(sval.type))
		return 0;

	if (sval_too_low(type, sval))
		return 0;
	if (sval_too_high(type, sval))
		return 0;
	return 1;
}

static sval_t cast_to_fp(struct symbol *type, sval_t sval)
{
	sval_t ret = {};

	ret.type = type;
	if (type == &float_ctype) {
		if (!sval_is_fp(sval)) {
			if (sval_unsigned(sval))
				ret.fvalue = sval.uvalue;
			else
				ret.fvalue = sval.value;
		} else if (sval.type == &float_ctype)
			ret.fvalue = sval.fvalue;
		else if (sval.type == &double_ctype)
			ret.fvalue = sval.dvalue;
		else
			ret.fvalue = sval.ldvalue;
	} else if (type == &double_ctype) {
		if (!sval_is_fp(sval)) {
			if (sval_unsigned(sval))
				ret.dvalue = sval.uvalue;
			else
				ret.dvalue = sval.value;
		} else if (sval.type == &float_ctype)
			ret.dvalue = sval.fvalue;
		else if (sval.type == &double_ctype)
			ret.dvalue = sval.dvalue;
		else
			ret.dvalue = sval.ldvalue;
	} else if (type == &ldouble_ctype) {
		if (!sval_is_fp(sval)) {
			if (sval_unsigned(sval))
				ret.ldvalue = (long double)sval.uvalue;
			else
				ret.ldvalue = (long double)sval.value;
		} else if (sval.type == &float_ctype)
			ret.ldvalue = sval.fvalue;
		else if (sval.type == &double_ctype)
			ret.ldvalue = sval.dvalue;
		else
			ret.ldvalue = sval.ldvalue;
	} else {
		sm_perror("%s: bad type: %s", __func__, type_to_str(type));
	}

	return ret;
}

static sval_t cast_from_fp(struct symbol *type, sval_t sval)
{
	sval_t ret = {};

	ret.type = &llong_ctype;
	if (sval.type == &float_ctype)
		ret.value = sval.fvalue;
	else if (sval.type == &double_ctype)
		ret.value = sval.dvalue;
	else if (sval.type == &ldouble_ctype)
		ret.value = sval.ldvalue;
	else
		sm_perror("%s: bad type: %s", __func__, type_to_str(type));

	return sval_cast(type, ret);
}

sval_t sval_cast(struct symbol *type, sval_t sval)
{
	sval_t ret;

	if (!type)
		type = &int_ctype;

	if (type_is_fp(type))
		return cast_to_fp(type, sval);
	if (type_is_fp(sval.type))
		return cast_from_fp(type, sval);

	ret.type = type;
	switch (sval_bits(ret)) {
	case 1:
		ret.value = !!sval.value;
		break;
	case 8:
		if (sval_unsigned(ret))
			ret.value = (long long)(unsigned char)sval.value;
		else
			ret.value = (long long)(char)sval.value;
		break;
	case 16:
		if (sval_unsigned(ret))
			ret.value = (long long)(unsigned short)sval.value;
		else
			ret.value = (long long)(short)sval.value;
		break;
	case 32:
		if (sval_unsigned(ret))
			ret.value = (long long)(unsigned int)sval.value;
		else
			ret.value = (long long)(int)sval.value;
		break;
	default:
		ret.value = sval.value;
	}
	return ret;

}

sval_t sval_preop(sval_t sval, int op)
{
	switch (op) {
	case '!':
		sval.value = !sval.value;
		break;
	case '~':
		sval.value = ~sval.value;
		sval = sval_cast(sval.type, sval);
		break;
	case '-':
		sval.value = -sval.value;
		sval = sval_cast(sval.type, sval);
		break;
	}
	return sval;
}

static sval_t sval_binop_unsigned(struct symbol *type, sval_t left, int op, sval_t right)
{
	sval_t ret;

	ret.type = type;
	switch (op) {
	case '*':
		ret.uvalue =  left.uvalue * right.uvalue;
		break;
	case '/':
		if (right.uvalue == 0) {
			sm_debug("%s: divide by zero", __func__);
			ret.uvalue = 123456789;
		} else {
			ret.uvalue = left.uvalue / right.uvalue;
		}
		break;
	case '+':
		ret.uvalue = left.uvalue + right.uvalue;
		break;
	case '-':
		ret.uvalue = left.uvalue - right.uvalue;
		break;
	case '%':
		if (right.uvalue == 0) {
			sm_perror(" %s: MOD by zero", __func__);
			ret.uvalue = 123456789;
		} else {
			ret.uvalue = left.uvalue % right.uvalue;
		}
		break;
	case '|':
		ret.uvalue = left.uvalue | right.uvalue;
		break;
	case '&':
		ret.uvalue = left.uvalue & right.uvalue;
		break;
	case SPECIAL_RIGHTSHIFT:
		ret.uvalue = left.uvalue >> right.uvalue;
		break;
	case SPECIAL_LEFTSHIFT:
		ret.uvalue = left.uvalue << right.uvalue;
		break;
	case '^':
		ret.uvalue = left.uvalue ^ right.uvalue;
		break;
	default:
		sm_perror(" %s: unhandled binop %s", __func__,
		       show_special(op));
		ret.uvalue = 1234567;
	}
	return ret;
}


static sval_t sval_binop_signed(struct symbol *type, sval_t left, int op, sval_t right)
{
	sval_t ret;

	ret.type = type;
	switch (op) {
	case '*':
		ret.value =  left.value * right.value;
		break;
	case '/':
		if (right.value == 0) {
			sm_debug("%s: divide by zero", __func__);
			ret.value = 123456789;
		} else if (left.value == LLONG_MIN && right.value == -1) {
			sm_debug("%s: invalid divide LLONG_MIN/-1", __func__);
			ret.value = 12345678;
		} else {
			ret.value = left.value / right.value;
		}
		break;
	case '+':
		ret.value = left.value + right.value;
		break;
	case '-':
		ret.value = left.value - right.value;
		break;
	case '%':
		if (right.value == 0) {
			sm_perror(" %s: MOD by zero", __func__);
			ret.value = 123456789;
		} else {
			ret.value = left.value % right.value;
		}
		break;
	case '|':
		ret.value = left.value | right.value;
		break;
	case '&':
		ret.value = left.value & right.value;
		break;
	case SPECIAL_RIGHTSHIFT:
		ret.value = left.value >> right.value;
		break;
	case SPECIAL_LEFTSHIFT:
		ret.value = left.value << right.value;
		break;
	case '^':
		ret.value = left.value ^ right.value;
		break;
	default:
		sm_perror(" %s: unhandled binop %s", __func__,
		       show_special(op));
		ret.value = 1234567;
	}
	return ret;
}

static sval_t ptr_binop(struct symbol *type, sval_t left, int op, sval_t right)
{
	sval_t ret;
	int align;

	if (op != '+' && op != '-')
		return sval_binop_unsigned(type, left, op, right);

	ret.type = type;
	if (type->type == SYM_PTR)
		type = get_real_base_type(type);
	align = type->ctype.alignment;
	if (align <= 0)
		align = 1;

	if (op == '+') {
		if (type_is_ptr(left.type))
			ret.value = left.value + right.value * align;
		else
			ret.value = left.value * align + right.value;
	} else {
		if (!type_is_ptr(left.type)) {
			left.value = -left.value;
			ret = ptr_binop(type, left, '+', right);
		} else if (!type_is_ptr(right.type)) {
			right.value = -right.value;
			ret = ptr_binop(type, left, '+', right);
		} else {
			ret.value = (left.value - right.value) / align;
		}
	}

	if (op == '-')
		ret.type = ssize_t_ctype;
	return ret;
}

sval_t sval_binop(sval_t left, int op, sval_t right)
{
	struct symbol *type;
	sval_t ret;

	type = get_promoted_type(left.type, right.type);

	if (type_is_ptr(type))
		ret = ptr_binop(type, left, op, right);
	else if (type_unsigned(type))
		ret = sval_binop_unsigned(type, left, op, right);
	else
		ret = sval_binop_signed(type, left, op, right);
	return sval_cast(type, ret);
}

int sval_unop_overflows(sval_t sval, int op)
{
	if (op != '-')
		return 0;
	if (sval_positive_bits(sval) == 32 && sval.value == INT_MIN)
		return 1;
	if (sval_positive_bits(sval) == 64 && sval.value == LLONG_MIN)
		return 1;
	if (sval_is_negative(sval))
		return 0;
	if (sval_signed(sval))
		return 0;
	if (sval_bits(sval) == 32 && sval.uvalue > INT_MAX)
		return 1;
	if (sval_bits(sval) == 64 && sval.uvalue > LLONG_MAX)
		return 1;
	return 0;
}

int sval_binop_overflows(sval_t left, int op, sval_t right)
{
	struct symbol *type;
	sval_t max, min;

	type = left.type;
	if (type_positive_bits(right.type) > type_positive_bits(left.type))
		type = right.type;
	if (type_positive_bits(type) < 31)
		type = &int_ctype;

	max = sval_type_max(type);
	min = sval_type_min(type);

	switch (op) {
	case '+':
		if (sval_is_negative(left) && sval_is_negative(right)) {
			if (left.value < min.value + right.value)
				return 1;
			return 0;
		}
		if (sval_is_negative(left) || sval_is_negative(right))
			return 0;
		if (left.uvalue > max.uvalue - right.uvalue)
				return 1;
		return 0;
	case '*':
		if (type_signed(type)) {
			if (left.value == 0 || right.value == 0)
				return 0;
			if (left.value > max.value / right.value)
				return 1;
			if (left.value == -1 || right.value == -1)
				return 0;
			return left.value != left.value * right.value / right.value;

		}
		return right.uvalue != 0 && left.uvalue > max.uvalue / right.uvalue;
	case '-':
		if (type_unsigned(type)) {
			if (sval_cmp(left, right) < 0)
				return 1;
			return 0;
		}
		if (sval_is_negative(left) && sval_is_negative(right))
			return 0;

		if (sval_is_negative(left)) {
			if (left.value < min.value + right.value)
				return 1;
			return 0;
		}
		if (sval_is_negative(right)) {
			if (right.value == min.value)
				return 1;
			right = sval_preop(right, '-');
			if (sval_binop_overflows(left, '+', right))
				return 1;
			return 0;
		}
		return 0;
	case SPECIAL_LEFTSHIFT:
		if (sval_cmp(left, sval_binop(max, invert_op(op), right)) > 0)
			return 1;
		return 0;
	}
	return 0;
}

int sval_binop_overflows_no_sign(sval_t left, int op, sval_t right)
{

	struct symbol *type;

	type = left.type;
	if (type_positive_bits(right.type) > type_positive_bits(left.type))
		type = right.type;
	if (type_positive_bits(type) <= 31)
		type = &uint_ctype;
	else
		type = &ullong_ctype;

	left = sval_cast(type, left);
	right = sval_cast(type, right);
	return sval_binop_overflows(left, op, right);
}

int find_first_zero_bit(unsigned long long uvalue)
{
	int i;

	for (i = 0; i < 64; i++) {
		if (!(uvalue & (1ULL << i)))
			return i;
	}
	return i;
}

int sm_fls64(unsigned long long uvalue)
{
	int high_bit = 0;

	while (uvalue) {
		uvalue >>= 1;
		high_bit++;
	}

	return high_bit;
}

unsigned long long fls_mask(unsigned long long uvalue)
{
	int high_bit = 0;

	high_bit = sm_fls64(uvalue);
	if (high_bit == 0)
		return 0;

	return ((unsigned long long)-1) >> (64 - high_bit);
}

unsigned long long sval_fls_mask(sval_t sval)
{
	return fls_mask(sval.uvalue);
}

static char *fp_to_str(sval_t sval)
{
	char buf[32];

	if (sval.type == &float_ctype)
		snprintf(buf, sizeof(buf), "%f", sval.fvalue);
	else if (sval.type == &double_ctype)
		snprintf(buf, sizeof(buf), "%e", sval.dvalue);
	else if (sval.type == &ldouble_ctype) {
		snprintf(buf, sizeof(buf), "%Lf", sval.ldvalue);
	} else
		snprintf(buf, sizeof(buf), "nan");

	return alloc_sname(buf);
}

const char *sval_to_str(sval_t sval)
{
	char buf[32];

	if (sval_is_fp(sval))
		return fp_to_str(sval);

	if (sval_is_ptr(sval) && sval.value == valid_ptr_max)
		return "ptr_max";
	if (sval_unsigned(sval) && sval.value == ULLONG_MAX)
		return "u64max";
	if (sval_unsigned(sval) && sval.value == UINT_MAX)
		return "u32max";
	if (sval.value == USHRT_MAX)
		return "u16max";

	if (sval_signed(sval) && sval.value == LLONG_MAX)
		return "s64max";
	if (sval.value == INT_MAX)
		return "s32max";
	if (sval.value == SHRT_MAX)
		return "s16max";

	if (sval_signed(sval) && sval.value == SHRT_MIN)
		return "s16min";
	if (sval_signed(sval) && sval.value == INT_MIN)
		return "s32min";
	if (sval_signed(sval) && sval.value == LLONG_MIN)
		return "s64min";

	if (sval_unsigned(sval))
		snprintf(buf, sizeof(buf), "%llu", sval.value);
	else if (sval.value < 0)
		snprintf(buf, sizeof(buf), "(%lld)", sval.value);
	else
		snprintf(buf, sizeof(buf), "%lld", sval.value);

	return alloc_sname(buf);
}

const char *sval_to_str_or_err_ptr(sval_t sval)
{
	char buf[12];

	if (option_project != PROJ_KERNEL ||
	    !is_ptr_type(sval.type))
		return sval_to_str(sval);

	if (!sval_is_fp(sval) && sval.uvalue >= -4905ULL) {
		snprintf(buf, sizeof(buf), "(%lld)", sval.value);
		return alloc_sname(buf);
	}

	return sval_to_str(sval);
}

const char *sval_to_numstr(sval_t sval)
{
	char buf[30];

	if (type_is_fp(sval.type))
		return fp_to_str(sval);

	if (sval_unsigned(sval))
		snprintf(buf, sizeof(buf), "%llu", sval.value);
	else if (sval.value < 0)
		snprintf(buf, sizeof(buf), "(%lld)", sval.value);
	else
		snprintf(buf, sizeof(buf), "%lld", sval.value);

	return alloc_sname(buf);
}

sval_t ll_to_sval(long long val)
{
	sval_t ret;

	ret.type = &llong_ctype;
	ret.value = val;
	return ret;
}

static void free_svals(struct symbol *sym)
{
	if (__inline_fn)
		return;
	clear_sval_alloc();
}

void register_sval(int my_id)
{
	add_hook(&free_svals, AFTER_FUNC_HOOK);
}
