Skip to content

Commit b8699e7

Browse files
committed
YulRunner: Add support for interactive inspection of the state
1 parent 4b69b5f commit b8699e7

File tree

6 files changed

+415
-7
lines changed

6 files changed

+415
-7
lines changed

test/tools/yulInterpreter/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ set(sources
55
EwasmBuiltinInterpreter.cpp
66
Interpreter.h
77
Interpreter.cpp
8+
Inspector.h
9+
Inspector.cpp
810
)
911

1012
add_library(yulInterpreter ${sources})
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
/**
19+
* Yul interpreter.
20+
*/
21+
22+
#include <test/tools/yulInterpreter/Inspector.h>
23+
24+
#include <boost/algorithm/string/predicate.hpp>
25+
#include <boost/algorithm/string.hpp>
26+
27+
using namespace solidity;
28+
using namespace solidity::yul;
29+
using namespace solidity::yul::test;
30+
31+
using namespace std;
32+
33+
namespace
34+
{
35+
36+
void printVariable(YulString const& _name, u256 const& _value)
37+
{
38+
cout << "\t" << _name.str() << " = " << _value.str();
39+
40+
if (_value != 0)
41+
cout << " (" << toCompactHexWithPrefix(_value) << ")";
42+
43+
cout << endl;
44+
}
45+
46+
}
47+
48+
void InspectedInterpreter::run(
49+
std::shared_ptr<Inspector> _inspector,
50+
InterpreterState& _state,
51+
Dialect const& _dialect,
52+
Block const& _ast,
53+
bool _disableExternalCalls,
54+
bool _disableMemoryTrace
55+
)
56+
{
57+
Scope scope;
58+
InspectedInterpreter{_inspector, _state, _dialect, scope, _disableExternalCalls, _disableMemoryTrace}(_ast);
59+
}
60+
61+
Inspector::NodeAction Inspector::queryUser(DebugData const& _data, map<YulString, u256> const& _variables)
62+
{
63+
if (m_stepMode == NodeAction::RunNode)
64+
{
65+
// Output instructions that are being skipped/run
66+
cout << "Running " << currentSource(_data) << endl;
67+
68+
return NodeAction::StepThroughNode;
69+
}
70+
71+
string input;
72+
73+
while (true)
74+
{
75+
// Output sourcecode about to run.
76+
cout << "> " << currentSource(_data) << endl;
77+
78+
// Ask user for action
79+
cout << endl
80+
<< "(s)tep/(n)ext/(i)nspect/(p)rint/all (v)ariables?"
81+
<< endl
82+
<< "# ";
83+
84+
cout.flush();
85+
86+
getline(cin, input);
87+
boost::algorithm::trim(input);
88+
89+
// Imitate GDB and repeat last cmd for empty string input.
90+
if (input == "")
91+
input = m_lastInput;
92+
else
93+
m_lastInput = input;
94+
95+
if (input == "next" || input == "n")
96+
return NodeAction::RunNode;
97+
else if (input == "step" || input == "s")
98+
return NodeAction::StepThroughNode;
99+
else if (input == "inspect" || input == "i")
100+
m_state.dumpTraceAndState(cout, false);
101+
else if (input == "variables" || input == "v")
102+
{
103+
for (auto &&[yulStr, val]: _variables)
104+
printVariable(yulStr, val);
105+
cout << endl;
106+
}
107+
else if (
108+
boost::starts_with(input, "print") ||
109+
boost::starts_with(input, "p")
110+
)
111+
{
112+
size_t whitespacePos = input.find(' ');
113+
114+
if (whitespacePos == string::npos)
115+
cout << "Error parsing command! Expected variable name." << endl;
116+
117+
string const varname = input.substr(whitespacePos + 1);
118+
119+
vector<string> candidates;
120+
121+
bool found = false;
122+
for (auto &&[yulStr, val]: _variables)
123+
if (yulStr.str() == varname)
124+
{
125+
printVariable(yulStr, val);
126+
found = true;
127+
break;
128+
}
129+
130+
if (!found)
131+
cout << varname << " not found." << endl;
132+
}
133+
}
134+
}
135+
136+
std::string Inspector::currentSource(DebugData const& _data) const
137+
{
138+
return m_source.substr(
139+
static_cast<size_t>(_data.nativeLocation.start),
140+
static_cast<size_t>(_data.nativeLocation.end - _data.nativeLocation.start)
141+
);
142+
}
143+
144+
u256 InspectedInterpreter::evaluate(Expression const& _expression)
145+
{
146+
InspectedExpressionEvaluator ev(m_inspector, m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace);
147+
ev.visit(_expression);
148+
return ev.value();
149+
}
150+
151+
std::vector<u256> InspectedInterpreter::evaluateMulti(Expression const& _expression)
152+
{
153+
InspectedExpressionEvaluator ev(m_inspector, m_state, m_dialect, *m_scope, m_variables, m_disableExternalCalls, m_disableMemoryTrace);
154+
ev.visit(_expression);
155+
return ev.values();
156+
}

test/tools/yulInterpreter/Inspector.h

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
/*
2+
This file is part of solidity.
3+
4+
solidity is free software: you can redistribute it and/or modify
5+
it under the terms of the GNU General Public License as published by
6+
the Free Software Foundation, either version 3 of the License, or
7+
(at your option) any later version.
8+
9+
solidity is distributed in the hope that it will be useful,
10+
but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
GNU General Public License for more details.
13+
14+
You should have received a copy of the GNU General Public License
15+
along with solidity. If not, see <http://www.gnu.org/licenses/>.
16+
*/
17+
// SPDX-License-Identifier: GPL-3.0
18+
/**
19+
* Yul inspector.
20+
*/
21+
22+
#include <test/tools/yulInterpreter/Interpreter.h>
23+
24+
#include <libyul/AST.h>
25+
26+
#include <string>
27+
28+
#pragma once
29+
30+
namespace solidity::yul::test
31+
{
32+
33+
/**
34+
* Inspector class to respond to queries by the user for:
35+
*
36+
* * Stepping through and over instructions.
37+
* * Printing a specific or all variables.
38+
* * Inspecting memory, storage and calldata memory.
39+
*/
40+
class Inspector
41+
{
42+
public:
43+
enum class NodeAction
44+
{
45+
RunNode,
46+
StepThroughNode,
47+
};
48+
49+
Inspector(std::string const& _source, InterpreterState const& _state)
50+
:m_source(_source), m_state(_state) {}
51+
52+
/* Asks the user what action to take.
53+
* @returns NodeAction::RunNode if the current AST node (and all children nodes!) should be
54+
* processed without stopping, else NodeAction::StepThroughNode.
55+
*/
56+
NodeAction queryUser(DebugData const& _data, std::map<YulString, u256> const& _variables);
57+
58+
void stepMode(NodeAction _action) { m_stepMode = _action; }
59+
60+
std::string const& source() const { return m_source; }
61+
62+
void interactiveVisit(DebugData const& _debugData, std::map<YulString, u256> const& _variables, std::function<void()> _visitNode)
63+
{
64+
Inspector::NodeAction action = queryUser(_debugData, _variables);
65+
66+
if (action == NodeAction::RunNode)
67+
{
68+
// user requested to run the whole node without stopping
69+
stepMode(Inspector::NodeAction::RunNode);
70+
71+
_visitNode();
72+
73+
// Reset step mode back
74+
stepMode(Inspector::NodeAction::StepThroughNode);
75+
}
76+
else
77+
_visitNode();
78+
}
79+
80+
private:
81+
std::string currentSource(DebugData const& _data) const;
82+
83+
/// Source of the file
84+
std::string const& m_source;
85+
86+
/// State of the interpreter
87+
InterpreterState const& m_state;
88+
89+
/// Last user query command
90+
std::string m_lastInput;
91+
92+
/// Used to run AST nodes without user interaction
93+
NodeAction m_stepMode = NodeAction::StepThroughNode;
94+
};
95+
96+
/**
97+
* Yul Interpreter with inspection. Allows the user to go through the code step
98+
* by step and inspect the state using the `Inspector` class
99+
*/
100+
class InspectedInterpreter: public Interpreter
101+
{
102+
public:
103+
static void run(
104+
std::shared_ptr<Inspector> _inspector,
105+
InterpreterState& _state,
106+
Dialect const& _dialect,
107+
Block const& _ast,
108+
bool _disableExternalCalls,
109+
bool _disableMemoryTracing
110+
);
111+
112+
InspectedInterpreter(
113+
std::shared_ptr<Inspector> _inspector,
114+
InterpreterState& _state,
115+
Dialect const& _dialect,
116+
Scope& _scope,
117+
bool _disableExternalCalls,
118+
bool _disableMemoryTracing,
119+
std::map<YulString, u256> _variables = {}
120+
):
121+
Interpreter(_state, _dialect, _scope, _disableExternalCalls, _disableMemoryTracing, _variables),
122+
m_inspector(_inspector)
123+
{
124+
}
125+
126+
void operator()(ExpressionStatement const& _node) override { helper(_node); }
127+
void operator()(Assignment const& _node) override { helper(_node); }
128+
void operator()(VariableDeclaration const& _node) override { helper(_node); }
129+
void operator()(If const& _node) override { helper(_node); }
130+
void operator()(Switch const& _node) override { helper(_node); }
131+
void operator()(ForLoop const& _node) override { helper(_node); }
132+
void operator()(Break const& _node) override { helper(_node); }
133+
void operator()(Continue const& _node) override { helper(_node); }
134+
void operator()(Leave const& _node) override { helper(_node); }
135+
void operator()(Block const& _node) override { helper(_node); }
136+
protected:
137+
/// Asserts that the expression evaluates to exactly one value and returns it.
138+
u256 evaluate(Expression const& _expression) override;
139+
/// Evaluates the expression and returns its value.
140+
std::vector<u256> evaluateMulti(Expression const& _expression) override;
141+
private:
142+
std::shared_ptr<Inspector> m_inspector;
143+
144+
template <typename ConcreteNode>
145+
void helper(ConcreteNode const& _node)
146+
{
147+
m_inspector->interactiveVisit(*_node.debugData, m_variables, [&]() {
148+
Interpreter::operator()(_node);
149+
});
150+
}
151+
152+
};
153+
154+
155+
class InspectedExpressionEvaluator: public ExpressionEvaluator
156+
{
157+
public:
158+
InspectedExpressionEvaluator(
159+
std::shared_ptr<Inspector> _inspector,
160+
InterpreterState& _state,
161+
Dialect const& _dialect,
162+
Scope& _scope,
163+
std::map<YulString, u256> const& _variables,
164+
bool _disableExternalCalls,
165+
bool _disableMemoryTrace
166+
):
167+
ExpressionEvaluator(_state, _dialect, _scope, _variables, _disableExternalCalls, _disableMemoryTrace),
168+
m_inspector(_inspector)
169+
{}
170+
171+
template <typename ConcreteNode>
172+
void helper(ConcreteNode const& _node)
173+
{
174+
m_inspector->interactiveVisit(*_node.debugData, m_variables, [&]() {
175+
ExpressionEvaluator::operator()(_node);
176+
});
177+
}
178+
179+
void operator()(Literal const& _node) override { helper(_node); }
180+
void operator()(Identifier const& _node) override { helper(_node); }
181+
void operator()(FunctionCall const& _node) override { helper(_node); }
182+
protected:
183+
std::unique_ptr<Interpreter> makeInterpreterCopy(std::map<YulString, u256> _variables = {}) const override
184+
{
185+
return std::make_unique<InspectedInterpreter>(
186+
m_inspector,
187+
m_state,
188+
m_dialect,
189+
m_scope,
190+
m_disableExternalCalls,
191+
m_disableMemoryTrace,
192+
std::move(_variables)
193+
);
194+
}
195+
std::unique_ptr<Interpreter> makeInterpreterNew(InterpreterState& _state, Scope& _scope) const override
196+
{
197+
return std::make_unique<InspectedInterpreter>(
198+
std::make_unique<Inspector>(
199+
m_inspector->source(),
200+
_state
201+
),
202+
_state,
203+
m_dialect,
204+
_scope,
205+
m_disableExternalCalls,
206+
m_disableMemoryTrace
207+
);
208+
}
209+
private:
210+
std::shared_ptr<Inspector> m_inspector;
211+
};
212+
213+
}

0 commit comments

Comments
 (0)