/*
    This file is part of solidity.
    solidity 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 3 of the License, or
    (at your option) any later version.
    solidity 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 solidity.  If not, see .
*/
/**
 * Unit tests for the SMT checker.
 */
#include 
#include 
#include 
#include 
using namespace std;
using namespace langutil;
namespace dev
{
namespace solidity
{
namespace test
{
class SMTCheckerFramework: public AnalysisFramework
{
protected:
	virtual std::pair
	parseAnalyseAndReturnError(
		std::string const& _source,
		bool _reportWarnings = false,
		bool _insertVersionPragma = true,
		bool _allowMultipleErrors = false,
		bool _allowRecoveryErrors = false
	)
	{
		return AnalysisFramework::parseAnalyseAndReturnError(
			"pragma experimental SMTChecker;\n" + _source,
			_reportWarnings,
			_insertVersionPragma,
			_allowMultipleErrors,
			_allowRecoveryErrors
		);
	}
};
BOOST_FIXTURE_TEST_SUITE(SMTChecker, SMTCheckerFramework)
BOOST_AUTO_TEST_CASE(division)
{
	string text = R"(
		contract C {
			function f(uint x, uint y) public pure returns (uint) {
				return x / y;
			}
		}
	)";
	CHECK_WARNING(text, "Division by zero");
	text = R"(
		contract C {
			function f(uint x, uint y) public pure returns (uint) {
				require(y != 0);
				return x / y;
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function f(int x, int y) public pure returns (int) {
				require(y != 0);
				return x / y;
			}
		}
	)";
	CHECK_WARNING(text, "Overflow");
	text = R"(
		contract C {
			function f(int x, int y) public pure returns (int) {
				require(y != 0);
				require(y != -1);
				return x / y;
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function mul(uint256 a, uint256 b) internal pure returns (uint256) {
				uint256 c;
				if (a != 0) {
					c = a * b;
					require(c / a == b);
				}
				return c;
			}
		}
	)";
	CHECK_SUCCESS_OR_WARNING(text, "might happen");
	text = R"(
		contract C {
			function mul(uint256 a, uint256 b) internal pure returns (uint256) {
				if (a == 0) {
					return 0;
				}
				// TODO remove when SMTChecker sees that this code is the `else` of the `return`.
				require(a != 0);
				uint256 c = a * b;
				require(c / a == b);
				return c;
			}
		}
	)";
	CHECK_SUCCESS_OR_WARNING(text, "might happen");
	text = R"(
		contract C {
			function div(uint256 a, uint256 b) internal pure returns (uint256) {
				require(b > 0);
				uint256 c = a / b;
				return c;
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(division_truncates_correctly)
{
	string text = R"(
		contract C {
			function f(uint x, uint y) public pure {
				x = 7;
				y = 2;
				assert(x / y == 3);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function f(int x, int y) public pure {
				x = 7;
				y = 2;
				assert(x / y == 3);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function f(int x, int y) public pure {
				x = -7;
				y = 2;
				assert(x / y == -3);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function f(int x, int y) public pure {
				x = 7;
				y = -2;
				assert(x / y == -3);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
	text = R"(
		contract C {
			function f(int x, int y) public pure {
				x = -7;
				y = -2;
				assert(x / y == 3);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(compound_assignment_division)
{
	string text = R"(
		contract C {
			function f(uint x) public pure {
				require(x == 2);
				uint y = 10;
				y /= y / x;
				assert(y == x);
				assert(y == 0);
			}
		}
	)";
	CHECK_WARNING(text, "Assertion violation");
	text = R"(
		contract C {
			uint[] array;
			function f(uint x, uint p) public {
				require(x == 2);
				array[p] = 10;
				array[p] /= array[p] / x;
				assert(array[p] == x);
				assert(array[p] == 0);
			}
		}
	)";
	CHECK_WARNING(text, "Assertion violation");
	text = R"(
		contract C {
			mapping (uint => uint) map;
			function f(uint x, uint p) public {
				require(x == 2);
				map[p] = 10;
				map[p] /= map[p] / x;
				assert(map[p] == x);
				assert(map[p] == 0);
			}
		}
	)";
	CHECK_WARNING(text, "Assertion violation");
}
BOOST_AUTO_TEST_CASE(mod)
{
	string text = R"(
		contract C {
			function f(int x, int y) public pure {
				require(y == -10);
				require(x == 100);
				int z1 = x % y;
				int z2 = x % -y;
				assert(z1 == z2);
			}
		}
	)";
	CHECK_SUCCESS_NO_WARNINGS(text);
}
BOOST_AUTO_TEST_CASE(import_base)
{
	CompilerStack c;
	c.setSources({
	{"base", R"(
		pragma solidity >=0.0;
		contract Base {
			uint x;
			address a;
			function f() internal returns (uint) {
				a = address(this);
				++x;
				return 2;
			}
		}
	)"},
	{"der", R"(
		pragma solidity >=0.0;
		pragma experimental SMTChecker;
		import "base";
		contract Der is Base {
			function g(uint y) public {
				x += f();
				assert(y > x);
			}
		}
	)"}
	});
	c.setEVMVersion(dev::test::Options::get().evmVersion());
	BOOST_CHECK(c.compile());
	unsigned asserts = 0;
	for (auto const& e: c.errors())
	{
		string const* msg = e->comment();
		BOOST_REQUIRE(msg);
		if (msg->find("Assertion violation") != string::npos)
			++asserts;
	}
	BOOST_CHECK_EQUAL(asserts, 1);
}
BOOST_AUTO_TEST_CASE(import_library)
{
	CompilerStack c;
	c.setSources({
	{"lib", R"(
		pragma solidity >=0.0;
		library L {
			uint constant one = 1;
			function f() internal pure returns (uint) {
				return one;
			}
		}
	)"},
	{"c", R"(
		pragma solidity >=0.0;
		pragma experimental SMTChecker;
		import "lib";
		contract C {
			function g(uint x) public pure {
				uint y = L.f();
				assert(x > y);
			}
		}
	)"}
	});
	c.setEVMVersion(dev::test::Options::get().evmVersion());
	BOOST_CHECK(c.compile());
	unsigned asserts = 0;
	for (auto const& e: c.errors())
	{
		string const* msg = e->comment();
		BOOST_REQUIRE(msg);
		if (msg->find("Assertion violation") != string::npos)
			++asserts;
	}
	BOOST_CHECK_EQUAL(asserts, 1);
}
BOOST_AUTO_TEST_SUITE_END()
}
}
}