mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Yul interpreter.
This commit is contained in:
parent
91c664ca1e
commit
0c432a861c
@ -2,6 +2,8 @@ if (OSSFUZZ)
|
||||
add_subdirectory(ossfuzz)
|
||||
endif()
|
||||
|
||||
add_subdirectory(yulInterpreter)
|
||||
|
||||
add_executable(solfuzzer afl_fuzzer.cpp fuzzer_common.cpp)
|
||||
target_link_libraries(solfuzzer PRIVATE libsolc evmasm ${Boost_PROGRAM_OPTIONS_LIBRARIES} ${Boost_SYSTEM_LIBRARIES})
|
||||
|
||||
|
10
test/tools/yulInterpreter/CMakeLists.txt
Normal file
10
test/tools/yulInterpreter/CMakeLists.txt
Normal file
@ -0,0 +1,10 @@
|
||||
set(sources
|
||||
EVMInstructionInterpreter.h
|
||||
EVMInstructionInterpreter.cpp
|
||||
Interpreter.h
|
||||
Interpreter.cpp
|
||||
|
||||
)
|
||||
|
||||
add_library(yulInterpreter ${sources})
|
||||
target_link_libraries(yulInterpreter PUBLIC yul solidity devcore)
|
489
test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Normal file
489
test/tools/yulInterpreter/EVMInstructionInterpreter.cpp
Normal file
@ -0,0 +1,489 @@
|
||||
/*
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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)))) & 0xff;
|
||||
case Instruction::SHL:
|
||||
return arg[0] > 255 ? 0 : (arg[1] << unsigned(arg[0] & 0xff));
|
||||
case Instruction::SHR:
|
||||
return arg[0] > 255 ? 0 : (arg[1] >> unsigned(arg[0] & 0xff));
|
||||
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] & 0xff);
|
||||
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] & 0xff) * 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();
|
||||
// --------------- invalid in strict assembly ---------------
|
||||
case Instruction::JUMP:
|
||||
case Instruction::JUMPI:
|
||||
case Instruction::POP:
|
||||
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)
|
||||
throw InterpreterTerminated();
|
||||
}
|
89
test/tools/yulInterpreter/EVMInstructionInterpreter.h
Normal file
89
test/tools/yulInterpreter/EVMInstructionInterpreter.h
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
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 testing, it is focused on a single
|
||||
* contract only, does not do any gas counting and differs from the correct
|
||||
* implementation in the following 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
|
||||
* - TODO
|
||||
*
|
||||
*/
|
||||
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 updaes 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 updaes 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());
|
||||
}
|
167
test/tools/yulInterpreter/Interpreter.h
Normal file
167
test/tools/yulInterpreter/Interpreter.h
Normal file
@ -0,0 +1,167 @@
|
||||
/*
|
||||
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 dev
|
||||
{
|
||||
using u120 = boost::multiprecision::number<boost::multiprecision::cpp_int_backend<120, 256, boost::multiprecision::unsigned_magnitude, boost::multiprecision::unchecked, void>>;
|
||||
}
|
||||
|
||||
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::u120 address = 0x11111111;
|
||||
dev::u256 balance = 0x22222222;
|
||||
dev::u120 origin = 0x33333333;
|
||||
dev::u120 caller = 0x44444444;
|
||||
dev::u256 callvalue = 0x55555555;
|
||||
/// Deployed code
|
||||
dev::bytes code = dev::asBytes("codecodecodecodecode");
|
||||
dev::u256 gasprice = 0x66666666;
|
||||
dev::u120 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))
|
||||
{}
|
||||
|
||||
virtual void operator()(ExpressionStatement const& _statement) override;
|
||||
virtual void operator()(Assignment const& _assignment) override;
|
||||
virtual void operator()(VariableDeclaration const& _varDecl) override;
|
||||
virtual void operator()(If const& _if) override;
|
||||
virtual void operator()(Switch const& _switch) override;
|
||||
virtual void operator()(FunctionDefinition const&) override;
|
||||
virtual void operator()(ForLoop const&) override;
|
||||
virtual 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)
|
||||
{}
|
||||
|
||||
virtual void operator()(Literal const&) override;
|
||||
virtual void operator()(Identifier const&) override;
|
||||
virtual void operator()(FunctionalInstruction const& _instr) override;
|
||||
virtual 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;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user