From 53b67334c539639f9b4b2178dec0a9641b6eecc6 Mon Sep 17 00:00:00 2001 From: Marenz Date: Tue, 19 Jul 2022 18:48:56 +0200 Subject: [PATCH 1/5] YulRunner: Add support for memoryguard() and literal parameters --- .../yulInterpreter/EVMInstructionInterpreter.cpp | 2 ++ test/tools/yulInterpreter/Interpreter.cpp | 14 +++++++++++++- 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 0a3c920a5..9fcd08b77 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -475,6 +475,8 @@ u256 EVMInstructionInterpreter::evalBuiltin( ); return 0; } + else if (fun == "memoryguard") + return _evaluatedArguments.at(0); else yulAssert(false, "Unknown builtin: " + fun); return 0; diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index bf0ad820b..45b39f2ed 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -351,7 +351,19 @@ void ExpressionEvaluator::evaluateArgs( if (!_literalArguments || !_literalArguments->at(_expr.size() - i - 1)) visit(expr); else - m_values = {0}; + { + string literal = std::get(expr).value.str(); + + try + { + m_values = {u256(literal)}; + } + catch (exception&) + { + m_values = {u256(0)}; + } + } + values.push_back(value()); ++i; } From 4b69b5fdc1646cbbdff59d837b00c20840aa55c6 Mon Sep 17 00:00:00 2001 From: Marenz Date: Thu, 11 Aug 2022 15:01:15 +0200 Subject: [PATCH 2/5] YulRunner: Add support for external calls to the same contract --- libevmasm/Instruction.h | 15 +++ test/libyul/EwasmTranslationTest.cpp | 1 + test/libyul/YulInterpreterTest.cpp | 1 + test/tools/ossfuzz/yulFuzzerCommon.cpp | 2 +- .../EVMInstructionInterpreter.cpp | 26 ++--- .../EVMInstructionInterpreter.h | 8 ++ test/tools/yulInterpreter/Interpreter.cpp | 108 ++++++++++++++++-- test/tools/yulInterpreter/Interpreter.h | 46 ++++++++ test/tools/yulrun.cpp | 7 +- 9 files changed, 187 insertions(+), 27 deletions(-) diff --git a/libevmasm/Instruction.h b/libevmasm/Instruction.h index 52bda8bf1..5d4432ca5 100644 --- a/libevmasm/Instruction.h +++ b/libevmasm/Instruction.h @@ -188,6 +188,21 @@ enum class Instruction: uint8_t SELFDESTRUCT = 0xff ///< halt execution and register account for later deletion }; +/// @returns true if the instruction is of the CALL opcode family +constexpr bool isCallInstruction(Instruction _inst) noexcept +{ + switch (_inst) + { + case Instruction::CALL: + case Instruction::CALLCODE: + case Instruction::DELEGATECALL: + case Instruction::STATICCALL: + return true; + default: + return false; + } +} + /// @returns true if the instruction is a PUSH inline bool isPushInstruction(Instruction _inst) { diff --git a/test/libyul/EwasmTranslationTest.cpp b/test/libyul/EwasmTranslationTest.cpp index 34a714404..075d11795 100644 --- a/test/libyul/EwasmTranslationTest.cpp +++ b/test/libyul/EwasmTranslationTest.cpp @@ -112,6 +112,7 @@ string EwasmTranslationTest::interpret() state, WasmDialect{}, *m_object->code, + /*disableExternalCalls=*/true, /*disableMemoryTracing=*/false ); } diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 3c890972e..700874349 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -98,6 +98,7 @@ string YulInterpreterTest::interpret() state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast, + /*disableExternalCalls=*/true, /*disableMemoryTracing=*/false ); } diff --git a/test/tools/ossfuzz/yulFuzzerCommon.cpp b/test/tools/ossfuzz/yulFuzzerCommon.cpp index cfbbbaa48..57f930cfe 100644 --- a/test/tools/ossfuzz/yulFuzzerCommon.cpp +++ b/test/tools/ossfuzz/yulFuzzerCommon.cpp @@ -53,7 +53,7 @@ yulFuzzerUtil::TerminationReason yulFuzzerUtil::interpret( TerminationReason reason = TerminationReason::None; try { - Interpreter::run(state, _dialect, *_ast, _disableMemoryTracing); + Interpreter::run(state, _dialect, *_ast, true, _disableMemoryTracing); } catch (StepLimitReached const&) { diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 9fcd08b77..30b2b5775 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -70,6 +70,10 @@ u256 readZeroExtended(bytes const& _data, u256 const& _offset) } } +} + +namespace solidity::yul::test +{ /// 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. @@ -185,7 +189,7 @@ u256 EVMInstructionInterpreter::eval( 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(readMemory(offset, size))); + return u256(keccak256(m_state.readMemory(offset, size))); } case Instruction::ADDRESS: return h256(m_state.address, h256::AlignRight); @@ -322,7 +326,6 @@ u256 EVMInstructionInterpreter::eval( return (0xdddddd + arg[1]) & u256("0xffffffffffffffffffffffffffffffffffffffff"); case Instruction::CALL: case Instruction::CALLCODE: - // TODO assign returndata accessMemory(arg[3], arg[4]); accessMemory(arg[5], arg[6]); logTrace(_instruction, arg); @@ -335,11 +338,11 @@ u256 EVMInstructionInterpreter::eval( return 0; case Instruction::RETURN: { - bytes data; + m_state.returndata = {}; if (accessMemory(arg[0], arg[1])) - data = readMemory(arg[0], arg[1]); - logTrace(_instruction, arg, data); - BOOST_THROW_EXCEPTION(ExplicitlyTerminated()); + m_state.returndata = m_state.readMemory(arg[0], arg[1]); + logTrace(_instruction, arg, m_state.returndata); + BOOST_THROW_EXCEPTION(ExplicitlyTerminatedWithReturn()); } case Instruction::REVERT: accessMemory(arg[0], arg[1]); @@ -499,18 +502,9 @@ bool EVMInstructionInterpreter::accessMemory(u256 const& _offset, u256 const& _s return false; } -bytes EVMInstructionInterpreter::readMemory(u256 const& _offset, u256 const& _size) -{ - yulAssert(_size <= 0xffff, "Too large read."); - bytes data(size_t(_size), uint8_t(0)); - for (size_t i = 0; i < data.size(); ++i) - data[i] = m_state.memory[_offset + i]; - return data; -} - u256 EVMInstructionInterpreter::readMemoryWord(u256 const& _offset) { - return u256(h256(readMemory(_offset, 32))); + return u256(h256(m_state.readMemory(_offset, 32))); } void EVMInstructionInterpreter::writeMemoryWord(u256 const& _offset, u256 const& _value) diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.h b/test/tools/yulInterpreter/EVMInstructionInterpreter.h index c05af8c1a..37bfdb214 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.h +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.h @@ -42,6 +42,14 @@ struct BuiltinFunctionForEVM; namespace solidity::yul::test { +/// 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. +void copyZeroExtended( + std::map& _target, bytes const& _source, + size_t _targetOffset, size_t _sourceOffset, size_t _size +); + struct InterpreterState; /** diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 45b39f2ed..f24f8b7f1 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -78,11 +78,12 @@ void Interpreter::run( InterpreterState& _state, Dialect const& _dialect, Block const& _ast, + bool _disableExternalCalls, bool _disableMemoryTrace ) { Scope scope; - Interpreter{_state, _dialect, scope, _disableMemoryTrace}(_ast); + Interpreter{_state, _dialect, scope, _disableExternalCalls, _disableMemoryTrace}(_ast); } void Interpreter::operator()(ExpressionStatement const& _expressionStatement) @@ -217,14 +218,14 @@ void Interpreter::operator()(Block const& _block) u256 Interpreter::evaluate(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); ev.visit(_expression); return ev.value(); } vector Interpreter::evaluateMulti(Expression const& _expression) { - ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableMemoryTrace); + ExpressionEvaluator ev(m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); ev.visit(_expression); return ev.values(); } @@ -288,7 +289,17 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) if (BuiltinFunctionForEVM const* fun = dialect->builtin(_funCall.functionName.name)) { EVMInstructionInterpreter interpreter(m_state, m_disableMemoryTrace); - setValue(interpreter.evalBuiltin(*fun, _funCall.arguments, values())); + + u256 const value = interpreter.evalBuiltin(*fun, _funCall.arguments, values()); + + if ( + !m_disableExternalCalls && + fun->instruction && + evmasm::isCallInstruction(*fun->instruction) + ) + runExternalCall(*fun->instruction); + + setValue(value); return; } } @@ -316,13 +327,13 @@ void ExpressionEvaluator::operator()(FunctionCall const& _funCall) variables[fun->returnVariables.at(i).name] = 0; m_state.controlFlowState = ControlFlowState::Default; - Interpreter interpreter(m_state, m_dialect, *scope, m_disableMemoryTrace, std::move(variables)); - interpreter(fun->body); + unique_ptr interpreter = makeInterpreterCopy(std::move(variables)); + (*interpreter)(fun->body); m_state.controlFlowState = ControlFlowState::Default; m_values.clear(); for (auto const& retVar: fun->returnVariables) - m_values.emplace_back(interpreter.valueOfVariable(retVar.name)); + m_values.emplace_back(interpreter->valueOfVariable(retVar.name)); } u256 ExpressionEvaluator::value() const @@ -380,3 +391,86 @@ void ExpressionEvaluator::incrementStep() BOOST_THROW_EXCEPTION(ExpressionNestingLimitReached()); } } + +void ExpressionEvaluator::runExternalCall(evmasm::Instruction _instruction) +{ + u256 memOutOffset = 0; + u256 memOutSize = 0; + u256 callvalue = 0; + u256 memInOffset = 0; + u256 memInSize = 0; + + // Setup memOut* values + if ( + _instruction == evmasm::Instruction::CALL || + _instruction == evmasm::Instruction::CALLCODE + ) + { + memOutOffset = values()[5]; + memOutSize = values()[6]; + callvalue = values()[2]; + memInOffset = values()[3]; + memInSize = values()[4]; + } + else if ( + _instruction == evmasm::Instruction::DELEGATECALL || + _instruction == evmasm::Instruction::STATICCALL + ) + { + memOutOffset = values()[4]; + memOutSize = values()[5]; + memInOffset = values()[2]; + memInSize = values()[3]; + } + else + yulAssert(false); + + // Don't execute external call if it isn't our own address + if (values()[1] != util::h160::Arith(m_state.address)) + return; + + Scope tmpScope; + InterpreterState tmpState; + tmpState.calldata = m_state.readMemory(memInOffset, memInSize); + tmpState.callvalue = callvalue; + + // Create new interpreter for the called contract + unique_ptr newInterpreter = makeInterpreterNew(tmpState, tmpScope); + + Scope* abstractRootScope = &m_scope; + Scope* fileScope = nullptr; + Block const* ast = nullptr; + + // Find file scope + while (abstractRootScope->parent) + { + fileScope = abstractRootScope; + abstractRootScope = abstractRootScope->parent; + } + + // Get AST for file scope + for (auto&& [block, scope]: abstractRootScope->subScopes) + if (scope.get() == fileScope) + { + ast = block; + break; + } + + yulAssert(ast); + + try + { + (*newInterpreter)(*ast); + } + catch (ExplicitlyTerminatedWithReturn const&) + { + // Copy return data to our memory + copyZeroExtended( + m_state.memory, + newInterpreter->returnData(), + memOutOffset.convert_to(), + 0, + memOutSize.convert_to() + ); + } +} diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index cdc454401..3eee09caa 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -24,6 +24,8 @@ #include #include +#include + #include #include @@ -47,6 +49,10 @@ class ExplicitlyTerminated: public InterpreterTerminatedGeneric { }; +class ExplicitlyTerminatedWithReturn: public ExplicitlyTerminated +{ +}; + class StepLimitReached: public InterpreterTerminatedGeneric { }; @@ -108,6 +114,15 @@ struct InterpreterState void dumpTraceAndState(std::ostream& _out, bool _disableMemoryTrace) const; /// Prints non-zero storage to @param _out. void dumpStorage(std::ostream& _out) const; + + bytes readMemory(u256 const& _offset, u256 const& _size) + { + yulAssert(_size <= 0xffff, "Too large read."); + bytes data(size_t(_size), uint8_t(0)); + for (size_t i = 0; i < data.size(); ++i) + data[i] = memory[_offset + i]; + return data; + } }; /** @@ -135,6 +150,7 @@ public: InterpreterState& _state, Dialect const& _dialect, Block const& _ast, + bool _disableExternalCalls, bool _disableMemoryTracing ); @@ -142,6 +158,7 @@ public: InterpreterState& _state, Dialect const& _dialect, Scope& _scope, + bool _disableExternalCalls, bool _disableMemoryTracing, std::map _variables = {} ): @@ -149,6 +166,7 @@ public: m_state(_state), m_variables(std::move(_variables)), m_scope(&_scope), + m_disableExternalCalls(_disableExternalCalls), m_disableMemoryTrace(_disableMemoryTracing) { } @@ -165,6 +183,7 @@ public: void operator()(Leave const&) override; void operator()(Block const& _block) override; + bytes returnData() const { return m_state.returndata; } std::vector const& trace() const { return m_state.trace; } u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); } @@ -187,6 +206,7 @@ private: /// Values of variables. std::map m_variables; Scope* m_scope; + bool m_disableExternalCalls; bool m_disableMemoryTrace; }; @@ -201,12 +221,14 @@ public: Dialect const& _dialect, Scope& _scope, std::map const& _variables, + bool _disableExternalCalls, bool _disableMemoryTrace ): m_state(_state), m_dialect(_dialect), m_variables(_variables), m_scope(_scope), + m_disableExternalCalls(_disableExternalCalls), m_disableMemoryTrace(_disableMemoryTrace) {} @@ -220,6 +242,29 @@ public: std::vector values() const { return m_values; } private: + void runExternalCall(evmasm::Instruction _instruction); + virtual std::unique_ptr makeInterpreterCopy(std::map _variables = {}) const + { + return std::make_unique( + m_state, + m_dialect, + m_scope, + m_disableExternalCalls, + m_disableMemoryTrace, + std::move(_variables) + ); + } + virtual std::unique_ptr makeInterpreterNew(InterpreterState& _state, Scope& _scope) const + { + return std::make_unique( + _state, + m_dialect, + _scope, + m_disableExternalCalls, + m_disableMemoryTrace + ); + } + void setValue(u256 _value); /// Evaluates the given expression from right to left and @@ -243,6 +288,7 @@ private: std::vector m_values; /// Current expression nesting level unsigned m_nestingLevel = 0; + bool m_disableExternalCalls; /// Flag to disable memory tracing bool m_disableMemoryTrace; }; diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index fd43d222d..4a273d371 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -74,7 +74,7 @@ pair, shared_ptr> parse(string const& _source } } -void interpret(string const& _source) +void interpret(string const& _source, bool _disableExternalCalls) { shared_ptr ast; shared_ptr analysisInfo; @@ -87,7 +87,7 @@ void interpret(string const& _source) try { Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); - Interpreter::run(state, dialect, *ast, /*disableMemoryTracing=*/false); + Interpreter::run(state, dialect, *ast, _disableExternalCalls, /*disableMemoryTracing=*/false); } catch (InterpreterTerminatedGeneric const&) { @@ -110,6 +110,7 @@ Allowed options)", po::options_description::m_default_line_length - 23); options.add_options() ("help", "Show this help screen.") + ("enable-external-calls", "Enable external calls") ("input-file", po::value>(), "input file"); po::positional_options_description filesPositions; filesPositions.add("input-file", -1); @@ -153,7 +154,7 @@ Allowed options)", else input = readUntilEnd(cin); - interpret(input); + interpret(input, !arguments.count("enable-external-calls")); } return 0; From b8699e7687488677704c993b6286c13edc66f036 Mon Sep 17 00:00:00 2001 From: Marenz Date: Thu, 11 Aug 2022 16:03:56 +0200 Subject: [PATCH 3/5] YulRunner: Add support for interactive inspection of the state --- test/tools/yulInterpreter/CMakeLists.txt | 2 + test/tools/yulInterpreter/Inspector.cpp | 156 ++++++++++++++++ test/tools/yulInterpreter/Inspector.h | 213 ++++++++++++++++++++++ test/tools/yulInterpreter/Interpreter.cpp | 28 +++ test/tools/yulInterpreter/Interpreter.h | 10 +- test/tools/yulrun.cpp | 13 +- 6 files changed, 415 insertions(+), 7 deletions(-) create mode 100644 test/tools/yulInterpreter/Inspector.cpp create mode 100644 test/tools/yulInterpreter/Inspector.h diff --git a/test/tools/yulInterpreter/CMakeLists.txt b/test/tools/yulInterpreter/CMakeLists.txt index d3ddac13d..cc1b40f07 100644 --- a/test/tools/yulInterpreter/CMakeLists.txt +++ b/test/tools/yulInterpreter/CMakeLists.txt @@ -5,6 +5,8 @@ set(sources EwasmBuiltinInterpreter.cpp Interpreter.h Interpreter.cpp + Inspector.h + Inspector.cpp ) add_library(yulInterpreter ${sources}) diff --git a/test/tools/yulInterpreter/Inspector.cpp b/test/tools/yulInterpreter/Inspector.cpp new file mode 100644 index 000000000..3ded7367f --- /dev/null +++ b/test/tools/yulInterpreter/Inspector.cpp @@ -0,0 +1,156 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Yul interpreter. + */ + +#include + +#include +#include + +using namespace solidity; +using namespace solidity::yul; +using namespace solidity::yul::test; + +using namespace std; + +namespace +{ + +void printVariable(YulString const& _name, u256 const& _value) +{ + cout << "\t" << _name.str() << " = " << _value.str(); + + if (_value != 0) + cout << " (" << toCompactHexWithPrefix(_value) << ")"; + + cout << endl; +} + +} + +void InspectedInterpreter::run( + std::shared_ptr _inspector, + InterpreterState& _state, + Dialect const& _dialect, + Block const& _ast, + bool _disableExternalCalls, + bool _disableMemoryTrace +) +{ + Scope scope; + InspectedInterpreter{_inspector, _state, _dialect, scope, _disableExternalCalls, _disableMemoryTrace}(_ast); +} + +Inspector::NodeAction Inspector::queryUser(DebugData const& _data, map const& _variables) +{ + if (m_stepMode == NodeAction::RunNode) + { + // Output instructions that are being skipped/run + cout << "Running " << currentSource(_data) << endl; + + return NodeAction::StepThroughNode; + } + + string input; + + while (true) + { + // Output sourcecode about to run. + cout << "> " << currentSource(_data) << endl; + + // Ask user for action + cout << endl + << "(s)tep/(n)ext/(i)nspect/(p)rint/all (v)ariables?" + << endl + << "# "; + + cout.flush(); + + getline(cin, input); + boost::algorithm::trim(input); + + // Imitate GDB and repeat last cmd for empty string input. + if (input == "") + input = m_lastInput; + else + m_lastInput = input; + + if (input == "next" || input == "n") + return NodeAction::RunNode; + else if (input == "step" || input == "s") + return NodeAction::StepThroughNode; + else if (input == "inspect" || input == "i") + m_state.dumpTraceAndState(cout, false); + else if (input == "variables" || input == "v") + { + for (auto &&[yulStr, val]: _variables) + printVariable(yulStr, val); + cout << endl; + } + else if ( + boost::starts_with(input, "print") || + boost::starts_with(input, "p") + ) + { + size_t whitespacePos = input.find(' '); + + if (whitespacePos == string::npos) + cout << "Error parsing command! Expected variable name." << endl; + + string const varname = input.substr(whitespacePos + 1); + + vector candidates; + + bool found = false; + for (auto &&[yulStr, val]: _variables) + if (yulStr.str() == varname) + { + printVariable(yulStr, val); + found = true; + break; + } + + if (!found) + cout << varname << " not found." << endl; + } + } +} + +std::string Inspector::currentSource(DebugData const& _data) const +{ + return m_source.substr( + static_cast(_data.nativeLocation.start), + static_cast(_data.nativeLocation.end - _data.nativeLocation.start) + ); +} + +u256 InspectedInterpreter::evaluate(Expression const& _expression) +{ + InspectedExpressionEvaluator ev(m_inspector, m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); + ev.visit(_expression); + return ev.value(); +} + +std::vector InspectedInterpreter::evaluateMulti(Expression const& _expression) +{ + InspectedExpressionEvaluator ev(m_inspector, m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace); + ev.visit(_expression); + return ev.values(); +} diff --git a/test/tools/yulInterpreter/Inspector.h b/test/tools/yulInterpreter/Inspector.h new file mode 100644 index 000000000..611279c6f --- /dev/null +++ b/test/tools/yulInterpreter/Inspector.h @@ -0,0 +1,213 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 +/** + * Yul inspector. + */ + +#include + +#include + +#include + +#pragma once + +namespace solidity::yul::test +{ + +/** + * Inspector class to respond to queries by the user for: + * + * * Stepping through and over instructions. + * * Printing a specific or all variables. + * * Inspecting memory, storage and calldata memory. + */ +class Inspector +{ +public: + enum class NodeAction + { + RunNode, + StepThroughNode, + }; + + Inspector(std::string const& _source, InterpreterState const& _state) + :m_source(_source), m_state(_state) {} + + /* Asks the user what action to take. + * @returns NodeAction::RunNode if the current AST node (and all children nodes!) should be + * processed without stopping, else NodeAction::StepThroughNode. + */ + NodeAction queryUser(DebugData const& _data, std::map const& _variables); + + void stepMode(NodeAction _action) { m_stepMode = _action; } + + std::string const& source() const { return m_source; } + + void interactiveVisit(DebugData const& _debugData, std::map const& _variables, std::function _visitNode) + { + Inspector::NodeAction action = queryUser(_debugData, _variables); + + if (action == NodeAction::RunNode) + { + // user requested to run the whole node without stopping + stepMode(Inspector::NodeAction::RunNode); + + _visitNode(); + + // Reset step mode back + stepMode(Inspector::NodeAction::StepThroughNode); + } + else + _visitNode(); + } + +private: + std::string currentSource(DebugData const& _data) const; + + /// Source of the file + std::string const& m_source; + + /// State of the interpreter + InterpreterState const& m_state; + + /// Last user query command + std::string m_lastInput; + + /// Used to run AST nodes without user interaction + NodeAction m_stepMode = NodeAction::StepThroughNode; +}; + +/** + * Yul Interpreter with inspection. Allows the user to go through the code step + * by step and inspect the state using the `Inspector` class + */ +class InspectedInterpreter: public Interpreter +{ +public: + static void run( + std::shared_ptr _inspector, + InterpreterState& _state, + Dialect const& _dialect, + Block const& _ast, + bool _disableExternalCalls, + bool _disableMemoryTracing + ); + + InspectedInterpreter( + std::shared_ptr _inspector, + InterpreterState& _state, + Dialect const& _dialect, + Scope& _scope, + bool _disableExternalCalls, + bool _disableMemoryTracing, + std::map _variables = {} + ): + Interpreter(_state, _dialect, _scope, _disableExternalCalls, _disableMemoryTracing, _variables), + m_inspector(_inspector) + { + } + + void operator()(ExpressionStatement const& _node) override { helper(_node); } + void operator()(Assignment const& _node) override { helper(_node); } + void operator()(VariableDeclaration const& _node) override { helper(_node); } + void operator()(If const& _node) override { helper(_node); } + void operator()(Switch const& _node) override { helper(_node); } + void operator()(ForLoop const& _node) override { helper(_node); } + void operator()(Break const& _node) override { helper(_node); } + void operator()(Continue const& _node) override { helper(_node); } + void operator()(Leave const& _node) override { helper(_node); } + void operator()(Block const& _node) override { helper(_node); } +protected: + /// Asserts that the expression evaluates to exactly one value and returns it. + u256 evaluate(Expression const& _expression) override; + /// Evaluates the expression and returns its value. + std::vector evaluateMulti(Expression const& _expression) override; +private: + std::shared_ptr m_inspector; + + template + void helper(ConcreteNode const& _node) + { + m_inspector->interactiveVisit(*_node.debugData, m_variables, [&]() { + Interpreter::operator()(_node); + }); + } + +}; + + +class InspectedExpressionEvaluator: public ExpressionEvaluator +{ +public: + InspectedExpressionEvaluator( + std::shared_ptr _inspector, + InterpreterState& _state, + Dialect const& _dialect, + Scope& _scope, + std::map const& _variables, + bool _disableExternalCalls, + bool _disableMemoryTrace + ): + ExpressionEvaluator(_state, _dialect, _scope, _variables, _disableExternalCalls, _disableMemoryTrace), + m_inspector(_inspector) + {} + + template + void helper(ConcreteNode const& _node) + { + m_inspector->interactiveVisit(*_node.debugData, m_variables, [&]() { + ExpressionEvaluator::operator()(_node); + }); + } + + void operator()(Literal const& _node) override { helper(_node); } + void operator()(Identifier const& _node) override { helper(_node); } + void operator()(FunctionCall const& _node) override { helper(_node); } +protected: + std::unique_ptr makeInterpreterCopy(std::map _variables = {}) const override + { + return std::make_unique( + m_inspector, + m_state, + m_dialect, + m_scope, + m_disableExternalCalls, + m_disableMemoryTrace, + std::move(_variables) + ); + } + std::unique_ptr makeInterpreterNew(InterpreterState& _state, Scope& _scope) const override + { + return std::make_unique( + std::make_unique( + m_inspector->source(), + _state + ), + _state, + m_dialect, + _scope, + m_disableExternalCalls, + m_disableMemoryTrace + ); + } +private: + std::shared_ptr m_inspector; +}; + +} diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index f24f8b7f1..9add48509 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -72,6 +72,34 @@ void InterpreterState::dumpTraceAndState(ostream& _out, bool _disableMemoryTrace } _out << "Storage dump:" << endl; dumpStorage(_out); + + if (!calldata.empty()) + { + _out << "Calldata dump:"; + + for (size_t offset = 0; offset < calldata.size(); ++offset) + if (calldata[offset] != 0) + { + if (offset % 32 == 0) + _out << + std::endl << + " " << + std::uppercase << + std::hex << + std::setfill(' ') << + std::setw(4) << + offset << + ": "; + + _out << + std::hex << + std::setw(2) << + std::setfill('0') << + static_cast(calldata[offset]); + } + + _out << endl; + } } void Interpreter::run( diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 3eee09caa..5dcb328d9 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -188,11 +188,11 @@ public: u256 valueOfVariable(YulString _name) const { return m_variables.at(_name); } -private: +protected: /// Asserts that the expression evaluates to exactly one value and returns it. - u256 evaluate(Expression const& _expression); + virtual u256 evaluate(Expression const& _expression); /// Evaluates the expression and returns its value. - std::vector evaluateMulti(Expression const& _expression); + virtual std::vector evaluateMulti(Expression const& _expression); void enterScope(Block const& _block); void leaveScope(); @@ -206,6 +206,8 @@ private: /// Values of variables. std::map m_variables; Scope* m_scope; + /// If not set, external calls (e.g. using `call()`) to the same contract + /// are evaluated in a new parser instance. bool m_disableExternalCalls; bool m_disableMemoryTrace; }; @@ -241,7 +243,7 @@ public: /// Returns the list of values of the expression. std::vector values() const { return m_values; } -private: +protected: void runExternalCall(evmasm::Instruction _instruction); virtual std::unique_ptr makeInterpreterCopy(std::map _variables = {}) const { diff --git a/test/tools/yulrun.cpp b/test/tools/yulrun.cpp index 4a273d371..eee342470 100644 --- a/test/tools/yulrun.cpp +++ b/test/tools/yulrun.cpp @@ -20,6 +20,7 @@ */ #include +#include #include #include @@ -74,7 +75,7 @@ pair, shared_ptr> parse(string const& _source } } -void interpret(string const& _source, bool _disableExternalCalls) +void interpret(string const& _source, bool _inspect, bool _disableExternalCalls) { shared_ptr ast; shared_ptr analysisInfo; @@ -87,7 +88,12 @@ void interpret(string const& _source, bool _disableExternalCalls) try { Dialect const& dialect(EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{})); - Interpreter::run(state, dialect, *ast, _disableExternalCalls, /*disableMemoryTracing=*/false); + + if (_inspect) + InspectedInterpreter::run(std::make_shared(_source, state), state, dialect, *ast, _disableExternalCalls, /*disableMemoryTracing=*/false); + + else + Interpreter::run(state, dialect, *ast, _disableExternalCalls, /*disableMemoryTracing=*/false); } catch (InterpreterTerminatedGeneric const&) { @@ -111,6 +117,7 @@ Allowed options)", options.add_options() ("help", "Show this help screen.") ("enable-external-calls", "Enable external calls") + ("interactive", "Run interactive") ("input-file", po::value>(), "input file"); po::positional_options_description filesPositions; filesPositions.add("input-file", -1); @@ -154,7 +161,7 @@ Allowed options)", else input = readUntilEnd(cin); - interpret(input, !arguments.count("enable-external-calls")); + interpret(input, arguments.count("interactive"), !arguments.count("enable-external-calls")); } return 0; From d10d9670083df9161226013b8b76b41309956e58 Mon Sep 17 00:00:00 2001 From: Marenz Date: Tue, 13 Sep 2022 13:16:59 +0200 Subject: [PATCH 4/5] yulRun: Fix unexpected return value for call* instructions and add tests --- test/libyul/YulInterpreterTest.cpp | 5 +++-- test/libyul/YulInterpreterTest.h | 1 + .../external_call_to_self.yul | 22 +++++++++++++++++++ ..._call.yul => external_call_unexecuted.yul} | 2 +- .../external_callcode_unexecuted.yul | 10 +++++++++ .../external_delegatecall_unexecuted.yul | 10 +++++++++ .../external_staticcall_unexecuted.yul | 12 ++++++++++ .../EVMInstructionInterpreter.cpp | 15 +++++++++++-- test/tools/yulInterpreter/Interpreter.cpp | 1 + 9 files changed, 73 insertions(+), 5 deletions(-) create mode 100644 test/libyul/yulInterpreterTests/external_call_to_self.yul rename test/libyul/yulInterpreterTests/{external_call.yul => external_call_unexecuted.yul} (94%) create mode 100644 test/libyul/yulInterpreterTests/external_callcode_unexecuted.yul create mode 100644 test/libyul/yulInterpreterTests/external_delegatecall_unexecuted.yul create mode 100644 test/libyul/yulInterpreterTests/external_staticcall_unexecuted.yul diff --git a/test/libyul/YulInterpreterTest.cpp b/test/libyul/YulInterpreterTest.cpp index 700874349..47b167c91 100644 --- a/test/libyul/YulInterpreterTest.cpp +++ b/test/libyul/YulInterpreterTest.cpp @@ -51,6 +51,7 @@ YulInterpreterTest::YulInterpreterTest(string const& _filename): { m_source = m_reader.source(); m_expectation = m_reader.simpleExpectations(); + m_simulateExternalCallsToSelf = m_reader.boolSetting("simulateExternalCall", false); } TestCase::TestResult YulInterpreterTest::run(ostream& _stream, string const& _linePrefix, bool const _formatted) @@ -98,8 +99,8 @@ string YulInterpreterTest::interpret() state, EVMDialect::strictAssemblyForEVMObjects(langutil::EVMVersion{}), *m_ast, - /*disableExternalCalls=*/true, - /*disableMemoryTracing=*/false + /*disableExternalCalls=*/ !m_simulateExternalCallsToSelf, + /*disableMemoryTracing=*/ false ); } catch (InterpreterTerminatedGeneric const&) diff --git a/test/libyul/YulInterpreterTest.h b/test/libyul/YulInterpreterTest.h index fc362ecf6..a20b0688a 100644 --- a/test/libyul/YulInterpreterTest.h +++ b/test/libyul/YulInterpreterTest.h @@ -47,6 +47,7 @@ private: std::shared_ptr m_ast; std::shared_ptr m_analysisInfo; + bool m_simulateExternalCallsToSelf = false; }; } diff --git a/test/libyul/yulInterpreterTests/external_call_to_self.yul b/test/libyul/yulInterpreterTests/external_call_to_self.yul new file mode 100644 index 000000000..ad2d2bb77 --- /dev/null +++ b/test/libyul/yulInterpreterTests/external_call_to_self.yul @@ -0,0 +1,22 @@ +{ + mstore(0x40, 0x42) + + if iszero(calldatasize()) { + let x := call(gas(), address(), 0, 0x40, 0x20, 0x100, 0x20) + sstore(0x64, calldataload(0)) + sstore(0x100, x) + return(0x0, 0) + } + return(0x40, 0x20) +} +// ==== +// simulateExternalCall: true +// ---- +// Trace: +// CALL(153, 0x11111111, 0, 64, 32, 256, 32) +// RETURN(0, 0) +// Memory dump: +// 40: 0000000000000000000000000000000000000000000000000000000000000042 +// 100: 0000000000000000000000000000000000000000000000000000000000000042 +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000100: 0000000000000000000000000000000000000000000000000000000000000001 diff --git a/test/libyul/yulInterpreterTests/external_call.yul b/test/libyul/yulInterpreterTests/external_call_unexecuted.yul similarity index 94% rename from test/libyul/yulInterpreterTests/external_call.yul rename to test/libyul/yulInterpreterTests/external_call_unexecuted.yul index b0d3383a4..5edfe600d 100644 --- a/test/libyul/yulInterpreterTests/external_call.yul +++ b/test/libyul/yulInterpreterTests/external_call_unexecuted.yul @@ -1,6 +1,6 @@ { let x := call(gas(), 0x45, 0x5, 0, 0x20, 0x30, 0x20) - sstore(100, x) + sstore(0x64, x) } // ---- // Trace: diff --git a/test/libyul/yulInterpreterTests/external_callcode_unexecuted.yul b/test/libyul/yulInterpreterTests/external_callcode_unexecuted.yul new file mode 100644 index 000000000..427d5604b --- /dev/null +++ b/test/libyul/yulInterpreterTests/external_callcode_unexecuted.yul @@ -0,0 +1,10 @@ +{ + let x := callcode(gas(), 0x45, 0x5, 0, 0x20, 0x30, 0x20) + sstore(100, x) +} +// ---- +// Trace: +// CALLCODE(153, 69, 5, 0, 32, 48, 32) +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001 diff --git a/test/libyul/yulInterpreterTests/external_delegatecall_unexecuted.yul b/test/libyul/yulInterpreterTests/external_delegatecall_unexecuted.yul new file mode 100644 index 000000000..661782589 --- /dev/null +++ b/test/libyul/yulInterpreterTests/external_delegatecall_unexecuted.yul @@ -0,0 +1,10 @@ +{ + let x := delegatecall(gas(), 0x45, 0, 0x20, 0x30, 0x20) + sstore(100, x) +} +// ---- +// Trace: +// DELEGATECALL(153, 69, 0, 32, 48, 32) +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001 diff --git a/test/libyul/yulInterpreterTests/external_staticcall_unexecuted.yul b/test/libyul/yulInterpreterTests/external_staticcall_unexecuted.yul new file mode 100644 index 000000000..32b50270d --- /dev/null +++ b/test/libyul/yulInterpreterTests/external_staticcall_unexecuted.yul @@ -0,0 +1,12 @@ +{ + let x := staticcall(gas(), 0x45, 0, 0x20, 0x30, 0x20) + sstore(0x64, x) +} +// ==== +// EVMVersion: >=byzantium +// ---- +// Trace: +// STATICCALL(153, 69, 0, 32, 48, 32) +// Memory dump: +// Storage dump: +// 0000000000000000000000000000000000000000000000000000000000000064: 0000000000000000000000000000000000000000000000000000000000000001 diff --git a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp index 30b2b5775..d16b6a5de 100644 --- a/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp +++ b/test/tools/yulInterpreter/EVMInstructionInterpreter.cpp @@ -329,13 +329,24 @@ u256 EVMInstructionInterpreter::eval( accessMemory(arg[3], arg[4]); accessMemory(arg[5], arg[6]); logTrace(_instruction, arg); - return arg[0] & 1; + // Randomly fail based on the called address if it isn't a call to self. + // Used for fuzzing. + return ( + (arg[0] > 0) && + (arg[1] == util::h160::Arith(m_state.address) || (arg[1] & 1)) + ) ? 1 : 0; case Instruction::DELEGATECALL: case Instruction::STATICCALL: accessMemory(arg[2], arg[3]); accessMemory(arg[4], arg[5]); logTrace(_instruction, arg); - return 0; + + // Randomly fail based on the called address if it isn't a call to self. + // Used for fuzzing. + return ( + (arg[0] > 0) && + (arg[1] == util::h160::Arith(m_state.address) || (arg[1] & 1)) + ) ? 1 : 0; case Instruction::RETURN: { m_state.returndata = {}; diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 9add48509..33546f745 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -500,5 +500,6 @@ void ExpressionEvaluator::runExternalCall(evmasm::Instruction _instruction) 0, memOutSize.convert_to() ); + m_state.returndata = newInterpreter->returnData(); } } From 692a99f8914f6e881d0e7be85ea3db76ca77e8bb Mon Sep 17 00:00:00 2001 From: Marenz Date: Tue, 13 Sep 2022 14:05:50 +0200 Subject: [PATCH 5/5] yulRun: Add recursion protection --- test/tools/yulInterpreter/Interpreter.cpp | 3 +++ test/tools/yulInterpreter/Interpreter.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/tools/yulInterpreter/Interpreter.cpp b/test/tools/yulInterpreter/Interpreter.cpp index 33546f745..ec7a77321 100644 --- a/test/tools/yulInterpreter/Interpreter.cpp +++ b/test/tools/yulInterpreter/Interpreter.cpp @@ -461,6 +461,9 @@ void ExpressionEvaluator::runExternalCall(evmasm::Instruction _instruction) InterpreterState tmpState; tmpState.calldata = m_state.readMemory(memInOffset, memInSize); tmpState.callvalue = callvalue; + tmpState.numInstance = m_state.numInstance + 1; + + yulAssert(tmpState.numInstance < 1024, "Detected more than 1024 recursive calls, aborting..."); // Create new interpreter for the called contract unique_ptr newInterpreter = makeInterpreterNew(tmpState, tmpScope); diff --git a/test/tools/yulInterpreter/Interpreter.h b/test/tools/yulInterpreter/Interpreter.h index 5dcb328d9..949d0ce4e 100644 --- a/test/tools/yulInterpreter/Interpreter.h +++ b/test/tools/yulInterpreter/Interpreter.h @@ -107,6 +107,9 @@ struct InterpreterState size_t maxExprNesting = 0; ControlFlowState controlFlowState = ControlFlowState::Default; + /// Number of the current state instance, used for recursion protection + size_t numInstance = 0; + /// Prints execution trace and non-zero storage to @param _out. /// Flag @param _disableMemoryTrace, if set, does not produce a memory dump. This /// avoids false positives reports by the fuzzer when certain optimizer steps are