/src/solidity/libevmasm/AssemblyItem.cpp
Line | Count | Source (jump to first uncovered line) |
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 | | #include <libevmasm/AssemblyItem.h> |
20 | | |
21 | | #include <libevmasm/Assembly.h> |
22 | | |
23 | | #include <libsolutil/CommonData.h> |
24 | | #include <libsolutil/CommonIO.h> |
25 | | #include <libsolutil/Numeric.h> |
26 | | #include <libsolutil/StringUtils.h> |
27 | | #include <libsolutil/FixedHash.h> |
28 | | #include <liblangutil/SourceLocation.h> |
29 | | |
30 | | #include <fstream> |
31 | | #include <limits> |
32 | | |
33 | | using namespace std; |
34 | | using namespace solidity; |
35 | | using namespace solidity::evmasm; |
36 | | using namespace solidity::langutil; |
37 | | |
38 | | static_assert(sizeof(size_t) <= 8, "size_t must be at most 64-bits wide"); |
39 | | |
40 | | namespace |
41 | | { |
42 | | |
43 | | string toStringInHex(u256 _value) |
44 | 0 | { |
45 | 0 | std::stringstream hexStr; |
46 | 0 | hexStr << std::uppercase << hex << _value; |
47 | 0 | return hexStr.str(); |
48 | 0 | } |
49 | | |
50 | | } |
51 | | |
52 | | AssemblyItem AssemblyItem::toSubAssemblyTag(size_t _subId) const |
53 | 0 | { |
54 | 0 | assertThrow(data() < (u256(1) << 64), util::Exception, "Tag already has subassembly set."); |
55 | 0 | assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); |
56 | 0 | auto tag = static_cast<size_t>(u256(data()) & 0xffffffffffffffffULL); |
57 | 0 | AssemblyItem r = *this; |
58 | 0 | r.m_type = PushTag; |
59 | 0 | r.setPushTagSubIdAndTag(_subId, tag); |
60 | 0 | return r; |
61 | 0 | } |
62 | | |
63 | | pair<size_t, size_t> AssemblyItem::splitForeignPushTag() const |
64 | 40.4k | { |
65 | 40.4k | assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); |
66 | 40.4k | u256 combined = u256(data()); |
67 | 40.4k | size_t subId = static_cast<size_t>((combined >> 64) - 1); |
68 | 40.4k | size_t tag = static_cast<size_t>(combined & 0xffffffffffffffffULL); |
69 | 40.4k | return make_pair(subId, tag); |
70 | 40.4k | } |
71 | | |
72 | | pair<string, string> AssemblyItem::nameAndData() const |
73 | 0 | { |
74 | 0 | switch (type()) |
75 | 0 | { |
76 | 0 | case Operation: |
77 | 0 | return {instructionInfo(instruction()).name, m_data != nullptr ? toStringInHex(*m_data) : ""}; |
78 | 0 | case Push: |
79 | 0 | return {"PUSH", toStringInHex(data())}; |
80 | 0 | case PushTag: |
81 | 0 | if (data() == 0) |
82 | 0 | return {"PUSH [ErrorTag]", ""}; |
83 | 0 | else |
84 | 0 | return {"PUSH [tag]", util::toString(data())}; |
85 | 0 | case PushSub: |
86 | 0 | return {"PUSH [$]", toString(util::h256(data()))}; |
87 | 0 | case PushSubSize: |
88 | 0 | return {"PUSH #[$]", toString(util::h256(data()))}; |
89 | 0 | case PushProgramSize: |
90 | 0 | return {"PUSHSIZE", ""}; |
91 | 0 | case PushLibraryAddress: |
92 | 0 | return {"PUSHLIB", toString(util::h256(data()))}; |
93 | 0 | case PushDeployTimeAddress: |
94 | 0 | return {"PUSHDEPLOYADDRESS", ""}; |
95 | 0 | case PushImmutable: |
96 | 0 | return {"PUSHIMMUTABLE", toString(util::h256(data()))}; |
97 | 0 | case AssignImmutable: |
98 | 0 | return {"ASSIGNIMMUTABLE", toString(util::h256(data()))}; |
99 | 0 | case Tag: |
100 | 0 | return {"tag", util::toString(data())}; |
101 | 0 | case PushData: |
102 | 0 | return {"PUSH data", toStringInHex(data())}; |
103 | 0 | case VerbatimBytecode: |
104 | 0 | return {"VERBATIM", util::toHex(verbatimData())}; |
105 | 0 | default: |
106 | 0 | assertThrow(false, InvalidOpcode, ""); |
107 | 0 | } |
108 | 0 | } |
109 | | |
110 | | void AssemblyItem::setPushTagSubIdAndTag(size_t _subId, size_t _tag) |
111 | 0 | { |
112 | 0 | assertThrow(m_type == PushTag || m_type == Tag, util::Exception, ""); |
113 | 0 | u256 data = _tag; |
114 | 0 | if (_subId != numeric_limits<size_t>::max()) |
115 | 0 | data |= (u256(_subId) + 1) << 64; |
116 | 0 | setData(data); |
117 | 0 | } |
118 | | |
119 | | size_t AssemblyItem::bytesRequired(size_t _addressLength, Precision _precision) const |
120 | 290k | { |
121 | 290k | switch (m_type) |
122 | 290k | { |
123 | 172k | case Operation: |
124 | 205k | case Tag: // 1 byte for the JUMPDEST |
125 | 205k | return 1; |
126 | 50.1k | case Push: |
127 | 50.1k | return 1 + max<size_t>(1, numberEncodingSize(data())); |
128 | 2.50k | case PushSubSize: |
129 | 2.50k | case PushProgramSize: |
130 | 2.50k | return 1 + 4; // worst case: a 16MB program |
131 | 30.0k | case PushTag: |
132 | 30.0k | case PushData: |
133 | 32.5k | case PushSub: |
134 | 32.5k | return 1 + _addressLength; |
135 | 0 | case PushLibraryAddress: |
136 | 0 | case PushDeployTimeAddress: |
137 | 0 | return 1 + 20; |
138 | 0 | case PushImmutable: |
139 | 0 | return 1 + 32; |
140 | 0 | case AssignImmutable: |
141 | 0 | { |
142 | 0 | unsigned long immutableOccurrences = 0; |
143 | | |
144 | | // Skip exact immutables count if no precise count was requested |
145 | 0 | if (_precision == Precision::Approximate) |
146 | 0 | immutableOccurrences = 1; // Assume one immut. ref. |
147 | 0 | else |
148 | 0 | { |
149 | 0 | solAssert(m_immutableOccurrences, "No immutable references. `bytesRequired()` called before assembly()?"); |
150 | 0 | immutableOccurrences = m_immutableOccurrences.value(); |
151 | 0 | } |
152 | | |
153 | 0 | if (immutableOccurrences != 0) |
154 | | // (DUP DUP PUSH <n> ADD MSTORE)* (PUSH <n> ADD MSTORE) |
155 | 0 | return (immutableOccurrences - 1) * (5 + 32) + (3 + 32); |
156 | 0 | else |
157 | | // POP POP |
158 | 0 | return 2; |
159 | 0 | } |
160 | 0 | case VerbatimBytecode: |
161 | 0 | return std::get<2>(*m_verbatimBytecode).size(); |
162 | 0 | default: |
163 | 0 | break; |
164 | 290k | } |
165 | 0 | assertThrow(false, InvalidOpcode, ""); |
166 | 0 | } |
167 | | |
168 | | size_t AssemblyItem::arguments() const |
169 | 47.2k | { |
170 | 47.2k | if (type() == Operation) |
171 | 25.7k | return static_cast<size_t>(instructionInfo(instruction()).args); |
172 | 21.4k | else if (type() == VerbatimBytecode) |
173 | 0 | return get<0>(*m_verbatimBytecode); |
174 | 21.4k | else if (type() == AssignImmutable) |
175 | 0 | return 2; |
176 | 21.4k | else |
177 | 21.4k | return 0; |
178 | 47.2k | } |
179 | | |
180 | | size_t AssemblyItem::returnValues() const |
181 | 47.2k | { |
182 | 47.2k | switch (m_type) |
183 | 47.2k | { |
184 | 25.7k | case Operation: |
185 | 25.7k | return static_cast<size_t>(instructionInfo(instruction()).ret); |
186 | 8.59k | case Push: |
187 | 14.3k | case PushTag: |
188 | 14.3k | case PushData: |
189 | 14.6k | case PushSub: |
190 | 15.0k | case PushSubSize: |
191 | 15.0k | case PushProgramSize: |
192 | 15.0k | case PushLibraryAddress: |
193 | 15.0k | case PushImmutable: |
194 | 15.0k | case PushDeployTimeAddress: |
195 | 15.0k | return 1; |
196 | 6.44k | case Tag: |
197 | 6.44k | return 0; |
198 | 0 | case VerbatimBytecode: |
199 | 0 | return get<1>(*m_verbatimBytecode); |
200 | 0 | default: |
201 | 0 | break; |
202 | 47.2k | } |
203 | 0 | return 0; |
204 | 47.2k | } |
205 | | |
206 | | bool AssemblyItem::canBeFunctional() const |
207 | 0 | { |
208 | 0 | if (m_jumpType != JumpType::Ordinary) |
209 | 0 | return false; |
210 | 0 | switch (m_type) |
211 | 0 | { |
212 | 0 | case Operation: |
213 | 0 | return !isDupInstruction(instruction()) && !isSwapInstruction(instruction()); |
214 | 0 | case Push: |
215 | 0 | case PushTag: |
216 | 0 | case PushData: |
217 | 0 | case PushSub: |
218 | 0 | case PushSubSize: |
219 | 0 | case PushProgramSize: |
220 | 0 | case PushLibraryAddress: |
221 | 0 | case PushDeployTimeAddress: |
222 | 0 | case PushImmutable: |
223 | 0 | return true; |
224 | 0 | case Tag: |
225 | 0 | return false; |
226 | 0 | default: |
227 | 0 | break; |
228 | 0 | } |
229 | 0 | return false; |
230 | 0 | } |
231 | | |
232 | | string AssemblyItem::getJumpTypeAsString() const |
233 | 0 | { |
234 | 0 | switch (m_jumpType) |
235 | 0 | { |
236 | 0 | case JumpType::IntoFunction: |
237 | 0 | return "[in]"; |
238 | 0 | case JumpType::OutOfFunction: |
239 | 0 | return "[out]"; |
240 | 0 | case JumpType::Ordinary: |
241 | 0 | default: |
242 | 0 | return ""; |
243 | 0 | } |
244 | 0 | } |
245 | | |
246 | | string AssemblyItem::toAssemblyText(Assembly const& _assembly) const |
247 | 0 | { |
248 | 0 | string text; |
249 | 0 | switch (type()) |
250 | 0 | { |
251 | 0 | case Operation: |
252 | 0 | { |
253 | 0 | assertThrow(isValidInstruction(instruction()), AssemblyException, "Invalid instruction."); |
254 | 0 | text = util::toLower(instructionInfo(instruction()).name); |
255 | 0 | break; |
256 | 0 | } |
257 | 0 | case Push: |
258 | 0 | text = toHex(toCompactBigEndian(data(), 1), util::HexPrefix::Add); |
259 | 0 | break; |
260 | 0 | case PushTag: |
261 | 0 | { |
262 | 0 | size_t sub{0}; |
263 | 0 | size_t tag{0}; |
264 | 0 | tie(sub, tag) = splitForeignPushTag(); |
265 | 0 | if (sub == numeric_limits<size_t>::max()) |
266 | 0 | text = string("tag_") + to_string(tag); |
267 | 0 | else |
268 | 0 | text = string("tag_") + to_string(sub) + "_" + to_string(tag); |
269 | 0 | break; |
270 | 0 | } |
271 | 0 | case Tag: |
272 | 0 | assertThrow(data() < 0x10000, AssemblyException, "Declaration of sub-assembly tag."); |
273 | 0 | text = string("tag_") + to_string(static_cast<size_t>(data())) + ":"; |
274 | 0 | break; |
275 | 0 | case PushData: |
276 | 0 | text = string("data_") + toHex(data()); |
277 | 0 | break; |
278 | 0 | case PushSub: |
279 | 0 | case PushSubSize: |
280 | 0 | { |
281 | 0 | vector<string> subPathComponents; |
282 | 0 | for (size_t subPathComponentId: _assembly.decodeSubPath(static_cast<size_t>(data()))) |
283 | 0 | subPathComponents.emplace_back("sub_" + to_string(subPathComponentId)); |
284 | 0 | text = |
285 | 0 | (type() == PushSub ? "dataOffset"s : "dataSize"s) + |
286 | 0 | "(" + |
287 | 0 | solidity::util::joinHumanReadable(subPathComponents, ".") + |
288 | 0 | ")"; |
289 | 0 | break; |
290 | 0 | } |
291 | 0 | case PushProgramSize: |
292 | 0 | text = string("bytecodeSize"); |
293 | 0 | break; |
294 | 0 | case PushLibraryAddress: |
295 | 0 | text = string("linkerSymbol(\"") + toHex(data()) + string("\")"); |
296 | 0 | break; |
297 | 0 | case PushDeployTimeAddress: |
298 | 0 | text = string("deployTimeAddress()"); |
299 | 0 | break; |
300 | 0 | case PushImmutable: |
301 | 0 | text = string("immutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")"; |
302 | 0 | break; |
303 | 0 | case AssignImmutable: |
304 | 0 | text = string("assignImmutable(\"") + "0x" + util::toHex(toCompactBigEndian(data(), 1)) + "\")"; |
305 | 0 | break; |
306 | 0 | case UndefinedItem: |
307 | 0 | assertThrow(false, AssemblyException, "Invalid assembly item."); |
308 | 0 | break; |
309 | 0 | case VerbatimBytecode: |
310 | 0 | text = string("verbatimbytecode_") + util::toHex(get<2>(*m_verbatimBytecode)); |
311 | 0 | break; |
312 | 0 | default: |
313 | 0 | assertThrow(false, InvalidOpcode, ""); |
314 | 0 | } |
315 | 0 | if (m_jumpType == JumpType::IntoFunction || m_jumpType == JumpType::OutOfFunction) |
316 | 0 | { |
317 | 0 | text += "\t//"; |
318 | 0 | if (m_jumpType == JumpType::IntoFunction) |
319 | 0 | text += " in"; |
320 | 0 | else |
321 | 0 | text += " out"; |
322 | 0 | } |
323 | 0 | return text; |
324 | 0 | } |
325 | | |
326 | | ostream& solidity::evmasm::operator<<(ostream& _out, AssemblyItem const& _item) |
327 | 0 | { |
328 | 0 | switch (_item.type()) |
329 | 0 | { |
330 | 0 | case Operation: |
331 | 0 | _out << " " << instructionInfo(_item.instruction()).name; |
332 | 0 | if (_item.instruction() == Instruction::JUMP || _item.instruction() == Instruction::JUMPI) |
333 | 0 | _out << "\t" << _item.getJumpTypeAsString(); |
334 | 0 | break; |
335 | 0 | case Push: |
336 | 0 | _out << " PUSH " << hex << _item.data() << dec; |
337 | 0 | break; |
338 | 0 | case PushTag: |
339 | 0 | { |
340 | 0 | size_t subId = _item.splitForeignPushTag().first; |
341 | 0 | if (subId == numeric_limits<size_t>::max()) |
342 | 0 | _out << " PushTag " << _item.splitForeignPushTag().second; |
343 | 0 | else |
344 | 0 | _out << " PushTag " << subId << ":" << _item.splitForeignPushTag().second; |
345 | 0 | break; |
346 | 0 | } |
347 | 0 | case Tag: |
348 | 0 | _out << " Tag " << _item.data(); |
349 | 0 | break; |
350 | 0 | case PushData: |
351 | 0 | _out << " PushData " << hex << static_cast<unsigned>(_item.data()) << dec; |
352 | 0 | break; |
353 | 0 | case PushSub: |
354 | 0 | _out << " PushSub " << hex << static_cast<size_t>(_item.data()) << dec; |
355 | 0 | break; |
356 | 0 | case PushSubSize: |
357 | 0 | _out << " PushSubSize " << hex << static_cast<size_t>(_item.data()) << dec; |
358 | 0 | break; |
359 | 0 | case PushProgramSize: |
360 | 0 | _out << " PushProgramSize"; |
361 | 0 | break; |
362 | 0 | case PushLibraryAddress: |
363 | 0 | { |
364 | 0 | string hash(util::h256((_item.data())).hex()); |
365 | 0 | _out << " PushLibraryAddress " << hash.substr(0, 8) + "..." + hash.substr(hash.length() - 8); |
366 | 0 | break; |
367 | 0 | } |
368 | 0 | case PushDeployTimeAddress: |
369 | 0 | _out << " PushDeployTimeAddress"; |
370 | 0 | break; |
371 | 0 | case PushImmutable: |
372 | 0 | _out << " PushImmutable"; |
373 | 0 | break; |
374 | 0 | case AssignImmutable: |
375 | 0 | _out << " AssignImmutable"; |
376 | 0 | break; |
377 | 0 | case VerbatimBytecode: |
378 | 0 | _out << " Verbatim " << util::toHex(_item.verbatimData()); |
379 | 0 | break; |
380 | 0 | case UndefinedItem: |
381 | 0 | _out << " ???"; |
382 | 0 | break; |
383 | 0 | default: |
384 | 0 | assertThrow(false, InvalidOpcode, ""); |
385 | 0 | } |
386 | 0 | return _out; |
387 | 0 | } |
388 | | |
389 | | size_t AssemblyItem::opcodeCount() const noexcept |
390 | 0 | { |
391 | 0 | switch (m_type) |
392 | 0 | { |
393 | 0 | case AssemblyItemType::AssignImmutable: |
394 | | // Append empty items if this AssignImmutable was referenced more than once. |
395 | | // For n immutable occurrences the first (n - 1) occurrences will |
396 | | // generate 5 opcodes and the last will generate 3 opcodes, |
397 | | // because it is reusing the 2 top-most elements on the stack. |
398 | 0 | solAssert(m_immutableOccurrences, ""); |
399 | | |
400 | 0 | if (m_immutableOccurrences.value() != 0) |
401 | 0 | return (*m_immutableOccurrences - 1) * 5 + 3; |
402 | 0 | else |
403 | 0 | return 2; // two POP's |
404 | 0 | default: |
405 | 0 | return 1; |
406 | 0 | } |
407 | 0 | } |
408 | | |
409 | | std::string AssemblyItem::computeSourceMapping( |
410 | | AssemblyItems const& _items, |
411 | | map<string, unsigned> const& _sourceIndicesMap |
412 | | ) |
413 | 0 | { |
414 | 0 | string ret; |
415 | |
|
416 | 0 | int prevStart = -1; |
417 | 0 | int prevLength = -1; |
418 | 0 | int prevSourceIndex = -1; |
419 | 0 | int prevModifierDepth = -1; |
420 | 0 | char prevJump = 0; |
421 | |
|
422 | 0 | for (auto const& item: _items) |
423 | 0 | { |
424 | 0 | if (!ret.empty()) |
425 | 0 | ret += ";"; |
426 | |
|
427 | 0 | SourceLocation const& location = item.location(); |
428 | 0 | int length = location.start != -1 && location.end != -1 ? location.end - location.start : -1; |
429 | 0 | int sourceIndex = |
430 | 0 | (location.sourceName && _sourceIndicesMap.count(*location.sourceName)) ? |
431 | 0 | static_cast<int>(_sourceIndicesMap.at(*location.sourceName)) : |
432 | 0 | -1; |
433 | 0 | char jump = '-'; |
434 | 0 | if (item.getJumpType() == evmasm::AssemblyItem::JumpType::IntoFunction) |
435 | 0 | jump = 'i'; |
436 | 0 | else if (item.getJumpType() == evmasm::AssemblyItem::JumpType::OutOfFunction) |
437 | 0 | jump = 'o'; |
438 | 0 | int modifierDepth = static_cast<int>(item.m_modifierDepth); |
439 | |
|
440 | 0 | unsigned components = 5; |
441 | 0 | if (modifierDepth == prevModifierDepth) |
442 | 0 | { |
443 | 0 | components--; |
444 | 0 | if (jump == prevJump) |
445 | 0 | { |
446 | 0 | components--; |
447 | 0 | if (sourceIndex == prevSourceIndex) |
448 | 0 | { |
449 | 0 | components--; |
450 | 0 | if (length == prevLength) |
451 | 0 | { |
452 | 0 | components--; |
453 | 0 | if (location.start == prevStart) |
454 | 0 | components--; |
455 | 0 | } |
456 | 0 | } |
457 | 0 | } |
458 | 0 | } |
459 | |
|
460 | 0 | if (components-- > 0) |
461 | 0 | { |
462 | 0 | if (location.start != prevStart) |
463 | 0 | ret += to_string(location.start); |
464 | 0 | if (components-- > 0) |
465 | 0 | { |
466 | 0 | ret += ':'; |
467 | 0 | if (length != prevLength) |
468 | 0 | ret += to_string(length); |
469 | 0 | if (components-- > 0) |
470 | 0 | { |
471 | 0 | ret += ':'; |
472 | 0 | if (sourceIndex != prevSourceIndex) |
473 | 0 | ret += to_string(sourceIndex); |
474 | 0 | if (components-- > 0) |
475 | 0 | { |
476 | 0 | ret += ':'; |
477 | 0 | if (jump != prevJump) |
478 | 0 | ret += jump; |
479 | 0 | if (components-- > 0) |
480 | 0 | { |
481 | 0 | ret += ':'; |
482 | 0 | if (modifierDepth != prevModifierDepth) |
483 | 0 | ret += to_string(modifierDepth); |
484 | 0 | } |
485 | 0 | } |
486 | 0 | } |
487 | 0 | } |
488 | 0 | } |
489 | |
|
490 | 0 | if (item.opcodeCount() > 1) |
491 | 0 | ret += string(item.opcodeCount() - 1, ';'); |
492 | |
|
493 | 0 | prevStart = location.start; |
494 | 0 | prevLength = length; |
495 | 0 | prevSourceIndex = sourceIndex; |
496 | 0 | prevJump = jump; |
497 | 0 | prevModifierDepth = modifierDepth; |
498 | 0 | } |
499 | 0 | return ret; |
500 | 0 | } |