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;