/src/solidity/test/tools/yulInterpreter/Interpreter.h
Line | Count | Source |
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 | | #pragma once |
23 | | |
24 | | #include <libyul/ASTForward.h> |
25 | | #include <libyul/backends/evm/EVMDialect.h> |
26 | | #include <libyul/optimiser/ASTWalker.h> |
27 | | |
28 | | #include <libevmasm/Instruction.h> |
29 | | |
30 | | #include <libsolutil/FixedHash.h> |
31 | | #include <libsolutil/CommonData.h> |
32 | | |
33 | | #include <libsolutil/Exceptions.h> |
34 | | |
35 | | #include <map> |
36 | | |
37 | | namespace solidity::yul |
38 | | { |
39 | | class Dialect; |
40 | | } |
41 | | |
42 | | namespace solidity::yul::test |
43 | | { |
44 | | |
45 | | class InterpreterTerminatedGeneric: public util::Exception |
46 | | { |
47 | | }; |
48 | | |
49 | | class ExplicitlyTerminated: public InterpreterTerminatedGeneric |
50 | | { |
51 | | }; |
52 | | |
53 | | class ExplicitlyTerminatedWithReturn: public ExplicitlyTerminated |
54 | | { |
55 | | }; |
56 | | |
57 | | class StepLimitReached: public InterpreterTerminatedGeneric |
58 | | { |
59 | | }; |
60 | | |
61 | | class TraceLimitReached: public InterpreterTerminatedGeneric |
62 | | { |
63 | | }; |
64 | | |
65 | | class ExpressionNestingLimitReached: public InterpreterTerminatedGeneric |
66 | | { |
67 | | }; |
68 | | |
69 | | enum class ControlFlowState |
70 | | { |
71 | | Default, |
72 | | Continue, |
73 | | Break, |
74 | | Leave |
75 | | }; |
76 | | |
77 | | struct InterpreterState |
78 | | { |
79 | | bytes calldata; |
80 | | bytes returndata; |
81 | | std::map<u256, uint8_t> memory; |
82 | | /// This is different than memory.size() because we ignore gas. |
83 | | u256 msize; |
84 | | std::map<util::h256, util::h256> storage; |
85 | | std::map<util::h256, util::h256> transientStorage; |
86 | | util::h160 address = util::h160("0x0000000000000000000000000000000011111111"); |
87 | | u256 balance = 0x22222222; |
88 | | u256 selfbalance = 0x22223333; |
89 | | util::h160 origin = util::h160("0x0000000000000000000000000000000033333333"); |
90 | | util::h160 caller = util::h160("0x0000000000000000000000000000000044444444"); |
91 | | u256 callvalue = 0x55555555; |
92 | | /// Deployed code |
93 | | bytes code = util::asBytes("codecodecodecodecode"); |
94 | | u256 gasprice = 0x66666666; |
95 | | util::h160 coinbase = util::h160("0x0000000000000000000000000000000077777777"); |
96 | | u256 timestamp = 0x88888888; |
97 | | u256 blockNumber = 1024; |
98 | | u256 difficulty = 0x9999999; |
99 | | u256 prevrandao = (u256(1) << 64) + 1; |
100 | | u256 gaslimit = 4000000; |
101 | | u256 chainid = 0x01; |
102 | | /// The minimum value of basefee: 7 wei. |
103 | | u256 basefee = 0x07; |
104 | | /// The minimum value of blobbasefee: 1 wei. |
105 | | u256 blobbasefee = 0x01; |
106 | | u256 slotnum = 0xaaaaaaaa; |
107 | | /// Log of changes / effects. Sholud be structured data in the future. |
108 | | std::vector<std::string> trace; |
109 | | /// This is actually an input parameter that more or less limits the runtime. |
110 | | size_t maxTraceSize = 0; |
111 | | size_t maxSteps = 0; |
112 | | size_t numSteps = 0; |
113 | | size_t maxExprNesting = 0; |
114 | | ControlFlowState controlFlowState = ControlFlowState::Default; |
115 | | |
116 | | /// Number of the current state instance, used for recursion protection |
117 | | size_t numInstance = 0; |
118 | | |
119 | | // Blob commitment hash version |
120 | | util::FixedHash<1> const blobHashVersion = util::FixedHash<1>(1); |
121 | | // Blob commitments |
122 | | std::array<u256, 2> const blobCommitments = {0x01, 0x02}; |
123 | | |
124 | | /// Prints execution trace and non-zero storage to @param _out. |
125 | | /// Flag @param _disableMemoryTrace, if set, does not produce a memory dump. This |
126 | | /// avoids false positives reports by the fuzzer when certain optimizer steps are |
127 | | /// activated e.g., Redundant store eliminator, Equal store eliminator. |
128 | | void dumpTraceAndState(std::ostream& _out, bool _disableMemoryTrace) const; |
129 | | /// Prints non-zero storage to @param _out. |
130 | | void dumpStorage(std::ostream& _out) const; |
131 | | /// Prints non-zero transient storage to @param _out. |
132 | | void dumpTransientStorage(std::ostream& _out) const; |
133 | | |
134 | | bytes readMemory(u256 const& _offset, u256 const& _size) |
135 | 193k | { |
136 | 193k | yulAssert(_size <= 0xffff, "Too large read."); |
137 | 193k | bytes data(size_t(_size), uint8_t(0)); |
138 | 252M | for (size_t i = 0; i < data.size(); ++i) |
139 | 251M | data[i] = memory[_offset + i]; |
140 | 193k | return data; |
141 | 193k | } |
142 | | }; |
143 | | |
144 | | /** |
145 | | * Scope structure built and maintained during execution. |
146 | | */ |
147 | | struct Scope |
148 | | { |
149 | | /// Used for variables and functions. Value is nullptr for variables. |
150 | | std::map<YulName, FunctionDefinition const*> names; |
151 | | std::map<Block const*, std::unique_ptr<Scope>> subScopes; |
152 | | Scope* parent = nullptr; |
153 | | }; |
154 | | |
155 | | /** |
156 | | * Yul interpreter. |
157 | | */ |
158 | | class Interpreter: public ASTWalker |
159 | | { |
160 | | public: |
161 | | /// Executes the Yul interpreter. Flag @param _disableMemoryTracing if set ensures that |
162 | | /// instructions that write to memory do not affect @param _state. This |
163 | | /// avoids false positives reports by the fuzzer when certain optimizer steps are |
164 | | /// activated e.g., Redundant store eliminator, Equal store eliminator. |
165 | | static void run( |
166 | | InterpreterState& _state, |
167 | | AST const& _ast, |
168 | | bool _disableExternalCalls, |
169 | | bool _disableMemoryTracing |
170 | | ); |
171 | | |
172 | | Interpreter( |
173 | | InterpreterState& _state, |
174 | | Dialect const& _dialect, |
175 | | Scope& _scope, |
176 | | bool _disableExternalCalls, |
177 | | bool _disableMemoryTracing, |
178 | | std::map<YulName, u256> _variables = {} |
179 | | ): |
180 | 332k | m_dialect(_dialect), |
181 | 332k | m_state(_state), |
182 | 332k | m_variables(std::move(_variables)), |
183 | 332k | m_scope(&_scope), |
184 | 332k | m_disableExternalCalls(_disableExternalCalls), |
185 | 332k | m_disableMemoryTrace(_disableMemoryTracing) |
186 | 332k | { |
187 | 332k | } |
188 | | |
189 | | void operator()(ExpressionStatement const& _statement) override; |
190 | | void operator()(Assignment const& _assignment) override; |
191 | | void operator()(VariableDeclaration const& _varDecl) override; |
192 | | void operator()(If const& _if) override; |
193 | | void operator()(Switch const& _switch) override; |
194 | | void operator()(FunctionDefinition const&) override; |
195 | | void operator()(ForLoop const&) override; |
196 | | void operator()(Break const&) override; |
197 | | void operator()(Continue const&) override; |
198 | | void operator()(Leave const&) override; |
199 | | void operator()(Block const& _block) override; |
200 | | |
201 | 0 | bytes returnData() const { return m_state.returndata; } |
202 | 0 | std::vector<std::string> const& trace() const { return m_state.trace; } |
203 | | |
204 | 132k | u256 valueOfVariable(YulName _name) const { return m_variables.at(_name); } |
205 | | |
206 | | protected: |
207 | | /// Asserts that the expression evaluates to exactly one value and returns it. |
208 | | virtual u256 evaluate(Expression const& _expression); |
209 | | /// Evaluates the expression and returns its value. |
210 | | virtual std::vector<u256> evaluateMulti(Expression const& _expression); |
211 | | |
212 | | void enterScope(Block const& _block); |
213 | | void leaveScope(); |
214 | | |
215 | | /// Increment interpreter step count, throwing exception if step limit |
216 | | /// is reached. |
217 | | void incrementStep(); |
218 | | |
219 | | Dialect const& m_dialect; |
220 | | InterpreterState& m_state; |
221 | | /// Values of variables. |
222 | | std::map<YulName, u256> m_variables; |
223 | | Scope* m_scope; |
224 | | /// If not set, external calls (e.g. using `call()`) to the same contract |
225 | | /// are evaluated in a new parser instance. |
226 | | bool m_disableExternalCalls; |
227 | | bool m_disableMemoryTrace; |
228 | | }; |
229 | | |
230 | | /** |
231 | | * Yul expression evaluator. |
232 | | */ |
233 | | class ExpressionEvaluator: public ASTWalker |
234 | | { |
235 | | public: |
236 | | ExpressionEvaluator( |
237 | | InterpreterState& _state, |
238 | | Dialect const& _dialect, |
239 | | Scope& _scope, |
240 | | std::map<YulName, u256> const& _variables, |
241 | | bool _disableExternalCalls, |
242 | | bool _disableMemoryTrace |
243 | | ): |
244 | 1.77M | m_state(_state), |
245 | 1.77M | m_dialect(_dialect), |
246 | 1.77M | m_variables(_variables), |
247 | 1.77M | m_scope(_scope), |
248 | 1.77M | m_disableExternalCalls(_disableExternalCalls), |
249 | 1.77M | m_disableMemoryTrace(_disableMemoryTrace) |
250 | 1.77M | {} |
251 | | |
252 | | void operator()(Literal const&) override; |
253 | | void operator()(Identifier const&) override; |
254 | | void operator()(FunctionCall const& _funCall) override; |
255 | | |
256 | | /// Asserts that the expression has exactly one value and returns it. |
257 | | u256 value() const; |
258 | | /// Returns the list of values of the expression. |
259 | 4.82M | std::vector<u256> values() const { return m_values; } |
260 | | |
261 | | protected: |
262 | | void runExternalCall(evmasm::Instruction _instruction); |
263 | | virtual std::unique_ptr<Interpreter> makeInterpreterCopy(std::map<YulName, u256> _variables = {}) const |
264 | 235k | { |
265 | 235k | return std::make_unique<Interpreter>( |
266 | 235k | m_state, |
267 | 235k | m_dialect, |
268 | 235k | m_scope, |
269 | 235k | m_disableExternalCalls, |
270 | 235k | m_disableMemoryTrace, |
271 | 235k | std::move(_variables) |
272 | 235k | ); |
273 | 235k | } |
274 | | virtual std::unique_ptr<Interpreter> makeInterpreterNew(InterpreterState& _state, Scope& _scope) const |
275 | 0 | { |
276 | 0 | return std::make_unique<Interpreter>( |
277 | 0 | _state, |
278 | 0 | m_dialect, |
279 | 0 | _scope, |
280 | 0 | m_disableExternalCalls, |
281 | 0 | m_disableMemoryTrace |
282 | 0 | ); |
283 | 0 | } |
284 | | |
285 | | void setValue(u256 _value); |
286 | | |
287 | | /// Evaluates the given expression from right to left and |
288 | | /// stores it in m_value. |
289 | | void evaluateArgs( |
290 | | std::vector<Expression> const& _expr, |
291 | | std::vector<std::optional<LiteralKind>> const* _literalArguments |
292 | | ); |
293 | | |
294 | | /// Increment evaluation count, throwing exception if the |
295 | | /// nesting level is beyond the upper bound configured in |
296 | | /// the interpreter state. |
297 | | void incrementStep(); |
298 | | |
299 | | InterpreterState& m_state; |
300 | | Dialect const& m_dialect; |
301 | | /// Values of variables. |
302 | | std::map<YulName, u256> const& m_variables; |
303 | | Scope& m_scope; |
304 | | /// Current value of the expression |
305 | | std::vector<u256> m_values; |
306 | | /// Current expression nesting level |
307 | | unsigned m_nestingLevel = 0; |
308 | | bool m_disableExternalCalls; |
309 | | /// Flag to disable memory tracing |
310 | | bool m_disableMemoryTrace; |
311 | | }; |
312 | | |
313 | | } |