Yul interpreter.

This commit is contained in:
chriseth 2018-02-06 10:57:16 +01:00
parent 91c664ca1e
commit 0c432a861c
6 changed files with 992 additions and 0 deletions

View File

@ -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})

View 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)

View 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();
}

View 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;
};
}
}

View 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());
}

View 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;
};
}
}