mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
commit
70bbdd8fac
@ -161,7 +161,7 @@ private:
|
||||
/// Current positions of equivalence classes, equal to the empty set if already deleted.
|
||||
std::map<Id, std::set<int>> m_classPositions;
|
||||
|
||||
/// The actual eqivalence class items and how to compute them.
|
||||
/// The actual equivalence class items and how to compute them.
|
||||
ExpressionClasses& m_expressionClasses;
|
||||
/// Keeps information about which storage or memory slots were written to by which operations.
|
||||
/// The operations are sorted ascendingly by sequence number.
|
||||
|
@ -30,7 +30,7 @@ add_executable(soltest ${sources} ${headers}
|
||||
${libsolidity_sources} ${libsolidity_headers}
|
||||
${libsolidity_util_sources} ${libsolidity_util_headers}
|
||||
)
|
||||
target_link_libraries(soltest PRIVATE libsolc yul solidity evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||
target_link_libraries(soltest PRIVATE libsolc yul solidity yulInterpreter evmasm devcore ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||
|
||||
if (LLL)
|
||||
target_link_libraries(soltest PRIVATE lll)
|
||||
|
@ -23,6 +23,7 @@
|
||||
#include <test/libsolidity/SemanticTest.h>
|
||||
#include <test/libsolidity/SMTCheckerJSONTest.h>
|
||||
#include <test/libyul/YulOptimizerTest.h>
|
||||
#include <test/libyul/YulInterpreterTest.h>
|
||||
#include <test/libyul/ObjectCompilerTest.h>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
@ -51,6 +52,7 @@ Testsuite const g_interactiveTestsuites[] = {
|
||||
/*
|
||||
Title Path Subpath SMT IPC Creator function */
|
||||
{"Yul Optimizer", "libyul", "yulOptimizerTests", false, false, &yul::test::YulOptimizerTest::create},
|
||||
{"Yul Interpreter", "libyul", "yulInterpreterTests", false, false, &yul::test::YulInterpreterTest::create},
|
||||
{"Yul Object Compiler", "libyul", "objectCompiler", false, false, &yul::test::ObjectCompilerTest::create},
|
||||
{"Syntax", "libsolidity", "syntaxTests", false, false, &SyntaxTest::create},
|
||||
{"Semantic", "libsolidity", "semanticTests", false, true, &SemanticTest::create},
|
||||
|
160
test/libyul/YulInterpreterTest.cpp
Normal file
160
test/libyul/YulInterpreterTest.cpp
Normal file
@ -0,0 +1,160 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <test/libyul/YulInterpreterTest.h>
|
||||
|
||||
#include <test/tools/yulInterpreter/Interpreter.h>
|
||||
|
||||
#include <test/Options.h>
|
||||
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <libdevcore/AnsiColorized.h>
|
||||
|
||||
#include <boost/test/unit_test.hpp>
|
||||
#include <boost/algorithm/string.hpp>
|
||||
|
||||
#include <fstream>
|
||||
|
||||
using namespace dev;
|
||||
using namespace langutil;
|
||||
using namespace yul;
|
||||
using namespace yul::test;
|
||||
using namespace dev::solidity;
|
||||
using namespace dev::solidity::test;
|
||||
using namespace std;
|
||||
|
||||
YulInterpreterTest::YulInterpreterTest(string const& _filename)
|
||||
{
|
||||
boost::filesystem::path path(_filename);
|
||||
|
||||
ifstream file(_filename);
|
||||
if (!file)
|
||||
BOOST_THROW_EXCEPTION(runtime_error("Cannot open test case: \"" + _filename + "\"."));
|
||||
file.exceptions(ios::badbit);
|
||||
|
||||
string line;
|
||||
while (getline(file, line))
|
||||
{
|
||||
if (boost::algorithm::starts_with(line, "// ----"))
|
||||
break;
|
||||
m_source += std::move(line) + "\n";
|
||||
}
|
||||
while (getline(file, line))
|
||||
if (boost::algorithm::starts_with(line, "// "))
|
||||
m_expectation += line.substr(3) + "\n";
|
||||
else
|
||||
m_expectation += line + "\n";
|
||||
}
|
||||
|
||||
bool YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted)
|
||||
{
|
||||
if (!parse(_stream, _linePrefix, _formatted))
|
||||
return false;
|
||||
|
||||
m_obtainedResult = interpret();
|
||||
|
||||
if (m_expectation != m_obtainedResult)
|
||||
{
|
||||
string nextIndentLevel = _linePrefix + " ";
|
||||
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Expected result:" << endl;
|
||||
// TODO could compute a simple diff with highlighted lines
|
||||
printIndented(_stream, m_expectation, nextIndentLevel);
|
||||
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::CYAN}) << _linePrefix << "Obtained result:" << endl;
|
||||
printIndented(_stream, m_obtainedResult, nextIndentLevel);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void YulInterpreterTest::printSource(ostream& _stream, string const& _linePrefix, bool const) const
|
||||
{
|
||||
printIndented(_stream, m_source, _linePrefix);
|
||||
}
|
||||
|
||||
void YulInterpreterTest::printUpdatedExpectations(ostream& _stream, string const& _linePrefix) const
|
||||
{
|
||||
printIndented(_stream, m_obtainedResult, _linePrefix);
|
||||
}
|
||||
|
||||
void YulInterpreterTest::printIndented(ostream& _stream, string const& _output, string const& _linePrefix) const
|
||||
{
|
||||
stringstream output(_output);
|
||||
string line;
|
||||
while (getline(output, line))
|
||||
_stream << _linePrefix << line << endl;
|
||||
}
|
||||
|
||||
bool YulInterpreterTest::parse(ostream& _stream, string const& _linePrefix, bool const _formatted)
|
||||
{
|
||||
AssemblyStack stack(dev::test::Options::get().evmVersion(), AssemblyStack::Language::StrictAssembly);
|
||||
if (stack.parseAndAnalyze("", m_source))
|
||||
{
|
||||
m_ast = stack.parserResult()->code;
|
||||
m_analysisInfo = stack.parserResult()->analysisInfo;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
AnsiColorized(_stream, _formatted, {formatting::BOLD, formatting::RED}) << _linePrefix << "Error parsing source." << endl;
|
||||
printErrors(_stream, stack.errors());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
string YulInterpreterTest::interpret()
|
||||
{
|
||||
InterpreterState state;
|
||||
state.maxTraceSize = 10000;
|
||||
Interpreter interpreter(state);
|
||||
try
|
||||
{
|
||||
interpreter(*m_ast);
|
||||
}
|
||||
catch (InterpreterTerminated const&)
|
||||
{
|
||||
}
|
||||
|
||||
stringstream result;
|
||||
result << "Trace:" << endl;;
|
||||
for (auto const& line: interpreter.trace())
|
||||
result << " " << line << endl;
|
||||
result << "Memory dump:\n";
|
||||
for (size_t i = 0; i < state.memory.size(); i += 0x20)
|
||||
result << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl;
|
||||
result << "Storage dump:" << endl;
|
||||
for (auto const& slot: state.storage)
|
||||
result << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
|
||||
return result.str();
|
||||
}
|
||||
|
||||
void YulInterpreterTest::printErrors(ostream& _stream, ErrorList const& _errors)
|
||||
{
|
||||
SourceReferenceFormatter formatter(_stream);
|
||||
|
||||
for (auto const& error: _errors)
|
||||
formatter.printExceptionInformation(
|
||||
*error,
|
||||
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||
);
|
||||
}
|
72
test/libyul/YulInterpreterTest.h
Normal file
72
test/libyul/YulInterpreterTest.h
Normal file
@ -0,0 +1,72 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <test/TestCase.h>
|
||||
|
||||
namespace langutil
|
||||
{
|
||||
class Scanner;
|
||||
class Error;
|
||||
using ErrorList = std::vector<std::shared_ptr<Error const>>;
|
||||
}
|
||||
|
||||
namespace yul
|
||||
{
|
||||
struct AsmAnalysisInfo;
|
||||
struct Block;
|
||||
struct Dialect;
|
||||
}
|
||||
|
||||
namespace yul
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class YulInterpreterTest: public dev::solidity::test::TestCase
|
||||
{
|
||||
public:
|
||||
static std::unique_ptr<TestCase> create(Config const& _config)
|
||||
{
|
||||
return std::unique_ptr<TestCase>(new YulInterpreterTest(_config.filename));
|
||||
}
|
||||
|
||||
explicit YulInterpreterTest(std::string const& _filename);
|
||||
|
||||
bool run(std::ostream& _stream, std::string const& _linePrefix = "", bool const _formatted = false) override;
|
||||
|
||||
void printSource(std::ostream& _stream, std::string const &_linePrefix = "", bool const _formatted = false) const override;
|
||||
void printUpdatedExpectations(std::ostream& _stream, std::string const& _linePrefix) const override;
|
||||
|
||||
private:
|
||||
void printIndented(std::ostream& _stream, std::string const& _output, std::string const& _linePrefix = "") const;
|
||||
bool parse(std::ostream& _stream, std::string const& _linePrefix, bool const _formatted);
|
||||
std::string interpret();
|
||||
|
||||
static void printErrors(std::ostream& _stream, langutil::ErrorList const& _errors);
|
||||
|
||||
std::string m_source;
|
||||
std::string m_expectation;
|
||||
|
||||
std::shared_ptr<Block> m_ast;
|
||||
std::shared_ptr<AsmAnalysisInfo> m_analysisInfo;
|
||||
std::string m_obtainedResult;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
16
test/libyul/yulInterpreterTests/ambiguous_vars.yul
Normal file
16
test/libyul/yulInterpreterTests/ambiguous_vars.yul
Normal file
@ -0,0 +1,16 @@
|
||||
{
|
||||
{
|
||||
let a := 0x20
|
||||
mstore(a, 2)
|
||||
}
|
||||
let a
|
||||
mstore(a, 3)
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// MSTORE_AT_SIZE(32, 32) [0000000000000000000000000000000000000000000000000000000000000002]
|
||||
// MSTORE_AT_SIZE(0, 32) [0000000000000000000000000000000000000000000000000000000000000003]
|
||||
// Memory dump:
|
||||
// 0: 0000000000000000000000000000000000000000000000000000000000000003
|
||||
// 20: 0000000000000000000000000000000000000000000000000000000000000002
|
||||
// Storage dump:
|
17
test/libyul/yulInterpreterTests/external_call.yul
Normal file
17
test/libyul/yulInterpreterTests/external_call.yul
Normal file
@ -0,0 +1,17 @@
|
||||
{
|
||||
let x := call(gas(), 0x45, 0x5, 0, 0x20, 0x30, 0x20)
|
||||
sstore(100, x)
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// GAS()
|
||||
// MLOAD_FROM_SIZE(0, 32)
|
||||
// MSTORE_AT_SIZE(48, 32)
|
||||
// CALL(153, 69, 5, 0, 32, 48, 32)
|
||||
// SSTORE(100, 1)
|
||||
// Memory dump:
|
||||
// 0: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 20: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 40: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// Storage dump:
|
||||
// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001
|
14
test/libyul/yulInterpreterTests/function_calls.yul
Normal file
14
test/libyul/yulInterpreterTests/function_calls.yul
Normal file
@ -0,0 +1,14 @@
|
||||
{
|
||||
function f(a, b) -> x, y {
|
||||
x := add(a, b)
|
||||
y := mul(a, b)
|
||||
}
|
||||
let r, t := f(6, 7)
|
||||
sstore(r, t)
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// SSTORE(13, 42)
|
||||
// Memory dump:
|
||||
// Storage dump:
|
||||
// 000000000000000000000000000000000000000000000000000000000000000d: 000000000000000000000000000000000000000000000000000000000000002a
|
20
test/libyul/yulInterpreterTests/loop.yul
Normal file
20
test/libyul/yulInterpreterTests/loop.yul
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
for { let x := 2 } lt(x, 10) { x := add(x, 1) } {
|
||||
mstore(mul(x, 5), mul(x, 0x1000))
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// MSTORE_AT_SIZE(10, 32) [0000000000000000000000000000000000000000000000000000000000002000]
|
||||
// MSTORE_AT_SIZE(15, 32) [0000000000000000000000000000000000000000000000000000000000003000]
|
||||
// MSTORE_AT_SIZE(20, 32) [0000000000000000000000000000000000000000000000000000000000004000]
|
||||
// MSTORE_AT_SIZE(25, 32) [0000000000000000000000000000000000000000000000000000000000005000]
|
||||
// MSTORE_AT_SIZE(30, 32) [0000000000000000000000000000000000000000000000000000000000006000]
|
||||
// MSTORE_AT_SIZE(35, 32) [0000000000000000000000000000000000000000000000000000000000007000]
|
||||
// MSTORE_AT_SIZE(40, 32) [0000000000000000000000000000000000000000000000000000000000008000]
|
||||
// MSTORE_AT_SIZE(45, 32) [0000000000000000000000000000000000000000000000000000000000009000]
|
||||
// Memory dump:
|
||||
// 0: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 20: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 40: 0000000000000000000000900000000000000000000000000000000000000000
|
||||
// Storage dump:
|
10
test/libyul/yulInterpreterTests/simple_mstore.yul
Normal file
10
test/libyul/yulInterpreterTests/simple_mstore.yul
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
mstore(10, 11)
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// MSTORE_AT_SIZE(10, 32) [000000000000000000000000000000000000000000000000000000000000000b]
|
||||
// Memory dump:
|
||||
// 0: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 20: 0000000000000000000b00000000000000000000000000000000000000000000
|
||||
// Storage dump:
|
5
test/libyul/yulInterpreterTests/smoke.yul
Normal file
5
test/libyul/yulInterpreterTests/smoke.yul
Normal file
@ -0,0 +1,5 @@
|
||||
{}
|
||||
// ----
|
||||
// Trace:
|
||||
// Memory dump:
|
||||
// Storage dump:
|
13
test/libyul/yulInterpreterTests/switch_statement.yul
Normal file
13
test/libyul/yulInterpreterTests/switch_statement.yul
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
switch 7
|
||||
case 7 { mstore(1, 2) }
|
||||
case 3 { mstore(6, 7) }
|
||||
default { mstore(8, 9) }
|
||||
}
|
||||
// ----
|
||||
// Trace:
|
||||
// MSTORE_AT_SIZE(1, 32) [0000000000000000000000000000000000000000000000000000000000000002]
|
||||
// Memory dump:
|
||||
// 0: 0000000000000000000000000000000000000000000000000000000000000000
|
||||
// 20: 0200000000000000000000000000000000000000000000000000000000000000
|
||||
// Storage dump:
|
@ -2,6 +2,10 @@ if (OSSFUZZ)
|
||||
add_subdirectory(ossfuzz)
|
||||
endif()
|
||||
|
||||
add_subdirectory(yulInterpreter)
|
||||
add_executable(yulrun yulrun.cpp)
|
||||
target_link_libraries(yulrun PRIVATE yulInterpreter libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES})
|
||||
|
||||
add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp)
|
||||
target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES})
|
||||
|
||||
@ -26,5 +30,6 @@ add_executable(isoltest
|
||||
../libsolidity/SMTCheckerJSONTest.cpp
|
||||
../libyul/ObjectCompilerTest.cpp
|
||||
../libyul/YulOptimizerTest.cpp
|
||||
../libyul/YulInterpreterTest.cpp
|
||||
)
|
||||
target_link_libraries(isoltest PRIVATE libsolc solidity evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||
target_link_libraries(isoltest PRIVATE libsolc solidity yulInterpreter evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_UNIT_TEST_FRAMEWORK_LIBRARIES})
|
||||
|
9
test/tools/yulInterpreter/CMakeLists.txt
Normal file
9
test/tools/yulInterpreter/CMakeLists.txt
Normal file
@ -0,0 +1,9 @@
|
||||
set(sources
|
||||
EVMInstructionInterpreter.h
|
||||
EVMInstructionInterpreter.cpp
|
||||
Interpreter.h
|
||||
Interpreter.cpp
|
||||
)
|
||||
|
||||
add_library(yulInterpreter ${sources})
|
||||
target_link_libraries(yulInterpreter PUBLIC yul solidity devcore)
|
500
test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Normal file
500
test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Normal file
@ -0,0 +1,500 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Yul interpreter module that evaluates EVM instructions.
|
||||
*/
|
||||
|
||||
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
|
||||
|
||||
#include <test/tools/yulInterpreter/Interpreter.h>
|
||||
|
||||
#include <libyul/AsmData.h>
|
||||
|
||||
#include <libevmasm/Instruction.h>
|
||||
|
||||
#include <libdevcore/Keccak256.h>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
using namespace yul::test;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
u256 exp256(u256 _base, u256 _exponent)
|
||||
{
|
||||
using boost::multiprecision::limb_type;
|
||||
u256 result = 1;
|
||||
while (_exponent)
|
||||
{
|
||||
if (static_cast<limb_type>(_exponent) & 1) // If exponent is odd.
|
||||
result *= _base;
|
||||
_base *= _base;
|
||||
_exponent >>= 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Reads 32 bytes from @a _data at position @a _offset bytes while
|
||||
/// interpreting @a _data to be padded with an infinite number of zero
|
||||
/// bytes beyond its end.
|
||||
u256 readZeroExtended(bytes const& _data, u256 const& _offset)
|
||||
{
|
||||
if (_offset >= _data.size())
|
||||
return 0;
|
||||
else if (_offset + 32 <= _data.size())
|
||||
return *reinterpret_cast<h256 const*>(_data.data() + size_t(_offset));
|
||||
else
|
||||
{
|
||||
size_t off = size_t(_offset);
|
||||
u256 val;
|
||||
for (size_t i = 0; i < 32; ++i)
|
||||
{
|
||||
val <<= 8;
|
||||
if (off + i < _data.size())
|
||||
val += _data[off + i];
|
||||
}
|
||||
return val;
|
||||
}
|
||||
}
|
||||
|
||||
/// Copy @a _size bytes of @a _source at offset @a _sourceOffset to
|
||||
/// @a _target at offset @a _targetOffset. Behaves as if @a _source would
|
||||
/// continue with an infinite sequence of zero bytes beyond its end.
|
||||
/// Asserts the target is large enough to hold the copied segment.
|
||||
void copyZeroExtended(
|
||||
bytes& _target, bytes const& _source,
|
||||
size_t _targetOffset, size_t _sourceOffset, size_t _size
|
||||
)
|
||||
{
|
||||
yulAssert(_targetOffset + _size < _target.size(), "");
|
||||
for (size_t i = 0; i < _size; ++i)
|
||||
_target[_targetOffset + i] = _sourceOffset + i < _source.size() ? _source[_sourceOffset + i] : 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
using u512 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<512, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
||||
|
||||
u256 EVMInstructionInterpreter::eval(
|
||||
solidity::Instruction _instruction,
|
||||
vector<u256> const& _arguments
|
||||
)
|
||||
{
|
||||
using dev::solidity::Instruction;
|
||||
|
||||
auto info = solidity::instructionInfo(_instruction);
|
||||
yulAssert(size_t(info.args) == _arguments.size(), "");
|
||||
|
||||
auto const& arg = _arguments;
|
||||
switch (_instruction)
|
||||
{
|
||||
case Instruction::STOP:
|
||||
throw InterpreterTerminated();
|
||||
// --------------- arithmetic ---------------
|
||||
case Instruction::ADD:
|
||||
return arg[0] + arg[1];
|
||||
case Instruction::MUL:
|
||||
return arg[0] * arg[1];
|
||||
case Instruction::SUB:
|
||||
return arg[0] - arg[1];
|
||||
case Instruction::DIV:
|
||||
return arg[1] == 0 ? 0 : arg[0] / arg[1];
|
||||
case Instruction::SDIV:
|
||||
return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) / u2s(arg[1]));
|
||||
case Instruction::MOD:
|
||||
return arg[1] == 0 ? 0 : arg[0] % arg[1];
|
||||
case Instruction::SMOD:
|
||||
return arg[1] == 0 ? 0 : s2u(u2s(arg[0]) % u2s(arg[1]));
|
||||
case Instruction::EXP:
|
||||
return exp256(arg[0], arg[1]);
|
||||
case Instruction::NOT:
|
||||
return ~arg[0];
|
||||
case Instruction::LT:
|
||||
return arg[0] < arg[1] ? 1 : 0;
|
||||
case Instruction::GT:
|
||||
return arg[0] > arg[1] ? 1 : 0;
|
||||
case Instruction::SLT:
|
||||
return u2s(arg[0]) < u2s(arg[1]) ? 1 : 0;
|
||||
case Instruction::SGT:
|
||||
return u2s(arg[0]) > u2s(arg[1]) ? 1 : 0;
|
||||
case Instruction::EQ:
|
||||
return arg[0] == arg[1] ? 1 : 0;
|
||||
case Instruction::ISZERO:
|
||||
return arg[0] == 0 ? 1 : 0;
|
||||
case Instruction::AND:
|
||||
return arg[0] & arg[1];
|
||||
case Instruction::OR:
|
||||
return arg[0] | arg[1];
|
||||
case Instruction::XOR:
|
||||
return arg[0] ^ arg[1];
|
||||
case Instruction::BYTE:
|
||||
return arg[0] >= 32 ? 0 : (arg[1] >> unsigned(8 * (31 - arg[0]))) & 0xff;
|
||||
case Instruction::SHL:
|
||||
return arg[0] > 255 ? 0 : (arg[1] << unsigned(arg[0]));
|
||||
case Instruction::SHR:
|
||||
return arg[0] > 255 ? 0 : (arg[1] >> unsigned(arg[0]));
|
||||
case Instruction::SAR:
|
||||
{
|
||||
static u256 const hibit = u256(1) << 255;
|
||||
if (arg[0] >= 256)
|
||||
return arg[1] & hibit ? u256(-1) : 0;
|
||||
else
|
||||
{
|
||||
unsigned amount = unsigned(arg[0]);
|
||||
u256 v = arg[1] >> amount;
|
||||
if (arg[1] & hibit)
|
||||
v |= u256(-1) << (256 - amount);
|
||||
return v;
|
||||
}
|
||||
}
|
||||
case Instruction::ADDMOD:
|
||||
return arg[2] == 0 ? 0 : u256((u512(arg[0]) + u512(arg[1])) % arg[2]);
|
||||
case Instruction::MULMOD:
|
||||
return arg[2] == 0 ? 0 : u256((u512(arg[0]) * u512(arg[1])) % arg[2]);
|
||||
case Instruction::SIGNEXTEND:
|
||||
if (arg[0] >= 31)
|
||||
return arg[0];
|
||||
else
|
||||
{
|
||||
unsigned testBit = unsigned(arg[0]) * 8 + 7;
|
||||
u256 ret = arg[1];
|
||||
u256 mask = ((u256(1) << testBit) - 1);
|
||||
if (boost::multiprecision::bit_test(ret, testBit))
|
||||
ret |= ~mask;
|
||||
else
|
||||
ret &= mask;
|
||||
return ret;
|
||||
}
|
||||
// --------------- blockchain stuff ---------------
|
||||
case Instruction::KECCAK256:
|
||||
{
|
||||
if (!logMemoryRead(arg[0], arg[1]))
|
||||
return u256("0x1234cafe1234cafe1234cafe") + arg[0];
|
||||
uint64_t offset = uint64_t(arg[0] & uint64_t(-1));
|
||||
uint64_t size = uint64_t(arg[1] & uint64_t(-1));
|
||||
return u256(keccak256(bytesConstRef(m_state.memory.data() + offset, size)));
|
||||
}
|
||||
case Instruction::ADDRESS:
|
||||
return m_state.address;
|
||||
case Instruction::BALANCE:
|
||||
return m_state.balance;
|
||||
case Instruction::ORIGIN:
|
||||
return m_state.origin;
|
||||
case Instruction::CALLER:
|
||||
return m_state.caller;
|
||||
case Instruction::CALLVALUE:
|
||||
return m_state.callvalue;
|
||||
case Instruction::CALLDATALOAD:
|
||||
return readZeroExtended(m_state.calldata, arg[0]);
|
||||
case Instruction::CALLDATASIZE:
|
||||
return m_state.calldata.size();
|
||||
case Instruction::CALLDATACOPY:
|
||||
if (logMemoryWrite(arg[0], arg[2]))
|
||||
copyZeroExtended(
|
||||
m_state.memory, m_state.calldata,
|
||||
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
|
||||
);
|
||||
return 0;
|
||||
case Instruction::CODESIZE:
|
||||
return m_state.code.size();
|
||||
case Instruction::CODECOPY:
|
||||
if (logMemoryWrite(arg[0], arg[2]))
|
||||
copyZeroExtended(
|
||||
m_state.memory, m_state.code,
|
||||
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
|
||||
);
|
||||
return 0;
|
||||
case Instruction::GASPRICE:
|
||||
return m_state.gasprice;
|
||||
case Instruction::EXTCODESIZE:
|
||||
logTrace(_instruction, arg);
|
||||
return u256(keccak256(h256(arg[0]))) & 0xffffff;
|
||||
case Instruction::EXTCODEHASH:
|
||||
logTrace(_instruction, arg);
|
||||
return u256(keccak256(h256(arg[0] + 1)));
|
||||
case Instruction::EXTCODECOPY:
|
||||
logTrace(_instruction, arg);
|
||||
if (logMemoryWrite(arg[1], arg[3]))
|
||||
// TODO this way extcodecopy and codecopy do the same thing.
|
||||
copyZeroExtended(
|
||||
m_state.memory, m_state.code,
|
||||
size_t(arg[1]), size_t(arg[2]), size_t(arg[3])
|
||||
);
|
||||
return 0;
|
||||
case Instruction::RETURNDATASIZE:
|
||||
logTrace(_instruction, arg);
|
||||
return m_state.returndata.size();
|
||||
case Instruction::RETURNDATACOPY:
|
||||
logTrace(_instruction, arg);
|
||||
if (logMemoryWrite(arg[0], arg[2]))
|
||||
copyZeroExtended(
|
||||
m_state.memory, m_state.returndata,
|
||||
size_t(arg[0]), size_t(arg[1]), size_t(arg[2])
|
||||
);
|
||||
return 0;
|
||||
case Instruction::BLOCKHASH:
|
||||
if (arg[0] >= m_state.blockNumber || arg[0] + 256 < m_state.blockNumber)
|
||||
return 0;
|
||||
else
|
||||
return 0xaaaaaaaa + (arg[0] - m_state.blockNumber - 256);
|
||||
case Instruction::COINBASE:
|
||||
return m_state.coinbase;
|
||||
case Instruction::TIMESTAMP:
|
||||
return m_state.timestamp;
|
||||
case Instruction::NUMBER:
|
||||
return m_state.blockNumber;
|
||||
case Instruction::DIFFICULTY:
|
||||
return m_state.difficulty;
|
||||
case Instruction::GASLIMIT:
|
||||
return m_state.gaslimit;
|
||||
// --------------- memory / storage / logs ---------------
|
||||
case Instruction::MLOAD:
|
||||
if (logMemoryRead(arg[0], 0x20))
|
||||
return u256(*reinterpret_cast<h256 const*>(m_state.memory.data() + size_t(arg[0])));
|
||||
else
|
||||
return 0x1234 + arg[0];
|
||||
case Instruction::MSTORE:
|
||||
if (logMemoryWrite(arg[0], 0x20, h256(arg[1]).asBytes()))
|
||||
*reinterpret_cast<h256*>(m_state.memory.data() + size_t(arg[0])) = h256(arg[1]);
|
||||
return 0;
|
||||
case Instruction::MSTORE8:
|
||||
if (logMemoryWrite(arg[0], 1, bytes{1, uint8_t(arg[1] & 0xff)}))
|
||||
m_state.memory[size_t(arg[0])] = uint8_t(arg[1] & 0xff);
|
||||
return 0;
|
||||
case Instruction::SLOAD:
|
||||
logTrace(_instruction, arg);
|
||||
return m_state.storage[h256(arg[0])];
|
||||
case Instruction::SSTORE:
|
||||
logTrace(Instruction::SSTORE, arg);
|
||||
m_state.storage[h256(arg[0])] = h256(arg[1]);
|
||||
return 0;
|
||||
case Instruction::PC:
|
||||
logTrace(_instruction);
|
||||
return 0x77;
|
||||
case Instruction::MSIZE:
|
||||
logTrace(_instruction);
|
||||
return m_state.msize;
|
||||
case Instruction::GAS:
|
||||
logTrace(_instruction);
|
||||
return 0x99;
|
||||
case Instruction::LOG0:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
case Instruction::LOG1:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
case Instruction::LOG2:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
case Instruction::LOG3:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
case Instruction::LOG4:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
// --------------- calls ---------------
|
||||
case Instruction::CREATE:
|
||||
logMemoryRead(arg[1], arg[2]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0xcccccc + arg[1];
|
||||
case Instruction::CREATE2:
|
||||
logMemoryRead(arg[2], arg[3]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0xdddddd + arg[1];
|
||||
case Instruction::CALL:
|
||||
case Instruction::CALLCODE:
|
||||
// TODO assign returndata
|
||||
logMemoryRead(arg[3], arg[4]);
|
||||
logMemoryWrite(arg[5], arg[6]);
|
||||
logTrace(_instruction, arg);
|
||||
return arg[0] & 1;
|
||||
case Instruction::DELEGATECALL:
|
||||
case Instruction::STATICCALL:
|
||||
logMemoryRead(arg[2], arg[3]);
|
||||
logMemoryWrite(arg[4], arg[5]);
|
||||
logTrace(_instruction, arg);
|
||||
return 0;
|
||||
case Instruction::RETURN:
|
||||
{
|
||||
bytes data;
|
||||
if (logMemoryRead(arg[0], arg[1]))
|
||||
data = bytesConstRef(m_state.memory.data() + size_t(arg[0]), size_t(arg[1])).toBytes();
|
||||
logTrace(_instruction, arg, data);
|
||||
throw InterpreterTerminated();
|
||||
}
|
||||
case Instruction::REVERT:
|
||||
logMemoryRead(arg[0], arg[1]);
|
||||
logTrace(_instruction, arg);
|
||||
throw InterpreterTerminated();
|
||||
case Instruction::INVALID:
|
||||
logTrace(_instruction);
|
||||
throw InterpreterTerminated();
|
||||
case Instruction::SELFDESTRUCT:
|
||||
logTrace(_instruction, arg);
|
||||
throw InterpreterTerminated();
|
||||
case Instruction::POP:
|
||||
break;
|
||||
// --------------- invalid in strict assembly ---------------
|
||||
case Instruction::JUMP:
|
||||
case Instruction::JUMPI:
|
||||
case Instruction::JUMPDEST:
|
||||
case Instruction::PUSH1:
|
||||
case Instruction::PUSH2:
|
||||
case Instruction::PUSH3:
|
||||
case Instruction::PUSH4:
|
||||
case Instruction::PUSH5:
|
||||
case Instruction::PUSH6:
|
||||
case Instruction::PUSH7:
|
||||
case Instruction::PUSH8:
|
||||
case Instruction::PUSH9:
|
||||
case Instruction::PUSH10:
|
||||
case Instruction::PUSH11:
|
||||
case Instruction::PUSH12:
|
||||
case Instruction::PUSH13:
|
||||
case Instruction::PUSH14:
|
||||
case Instruction::PUSH15:
|
||||
case Instruction::PUSH16:
|
||||
case Instruction::PUSH17:
|
||||
case Instruction::PUSH18:
|
||||
case Instruction::PUSH19:
|
||||
case Instruction::PUSH20:
|
||||
case Instruction::PUSH21:
|
||||
case Instruction::PUSH22:
|
||||
case Instruction::PUSH23:
|
||||
case Instruction::PUSH24:
|
||||
case Instruction::PUSH25:
|
||||
case Instruction::PUSH26:
|
||||
case Instruction::PUSH27:
|
||||
case Instruction::PUSH28:
|
||||
case Instruction::PUSH29:
|
||||
case Instruction::PUSH30:
|
||||
case Instruction::PUSH31:
|
||||
case Instruction::PUSH32:
|
||||
case Instruction::DUP1:
|
||||
case Instruction::DUP2:
|
||||
case Instruction::DUP3:
|
||||
case Instruction::DUP4:
|
||||
case Instruction::DUP5:
|
||||
case Instruction::DUP6:
|
||||
case Instruction::DUP7:
|
||||
case Instruction::DUP8:
|
||||
case Instruction::DUP9:
|
||||
case Instruction::DUP10:
|
||||
case Instruction::DUP11:
|
||||
case Instruction::DUP12:
|
||||
case Instruction::DUP13:
|
||||
case Instruction::DUP14:
|
||||
case Instruction::DUP15:
|
||||
case Instruction::DUP16:
|
||||
case Instruction::SWAP1:
|
||||
case Instruction::SWAP2:
|
||||
case Instruction::SWAP3:
|
||||
case Instruction::SWAP4:
|
||||
case Instruction::SWAP5:
|
||||
case Instruction::SWAP6:
|
||||
case Instruction::SWAP7:
|
||||
case Instruction::SWAP8:
|
||||
case Instruction::SWAP9:
|
||||
case Instruction::SWAP10:
|
||||
case Instruction::SWAP11:
|
||||
case Instruction::SWAP12:
|
||||
case Instruction::SWAP13:
|
||||
case Instruction::SWAP14:
|
||||
case Instruction::SWAP15:
|
||||
case Instruction::SWAP16:
|
||||
// --------------- EVM 2.0 ---------------
|
||||
case Instruction::JUMPTO:
|
||||
case Instruction::JUMPIF:
|
||||
case Instruction::JUMPV:
|
||||
case Instruction::JUMPSUB:
|
||||
case Instruction::JUMPSUBV:
|
||||
case Instruction::BEGINSUB:
|
||||
case Instruction::BEGINDATA:
|
||||
case Instruction::RETURNSUB:
|
||||
case Instruction::PUTLOCAL:
|
||||
case Instruction::GETLOCAL:
|
||||
{
|
||||
yulAssert(false, "");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool EVMInstructionInterpreter::logMemoryRead(u256 const& _offset, u256 const& _size)
|
||||
{
|
||||
return logMemory(false, _offset, _size);
|
||||
}
|
||||
|
||||
bool EVMInstructionInterpreter::logMemoryWrite(u256 const& _offset, u256 const& _size, bytes const& _data)
|
||||
{
|
||||
return logMemory(true, _offset, _size, _data);
|
||||
}
|
||||
|
||||
bool EVMInstructionInterpreter::logMemory(bool _write, u256 const& _offset, u256 const& _size, bytes const& _data)
|
||||
{
|
||||
/// Memory size limit. Anything beyond this will still work, but it has
|
||||
/// deterministic yet not necessarily consistent behaviour.
|
||||
size_t constexpr maxMemSize = 0x20000000;
|
||||
|
||||
logTrace(_write ? "MSTORE_AT_SIZE" : "MLOAD_FROM_SIZE", {_offset, _size}, _data);
|
||||
if (_offset + _size >= _offset)
|
||||
{
|
||||
u256 newSize = _offset + _size;
|
||||
newSize = (newSize + 0x1f) & ~u256(0x1f);
|
||||
m_state.msize = max(m_state.msize, newSize);
|
||||
if (newSize < maxMemSize)
|
||||
{
|
||||
if (m_state.memory.size() < newSize)
|
||||
m_state.memory.resize(size_t(newSize));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
m_state.msize = u256(-1);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void EVMInstructionInterpreter::logTrace(solidity::Instruction _instruction, std::vector<u256> const& _arguments, bytes const& _data)
|
||||
{
|
||||
logTrace(solidity::instructionInfo(_instruction).name, _arguments, _data);
|
||||
}
|
||||
|
||||
void EVMInstructionInterpreter::logTrace(std::string const& _pseudoInstruction, std::vector<u256> const& _arguments, bytes const& _data)
|
||||
{
|
||||
string message = _pseudoInstruction + "(";
|
||||
for (size_t i = 0; i < _arguments.size(); ++i)
|
||||
message += (i > 0 ? ", " : "") + formatNumber(_arguments[i]);
|
||||
message += ")";
|
||||
if (!_data.empty())
|
||||
message += " [" + toHex(_data) + "]";
|
||||
m_state.trace.emplace_back(std::move(message));
|
||||
if (m_state.maxTraceSize > 0 && m_state.trace.size() >= m_state.maxTraceSize)
|
||||
{
|
||||
m_state.trace.emplace_back("Trace size limit reached.");
|
||||
throw InterpreterTerminated();
|
||||
}
|
||||
}
|
90
test/tools/yulInterpreter/EVMInstructionInterpreter.h
Normal file
90
test/tools/yulInterpreter/EVMInstructionInterpreter.h
Normal file
@ -0,0 +1,90 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Yul interpreter module that evaluates EVM instructions.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/AsmDataForward.h>
|
||||
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace dev
|
||||
{
|
||||
namespace solidity
|
||||
{
|
||||
enum class Instruction: uint8_t;
|
||||
}
|
||||
}
|
||||
|
||||
namespace yul
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
struct InterpreterState;
|
||||
|
||||
/**
|
||||
* Interprets EVM instructions based on the current state and logs instructions with
|
||||
* side-effects.
|
||||
*
|
||||
* Since this is mainly meant to be used for differential fuzz testing, it is focused
|
||||
* on a single contract only, does not do any gas counting and differs from the correct
|
||||
* implementation in many ways:
|
||||
*
|
||||
* - If memory access to a "large" memory position is performed, a deterministic
|
||||
* value is returned. Data that is stored in a "large" memory position is not
|
||||
* retained.
|
||||
* - The blockhash instruction returns a fixed value if the argument is in range.
|
||||
* - Extcodesize returns a deterministic value depending on the address.
|
||||
* - Extcodecopy copies a deterministic value depending on the address.
|
||||
* - And many other things
|
||||
*
|
||||
* The main focus is that the generated execution trace is the same for equivalent executions
|
||||
* and likely to be different for non-equivalent executions.
|
||||
*/
|
||||
class EVMInstructionInterpreter
|
||||
{
|
||||
public:
|
||||
explicit EVMInstructionInterpreter(InterpreterState& _state):
|
||||
m_state(_state)
|
||||
{}
|
||||
dev::u256 eval(dev::solidity::Instruction _instruction, std::vector<dev::u256> const& _arguments);
|
||||
|
||||
private:
|
||||
/// Record a memory read in the trace. Also updates m_state.msize
|
||||
/// @returns true if m_state.memory can be used at that offset.
|
||||
bool logMemoryRead(dev::u256 const& _offset, dev::u256 const& _size = 32);
|
||||
/// Record a memory write in the trace. Also updates m_state.msize
|
||||
/// @returns true if m_state.memory can be used at that offset.
|
||||
bool logMemoryWrite(dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {});
|
||||
|
||||
bool logMemory(bool _write, dev::u256 const& _offset, dev::u256 const& _size = 32, dev::bytes const& _data = {});
|
||||
|
||||
void logTrace(dev::solidity::Instruction _instruction, std::vector<dev::u256> const& _arguments = {}, dev::bytes const& _data = {});
|
||||
/// Appends a log to the trace representing an instruction or similar operation by string,
|
||||
/// with arguments and auxiliary data (if nonempty).
|
||||
void logTrace(std::string const& _pseudoInstruction, std::vector<dev::u256> const& _arguments = {}, dev::bytes const& _data = {});
|
||||
|
||||
InterpreterState& m_state;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
235
test/tools/yulInterpreter/Interpreter.cpp
Normal file
235
test/tools/yulInterpreter/Interpreter.cpp
Normal file
@ -0,0 +1,235 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Yul interpreter.
|
||||
*/
|
||||
|
||||
#include <test/tools/yulInterpreter/Interpreter.h>
|
||||
|
||||
#include <test/tools/yulInterpreter/EVMInstructionInterpreter.h>
|
||||
|
||||
#include <libyul/AsmData.h>
|
||||
#include <libyul/Utilities.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
|
||||
#include <libdevcore/FixedHash.h>
|
||||
|
||||
#include <boost/range/adaptor/reversed.hpp>
|
||||
|
||||
using namespace std;
|
||||
using namespace dev;
|
||||
using namespace yul;
|
||||
using namespace yul::test;
|
||||
|
||||
|
||||
void Interpreter::operator()(ExpressionStatement const& _expressionStatement)
|
||||
{
|
||||
evaluateMulti(_expressionStatement.expression);
|
||||
}
|
||||
|
||||
void Interpreter::operator()(Assignment const& _assignment)
|
||||
{
|
||||
solAssert(_assignment.value, "");
|
||||
vector<u256> values = evaluateMulti(*_assignment.value);
|
||||
solAssert(values.size() == _assignment.variableNames.size(), "");
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
YulString varName = _assignment.variableNames.at(i).name;
|
||||
solAssert(m_variables.count(varName), "");
|
||||
m_variables[varName] = values.at(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::operator()(VariableDeclaration const& _declaration)
|
||||
{
|
||||
vector<u256> values(_declaration.variables.size(), 0);
|
||||
if (_declaration.value)
|
||||
values = evaluateMulti(*_declaration.value);
|
||||
|
||||
solAssert(values.size() == _declaration.variables.size(), "");
|
||||
for (size_t i = 0; i < values.size(); ++i)
|
||||
{
|
||||
YulString varName = _declaration.variables.at(i).name;
|
||||
solAssert(!m_variables.count(varName), "");
|
||||
m_variables[varName] = values.at(i);
|
||||
m_scopes.back().insert(varName);
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::operator()(If const& _if)
|
||||
{
|
||||
solAssert(_if.condition, "");
|
||||
if (evaluate(*_if.condition) != 0)
|
||||
(*this)(_if.body);
|
||||
}
|
||||
|
||||
void Interpreter::operator()(Switch const& _switch)
|
||||
{
|
||||
solAssert(_switch.expression, "");
|
||||
u256 val = evaluate(*_switch.expression);
|
||||
solAssert(!_switch.cases.empty(), "");
|
||||
for (auto const& c: _switch.cases)
|
||||
// Default case has to be last.
|
||||
if (!c.value || evaluate(*c.value) == val)
|
||||
{
|
||||
(*this)(c.body);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void Interpreter::operator()(FunctionDefinition const&)
|
||||
{
|
||||
}
|
||||
|
||||
void Interpreter::operator()(ForLoop const& _forLoop)
|
||||
{
|
||||
solAssert(_forLoop.condition, "");
|
||||
|
||||
openScope();
|
||||
for (auto const& statement: _forLoop.pre.statements)
|
||||
visit(statement);
|
||||
while (evaluate(*_forLoop.condition) != 0)
|
||||
{
|
||||
(*this)(_forLoop.body);
|
||||
(*this)(_forLoop.post);
|
||||
}
|
||||
closeScope();
|
||||
}
|
||||
|
||||
void Interpreter::operator()(Block const& _block)
|
||||
{
|
||||
openScope();
|
||||
// Register functions.
|
||||
for (auto const& statement: _block.statements)
|
||||
if (statement.type() == typeid(FunctionDefinition))
|
||||
{
|
||||
FunctionDefinition const& funDef = boost::get<FunctionDefinition>(statement);
|
||||
m_functions[funDef.name] = &funDef;
|
||||
m_scopes.back().insert(funDef.name);
|
||||
}
|
||||
ASTWalker::operator()(_block);
|
||||
closeScope();
|
||||
}
|
||||
|
||||
u256 Interpreter::evaluate(Expression const& _expression)
|
||||
{
|
||||
ExpressionEvaluator ev(m_state, m_variables, m_functions);
|
||||
ev.visit(_expression);
|
||||
return ev.value();
|
||||
}
|
||||
|
||||
vector<u256> Interpreter::evaluateMulti(Expression const& _expression)
|
||||
{
|
||||
ExpressionEvaluator ev(m_state, m_variables, m_functions);
|
||||
ev.visit(_expression);
|
||||
return ev.values();
|
||||
}
|
||||
|
||||
void Interpreter::closeScope()
|
||||
{
|
||||
for (auto const& var: m_scopes.back())
|
||||
{
|
||||
size_t erased = m_variables.erase(var) + m_functions.erase(var);
|
||||
solAssert(erased == 1, "");
|
||||
}
|
||||
m_scopes.pop_back();
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::operator()(Literal const& _literal)
|
||||
{
|
||||
static YulString const trueString("true");
|
||||
static YulString const falseString("false");
|
||||
|
||||
switch (_literal.kind)
|
||||
{
|
||||
case LiteralKind::Boolean:
|
||||
solAssert(_literal.value == trueString || _literal.value == falseString, "");
|
||||
setValue(_literal.value == trueString ? 1 : 0);
|
||||
break;
|
||||
case LiteralKind::Number:
|
||||
setValue(valueOfNumberLiteral(_literal));
|
||||
break;
|
||||
case LiteralKind::String:
|
||||
solAssert(_literal.value.str().size() <= 32, "");
|
||||
setValue(u256(h256(_literal.value.str(), h256::FromBinary, h256::AlignLeft)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::operator()(Identifier const& _identifier)
|
||||
{
|
||||
solAssert(m_variables.count(_identifier.name), "");
|
||||
setValue(m_variables.at(_identifier.name));
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::operator()(FunctionalInstruction const& _instr)
|
||||
{
|
||||
evaluateArgs(_instr.arguments);
|
||||
EVMInstructionInterpreter interpreter(m_state);
|
||||
// The instruction might also return nothing, but it does not
|
||||
// hurt to set the value in that case.
|
||||
setValue(interpreter.eval(_instr.instruction, values()));
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::operator()(FunctionCall const& _funCall)
|
||||
{
|
||||
solAssert(m_functions.count(_funCall.functionName.name), "");
|
||||
evaluateArgs(_funCall.arguments);
|
||||
|
||||
FunctionDefinition const& fun = *m_functions.at(_funCall.functionName.name);
|
||||
solAssert(m_values.size() == fun.parameters.size(), "");
|
||||
map<YulString, u256> variables;
|
||||
for (size_t i = 0; i < fun.parameters.size(); ++i)
|
||||
variables[fun.parameters.at(i).name] = m_values.at(i);
|
||||
for (size_t i = 0; i < fun.returnVariables.size(); ++i)
|
||||
variables[fun.returnVariables.at(i).name] = 0;
|
||||
|
||||
// TODO function name lookup could be a little more efficient,
|
||||
// we have to copy the list here.
|
||||
Interpreter interpreter(m_state, variables, m_functions);
|
||||
interpreter(fun.body);
|
||||
|
||||
m_values.clear();
|
||||
for (auto const& retVar: fun.returnVariables)
|
||||
m_values.emplace_back(interpreter.valueOfVariable(retVar.name));
|
||||
}
|
||||
|
||||
u256 ExpressionEvaluator::value() const
|
||||
{
|
||||
solAssert(m_values.size() == 1, "");
|
||||
return m_values.front();
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::setValue(u256 _value)
|
||||
{
|
||||
m_values.clear();
|
||||
m_values.emplace_back(std::move(_value));
|
||||
}
|
||||
|
||||
void ExpressionEvaluator::evaluateArgs(vector<Expression> const& _expr)
|
||||
{
|
||||
vector<u256> values;
|
||||
/// Function arguments are evaluated in reverse.
|
||||
for (auto const& expr: _expr | boost::adaptors::reversed)
|
||||
{
|
||||
visit(expr);
|
||||
values.push_back(value());
|
||||
}
|
||||
m_values = std::move(values);
|
||||
std::reverse(m_values.begin(), m_values.end());
|
||||
}
|
162
test/tools/yulInterpreter/Interpreter.h
Normal file
162
test/tools/yulInterpreter/Interpreter.h
Normal file
@ -0,0 +1,162 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Yul interpreter.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libyul/AsmDataForward.h>
|
||||
#include <libyul/optimiser/ASTWalker.h>
|
||||
|
||||
#include <libdevcore/FixedHash.h>
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
#include <libdevcore/Exceptions.h>
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace yul
|
||||
{
|
||||
namespace test
|
||||
{
|
||||
|
||||
class InterpreterTerminated: dev::Exception
|
||||
{
|
||||
};
|
||||
|
||||
struct InterpreterState
|
||||
{
|
||||
dev::bytes calldata;
|
||||
dev::bytes returndata;
|
||||
/// TODO turn this into "vector with holes" for the randomized testing
|
||||
dev::bytes memory;
|
||||
/// This is different than memory.size() because we ignore gas.
|
||||
dev::u256 msize;
|
||||
std::map<dev::h256, dev::h256> storage;
|
||||
dev::u160 address = 0x11111111;
|
||||
dev::u256 balance = 0x22222222;
|
||||
dev::u160 origin = 0x33333333;
|
||||
dev::u160 caller = 0x44444444;
|
||||
dev::u256 callvalue = 0x55555555;
|
||||
/// Deployed code
|
||||
dev::bytes code = dev::asBytes("codecodecodecodecode");
|
||||
dev::u256 gasprice = 0x66666666;
|
||||
dev::u160 coinbase = 0x77777777;
|
||||
dev::u256 timestamp = 0x88888888;
|
||||
dev::u256 blockNumber = 1024;
|
||||
dev::u256 difficulty = 0x9999999;
|
||||
dev::u256 gaslimit = 4000000;
|
||||
/// Log of changes / effects. Sholud be structured data in the future.
|
||||
std::vector<std::string> trace;
|
||||
/// This is actually an input parameter that more or less limits the runtime.
|
||||
size_t maxTraceSize = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* Yul interpreter.
|
||||
*/
|
||||
class Interpreter: public ASTWalker
|
||||
{
|
||||
public:
|
||||
Interpreter(
|
||||
InterpreterState& _state,
|
||||
std::map<YulString, dev::u256> _variables = {},
|
||||
std::map<YulString, FunctionDefinition const*> _functions = {}
|
||||
):
|
||||
m_state(_state),
|
||||
m_variables(std::move(_variables)),
|
||||
m_functions(std::move(_functions))
|
||||
{}
|
||||
|
||||
void operator()(ExpressionStatement const& _statement) override;
|
||||
void operator()(Assignment const& _assignment) override;
|
||||
void operator()(VariableDeclaration const& _varDecl) override;
|
||||
void operator()(If const& _if) override;
|
||||
void operator()(Switch const& _switch) override;
|
||||
void operator()(FunctionDefinition const&) override;
|
||||
void operator()(ForLoop const&) override;
|
||||
void operator()(Block const& _block) override;
|
||||
|
||||
std::vector<std::string> const& trace() const { return m_state.trace; }
|
||||
|
||||
dev::u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); }
|
||||
|
||||
private:
|
||||
/// Asserts that the expression evaluates to exactly one value and returns it.
|
||||
dev::u256 evaluate(Expression const& _expression);
|
||||
/// Evaluates the expression and returns its value.
|
||||
std::vector<dev::u256> evaluateMulti(Expression const& _expression);
|
||||
|
||||
void openScope() { m_scopes.push_back({}); }
|
||||
/// Unregisters variables.
|
||||
void closeScope();
|
||||
|
||||
InterpreterState& m_state;
|
||||
/// Values of variables.
|
||||
std::map<YulString, dev::u256> m_variables;
|
||||
/// Meanings of functions.
|
||||
std::map<YulString, FunctionDefinition const*> m_functions;
|
||||
/// Scopes of variables and functions, used to clear them at end of blocks.
|
||||
std::vector<std::set<YulString>> m_scopes;
|
||||
};
|
||||
|
||||
/**
|
||||
* Yul expression evaluator.
|
||||
*/
|
||||
class ExpressionEvaluator: public ASTWalker
|
||||
{
|
||||
public:
|
||||
ExpressionEvaluator(
|
||||
InterpreterState& _state,
|
||||
std::map<YulString, dev::u256> const& _variables,
|
||||
std::map<YulString, FunctionDefinition const*> const& _functions
|
||||
):
|
||||
m_state(_state),
|
||||
m_variables(_variables),
|
||||
m_functions(_functions)
|
||||
{}
|
||||
|
||||
void operator()(Literal const&) override;
|
||||
void operator()(Identifier const&) override;
|
||||
void operator()(FunctionalInstruction const& _instr) override;
|
||||
void operator()(FunctionCall const& _funCall) override;
|
||||
|
||||
/// Asserts that the expression has exactly one value and returns it.
|
||||
dev::u256 value() const;
|
||||
/// Returns the list of values of the expression.
|
||||
std::vector<dev::u256> values() const { return m_values; }
|
||||
|
||||
private:
|
||||
void setValue(dev::u256 _value);
|
||||
|
||||
/// Evaluates the given expression from right to left and
|
||||
/// stores it in m_value.
|
||||
void evaluateArgs(std::vector<Expression> const& _expr);
|
||||
|
||||
InterpreterState& m_state;
|
||||
/// Values of variables.
|
||||
std::map<YulString, dev::u256> const& m_variables;
|
||||
/// Meanings of functions.
|
||||
std::map<YulString, FunctionDefinition const*> const& m_functions;
|
||||
/// Current value of the expression
|
||||
std::vector<dev::u256> m_values;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
144
test/tools/yulrun.cpp
Normal file
144
test/tools/yulrun.cpp
Normal file
@ -0,0 +1,144 @@
|
||||
/*
|
||||
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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
/**
|
||||
* Yul interpreter.
|
||||
*/
|
||||
|
||||
#include <test/tools/yulInterpreter/Interpreter.h>
|
||||
|
||||
#include <libyul/AsmAnalysisInfo.h>
|
||||
#include <libyul/AsmParser.h>
|
||||
#include <libyul/AsmAnalysis.h>
|
||||
#include <libyul/Dialect.h>
|
||||
#include <libyul/backends/evm/EVMDialect.h>
|
||||
#include <libyul/AssemblyStack.h>
|
||||
|
||||
#include <liblangutil/Exceptions.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <liblangutil/EVMVersion.h>
|
||||
#include <liblangutil/SourceReferenceFormatter.h>
|
||||
|
||||
#include <libdevcore/CommonIO.h>
|
||||
#include <libdevcore/CommonData.h>
|
||||
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
||||
using namespace std;
|
||||
using namespace langutil;
|
||||
using namespace yul;
|
||||
using namespace dev;
|
||||
using namespace yul::test;
|
||||
|
||||
namespace po = boost::program_options;
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
void printErrors(ErrorList const& _errors)
|
||||
{
|
||||
for (auto const& error: _errors)
|
||||
SourceReferenceFormatter(cout).printExceptionInformation(
|
||||
*error,
|
||||
(error->type() == Error::Type::Warning) ? "Warning" : "Error"
|
||||
);
|
||||
}
|
||||
|
||||
pair<shared_ptr<Block>, shared_ptr<AsmAnalysisInfo>> parse(string const& _source)
|
||||
{
|
||||
AssemblyStack stack(dev::solidity::EVMVersion(), AssemblyStack::Language::StrictAssembly);
|
||||
if (stack.parseAndAnalyze("--INPUT--", _source))
|
||||
{
|
||||
yulAssert(stack.errors().empty(), "Parsed successfully but had errors.");
|
||||
return make_pair(stack.parserResult()->code, stack.parserResult()->analysisInfo);
|
||||
}
|
||||
else
|
||||
{
|
||||
printErrors(stack.errors());
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void interpret()
|
||||
{
|
||||
string source = readStandardInput();
|
||||
shared_ptr<Block> ast;
|
||||
shared_ptr<AsmAnalysisInfo> analysisInfo;
|
||||
tie(ast, analysisInfo) = parse(source);
|
||||
if (!ast || !analysisInfo)
|
||||
return;
|
||||
|
||||
InterpreterState state;
|
||||
state.maxTraceSize = 10000;
|
||||
Interpreter interpreter(state);
|
||||
try
|
||||
{
|
||||
interpreter(*ast);
|
||||
}
|
||||
catch (InterpreterTerminated const&)
|
||||
{
|
||||
}
|
||||
|
||||
cout << "Trace:" << endl;
|
||||
for (auto const& line: interpreter.trace())
|
||||
cout << " " << line << endl;
|
||||
cout << "Memory dump:" << endl;
|
||||
for (size_t i = 0; i < state.memory.size(); i += 0x20)
|
||||
cout << " " << std::hex << std::setw(4) << i << ": " << toHex(bytesConstRef(state.memory.data() + i, 0x20).toBytes()) << endl;
|
||||
cout << "Storage dump:" << endl;
|
||||
for (auto const& slot: state.storage)
|
||||
cout << " " << slot.first.hex() << ": " << slot.second.hex() << endl;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main(int argc, char** argv)
|
||||
{
|
||||
po::options_description options(
|
||||
R"(yulrun, the Yul interpreter.
|
||||
Usage: yulrun [Options] < input
|
||||
Reads a single source from stdin, runs it and prints a trace of all side-effects.
|
||||
|
||||
Allowed options)",
|
||||
po::options_description::m_default_line_length,
|
||||
po::options_description::m_default_line_length - 23);
|
||||
options.add_options()
|
||||
("help", "Show this help screen.");
|
||||
|
||||
po::variables_map arguments;
|
||||
try
|
||||
{
|
||||
po::command_line_parser cmdLineParser(argc, argv);
|
||||
cmdLineParser.options(options);
|
||||
po::store(cmdLineParser.run(), arguments);
|
||||
}
|
||||
catch (po::error const& _exception)
|
||||
{
|
||||
cerr << _exception.what() << endl;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (arguments.count("help"))
|
||||
cout << options;
|
||||
else
|
||||
interpret();
|
||||
|
||||
return 0;
|
||||
}
|
Loading…
Reference in New Issue
Block a user