/src/solidity/libsolidity/codegen/ArrayUtils.cpp
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 | | * @author Christian <c@ethdev.com> |
20 | | * @date 2015 |
21 | | * Code generation utils that handle arrays. |
22 | | */ |
23 | | |
24 | | #include <libsolidity/codegen/ArrayUtils.h> |
25 | | |
26 | | #include <libsolidity/ast/Types.h> |
27 | | #include <libsolidity/ast/TypeProvider.h> |
28 | | #include <libsolidity/codegen/CompilerContext.h> |
29 | | #include <libsolidity/codegen/CompilerUtils.h> |
30 | | #include <libsolidity/codegen/LValue.h> |
31 | | |
32 | | #include <libsolutil/FunctionSelector.h> |
33 | | #include <libsolutil/Whiskers.h> |
34 | | #include <libsolutil/StackTooDeepString.h> |
35 | | |
36 | | #include <libevmasm/Instruction.h> |
37 | | #include <liblangutil/Exceptions.h> |
38 | | |
39 | | using namespace solidity; |
40 | | using namespace solidity::evmasm; |
41 | | using namespace solidity::frontend; |
42 | | using namespace solidity::langutil; |
43 | | |
44 | | void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const |
45 | 9.99k | { |
46 | | // this copies source to target and also clears target if it was larger |
47 | | // need to leave "target_ref target_byte_off" on the stack at the end |
48 | | |
49 | | // stack layout: source_ref [source length] target_ref (top) |
50 | 9.99k | solAssert(_targetType.location() == DataLocation::Storage, ""); |
51 | | |
52 | | // TODO unroll loop for small sizes |
53 | | |
54 | 9.99k | bool fromCalldata = _sourceType.location() == DataLocation::CallData; |
55 | 9.99k | bool haveSourceLengthOnStack = fromCalldata && _sourceType.isDynamicallySized(); |
56 | | |
57 | 20.0k | for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) |
58 | 10.0k | m_context << swapInstruction(i); |
59 | | // stack: target_ref source_ref [source_length] |
60 | | |
61 | 9.99k | if (_sourceType.baseType()->category() == Type::Category::Array) |
62 | 20 | { |
63 | | // TODO: This limitation can now be removed since we use Yul utility functions that handle nested arrays correctly. |
64 | | // The old inline assembly implementation couldn't handle nested calldata dynamic arrays, but the Yul functions |
65 | | // support them through recursive calls. We keep this check temporarily for backward compatibility. |
66 | 20 | auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*_sourceType.baseType()); |
67 | 20 | solUnimplementedAssert( |
68 | 20 | !fromCalldata || |
69 | 20 | !_sourceType.isDynamicallyEncoded() || |
70 | 20 | !sourceBaseArrayType.isDynamicallySized(), |
71 | 20 | "Copying nested calldata dynamic arrays to storage is not implemented in the old code generator." |
72 | 20 | ); |
73 | 20 | } |
74 | 9.97k | else |
75 | 9.97k | { |
76 | | // TODO: This limitation can now be removed since we use Yul utility functions that handle non-value types correctly. |
77 | | // The old inline assembly implementation couldn't handle copying arrays of non-value types from memory or calldata to storage, |
78 | | // but the Yul functions support them. We keep this check temporarily for backward compatibility. |
79 | 9.97k | bool fromMemoryOrCalldata = _sourceType.location() == DataLocation::Memory || _sourceType.location() == DataLocation::CallData; |
80 | 9.97k | solUnimplementedAssert( |
81 | 9.97k | _sourceType.baseType()->isValueType() || !fromMemoryOrCalldata, |
82 | 9.97k | "Copying of type " + _sourceType.toString(false) + " to storage is not supported in legacy (only supported by the IR pipeline). " + |
83 | 9.97k | "Hint: try compiling with `--via-ir` (CLI) or the equivalent `viaIR: true` (Standard JSON)." |
84 | 9.97k | ); |
85 | 9.97k | } |
86 | | |
87 | 9.99k | if (haveSourceLengthOnStack) |
88 | 46 | { |
89 | | // stack: target_ref source_ref source_length |
90 | 46 | m_context << Instruction::SWAP1; |
91 | | // stack: target_ref source_length source_ref |
92 | 46 | m_context << Instruction::DUP3; |
93 | | // stack: target_ref source_length source_ref target_ref |
94 | 46 | } |
95 | 9.94k | else |
96 | 9.94k | { |
97 | | // stack: target_ref source_ref |
98 | 9.94k | m_context << Instruction::DUP2; |
99 | | // stack: target_ref source_ref target_ref |
100 | 9.94k | } |
101 | | |
102 | 9.99k | m_context.callYulFunction( |
103 | 9.99k | m_context.utilFunctions().copyArrayToStorageFunction(_sourceType, _targetType), |
104 | 9.99k | haveSourceLengthOnStack ? 3 : 2, |
105 | 9.99k | 0 |
106 | 9.99k | ); |
107 | | // stack: target_ref |
108 | 9.99k | } |
109 | | |
110 | | void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const |
111 | 2.87k | { |
112 | 2.87k | solUnimplementedAssert( |
113 | 2.87k | !_sourceType.baseType()->isDynamicallySized(), |
114 | 2.87k | "Nested dynamic arrays not implemented here." |
115 | 2.87k | ); |
116 | 2.87k | CompilerUtils utils(m_context); |
117 | | |
118 | 2.87k | if (_sourceType.location() == DataLocation::CallData) |
119 | 1.15k | { |
120 | 1.15k | if (!_sourceType.isDynamicallySized()) |
121 | 7 | m_context << _sourceType.length(); |
122 | 1.15k | if (!_sourceType.isByteArrayOrString()) |
123 | 11 | convertLengthToSize(_sourceType); |
124 | | |
125 | 1.15k | std::string routine = "calldatacopy(target, source, len)\n"; |
126 | 1.15k | if (_padToWordBoundaries) |
127 | 1.15k | routine += R"( |
128 | 1.15k | // Set padding suffix to zero |
129 | 1.15k | mstore(add(target, len), 0) |
130 | 1.15k | len := and(add(len, 0x1f), not(0x1f)) |
131 | 1.15k | )"; |
132 | 1.15k | routine += "target := add(target, len)\n"; |
133 | 1.15k | m_context.appendInlineAssembly("{" + routine + "}", {"target", "source", "len"}); |
134 | 1.15k | m_context << Instruction::POP << Instruction::POP; |
135 | 1.15k | } |
136 | 1.72k | else if (_sourceType.location() == DataLocation::Memory) |
137 | 33 | { |
138 | 33 | retrieveLength(_sourceType); |
139 | | // stack: target source length |
140 | 33 | if (!_sourceType.baseType()->isValueType()) |
141 | 0 | { |
142 | | // copy using a loop |
143 | 0 | m_context << u256(0) << Instruction::SWAP3; |
144 | | // stack: counter source length target |
145 | 0 | auto repeat = m_context.newTag(); |
146 | 0 | m_context << repeat; |
147 | 0 | m_context << Instruction::DUP2 << Instruction::DUP5; |
148 | 0 | m_context << Instruction::LT << Instruction::ISZERO; |
149 | 0 | auto loopEnd = m_context.appendConditionalJump(); |
150 | 0 | m_context << Instruction::DUP3 << Instruction::DUP5; |
151 | 0 | accessIndex(_sourceType, false); |
152 | 0 | MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); |
153 | 0 | if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType())) |
154 | 0 | copyArrayToMemory(*baseArray, _padToWordBoundaries); |
155 | 0 | else |
156 | 0 | utils.storeInMemoryDynamic(*_sourceType.baseType()); |
157 | 0 | m_context << Instruction::SWAP3 << u256(1) << Instruction::ADD; |
158 | 0 | m_context << Instruction::SWAP3; |
159 | 0 | m_context.appendJumpTo(repeat); |
160 | 0 | m_context << loopEnd; |
161 | 0 | m_context << Instruction::SWAP3; |
162 | 0 | utils.popStackSlots(3); |
163 | | // stack: updated_target_pos |
164 | 0 | return; |
165 | 0 | } |
166 | | |
167 | | // memcpy using the built-in contract |
168 | 33 | if (_sourceType.isDynamicallySized()) |
169 | 33 | { |
170 | | // change pointer to data part |
171 | 33 | m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; |
172 | 33 | m_context << Instruction::SWAP1; |
173 | 33 | } |
174 | 33 | if (!_sourceType.isByteArrayOrString()) |
175 | 2 | convertLengthToSize(_sourceType); |
176 | | // stack: <target> <source> <size> |
177 | 33 | m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; |
178 | | // We can resort to copying full 32 bytes only if |
179 | | // - the length is known to be a multiple of 32 or |
180 | | // - we will pad to full 32 bytes later anyway. |
181 | 33 | if (!_sourceType.isByteArrayOrString() || _padToWordBoundaries) |
182 | 33 | utils.memoryCopy32(); |
183 | 0 | else |
184 | 0 | utils.memoryCopy(); |
185 | | |
186 | 33 | m_context << Instruction::SWAP1 << Instruction::POP; |
187 | | // stack: <target> <size> |
188 | | |
189 | 33 | bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArrayOrString(); |
190 | | |
191 | 33 | if (paddingNeeded) |
192 | 31 | { |
193 | | // stack: <target> <size> |
194 | 31 | m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD; |
195 | | // stack: <length> <target + size> |
196 | 31 | m_context << Instruction::SWAP1 << u256(31) << Instruction::AND; |
197 | | // stack: <target + size> <remainder = size % 32> |
198 | 31 | evmasm::AssemblyItem skip = m_context.newTag(); |
199 | 31 | if (_sourceType.isDynamicallySized()) |
200 | 31 | { |
201 | 31 | m_context << Instruction::DUP1 << Instruction::ISZERO; |
202 | 31 | m_context.appendConditionalJumpTo(skip); |
203 | 31 | } |
204 | | // round off, load from there. |
205 | | // stack <target + size> <remainder = size % 32> |
206 | 31 | m_context << Instruction::DUP1 << Instruction::DUP3; |
207 | 31 | m_context << Instruction::SUB; |
208 | | // stack: target+size remainder <target + size - remainder> |
209 | 31 | m_context << Instruction::DUP1 << Instruction::MLOAD; |
210 | | // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) |
211 | 31 | m_context << u256(1); |
212 | 31 | m_context << Instruction::DUP4 << u256(32) << Instruction::SUB; |
213 | | // stack: ...<v> 1 <32 - remainder> |
214 | 31 | m_context << u256(0x100) << Instruction::EXP << Instruction::SUB; |
215 | 31 | m_context << Instruction::NOT << Instruction::AND; |
216 | | // stack: target+size remainder target+size-remainder <v & ...> |
217 | 31 | m_context << Instruction::DUP2 << Instruction::MSTORE; |
218 | | // stack: target+size remainder target+size-remainder |
219 | 31 | m_context << u256(32) << Instruction::ADD; |
220 | | // stack: target+size remainder <new_padded_end> |
221 | 31 | m_context << Instruction::SWAP2 << Instruction::POP; |
222 | | |
223 | 31 | if (_sourceType.isDynamicallySized()) |
224 | 31 | m_context << skip.tag(); |
225 | | // stack <target + "size"> <remainder = size % 32> |
226 | 31 | m_context << Instruction::POP; |
227 | 31 | } |
228 | 2 | else |
229 | | // stack: <target> <size> |
230 | 2 | m_context << Instruction::ADD; |
231 | 33 | } |
232 | 1.69k | else |
233 | 1.69k | { |
234 | 1.69k | solAssert(_sourceType.location() == DataLocation::Storage, ""); |
235 | 1.69k | unsigned storageBytes = _sourceType.baseType()->storageBytes(); |
236 | 1.69k | u256 storageSize = _sourceType.baseType()->storageSize(); |
237 | 1.69k | solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); |
238 | | |
239 | 1.69k | retrieveLength(_sourceType); |
240 | | // stack here: memory_offset storage_offset length |
241 | | // jump to end if length is zero |
242 | 1.69k | m_context << Instruction::DUP1 << Instruction::ISZERO; |
243 | 1.69k | evmasm::AssemblyItem loopEnd = m_context.appendConditionalJump(); |
244 | | // Special case for tightly-stored byte arrays |
245 | 1.69k | if (_sourceType.isByteArrayOrString()) |
246 | 1.47k | { |
247 | | // stack here: memory_offset storage_offset length |
248 | 1.47k | m_context << Instruction::DUP1 << u256(31) << Instruction::LT; |
249 | 1.47k | evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); |
250 | | // store the short byte array (discard lower-order byte) |
251 | 1.47k | m_context << u256(0x100) << Instruction::DUP1; |
252 | 1.47k | m_context << Instruction::DUP4 << Instruction::SLOAD; |
253 | 1.47k | m_context << Instruction::DIV << Instruction::MUL; |
254 | 1.47k | m_context << Instruction::DUP4 << Instruction::MSTORE; |
255 | | // stack here: memory_offset storage_offset length |
256 | | // add 32 or length to memory offset |
257 | 1.47k | m_context << Instruction::SWAP2; |
258 | 1.47k | if (_padToWordBoundaries) |
259 | 1.47k | m_context << u256(32); |
260 | 0 | else |
261 | 0 | m_context << Instruction::DUP3; |
262 | 1.47k | m_context << Instruction::ADD; |
263 | 1.47k | m_context << Instruction::SWAP2; |
264 | 1.47k | m_context.appendJumpTo(loopEnd); |
265 | 1.47k | m_context << longByteArray; |
266 | 1.47k | } |
267 | 217 | else |
268 | | // convert length to memory size |
269 | 217 | m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL; |
270 | | |
271 | 1.69k | m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; |
272 | 1.69k | if (_sourceType.isDynamicallySized()) |
273 | 1.59k | { |
274 | | // actual array data is stored at KECCAK256(storage_offset) |
275 | 1.59k | m_context << Instruction::SWAP1; |
276 | 1.59k | utils.computeHashStatic(); |
277 | 1.59k | m_context << Instruction::SWAP1; |
278 | 1.59k | } |
279 | | |
280 | | // stack here: memory_end_offset storage_data_offset memory_offset |
281 | 1.69k | bool haveByteOffset = !_sourceType.isByteArrayOrString() && storageBytes <= 16; |
282 | 1.69k | if (haveByteOffset) |
283 | 147 | m_context << u256(0) << Instruction::SWAP1; |
284 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
285 | 1.69k | evmasm::AssemblyItem loopStart = m_context.newTag(); |
286 | 1.69k | m_context << loopStart; |
287 | | // load and store |
288 | 1.69k | if (_sourceType.isByteArrayOrString()) |
289 | 1.47k | { |
290 | | // Packed both in storage and memory. |
291 | 1.47k | m_context << Instruction::DUP2 << Instruction::SLOAD; |
292 | 1.47k | m_context << Instruction::DUP2 << Instruction::MSTORE; |
293 | | // increment storage_data_offset by 1 |
294 | 1.47k | m_context << Instruction::SWAP1 << u256(1) << Instruction::ADD; |
295 | | // increment memory offset by 32 |
296 | 1.47k | m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; |
297 | 1.47k | } |
298 | 217 | else |
299 | 217 | { |
300 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
301 | 217 | if (haveByteOffset) |
302 | 147 | m_context << Instruction::DUP3 << Instruction::DUP3; |
303 | 70 | else |
304 | 70 | m_context << Instruction::DUP2 << u256(0); |
305 | 217 | StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); |
306 | 217 | if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType())) |
307 | 0 | copyArrayToMemory(*baseArray, _padToWordBoundaries); |
308 | 217 | else |
309 | 217 | utils.storeInMemoryDynamic(*_sourceType.baseType()); |
310 | | // increment storage_data_offset and byte offset |
311 | 217 | if (haveByteOffset) |
312 | 147 | incrementByteOffset(storageBytes, 2, 3); |
313 | 70 | else |
314 | 70 | { |
315 | 70 | m_context << Instruction::SWAP1; |
316 | 70 | m_context << storageSize << Instruction::ADD; |
317 | 70 | m_context << Instruction::SWAP1; |
318 | 70 | } |
319 | 217 | } |
320 | | // check for loop condition |
321 | 1.69k | m_context << Instruction::DUP1 << dupInstruction(haveByteOffset ? 5 : 4); |
322 | 1.69k | m_context << Instruction::GT; |
323 | 1.69k | m_context.appendConditionalJumpTo(loopStart); |
324 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
325 | 1.69k | if (haveByteOffset) |
326 | 147 | m_context << Instruction::SWAP1 << Instruction::POP; |
327 | 1.69k | if (!_sourceType.isByteArrayOrString()) |
328 | 217 | { |
329 | 217 | solAssert(_sourceType.calldataStride() % 32 == 0, ""); |
330 | 217 | solAssert(_sourceType.memoryStride() % 32 == 0, ""); |
331 | 217 | } |
332 | 1.69k | if (_padToWordBoundaries && _sourceType.isByteArrayOrString()) |
333 | 1.47k | { |
334 | | // memory_end_offset - start is the actual length (we want to compute the ceil of). |
335 | | // memory_offset - start is its next multiple of 32, but it might be off by 32. |
336 | | // so we compute: memory_end_offset += (memory_offset - memory_end_offset) & 31 |
337 | 1.47k | m_context << Instruction::DUP3 << Instruction::SWAP1 << Instruction::SUB; |
338 | 1.47k | m_context << u256(31) << Instruction::AND; |
339 | 1.47k | m_context << Instruction::DUP3 << Instruction::ADD; |
340 | 1.47k | m_context << Instruction::SWAP2; |
341 | 1.47k | } |
342 | 1.69k | m_context << loopEnd << Instruction::POP << Instruction::POP; |
343 | 1.69k | } |
344 | 2.87k | } |
345 | | |
346 | | void ArrayUtils::clearArray(ArrayType const& _typeIn) const |
347 | 55 | { |
348 | 55 | Type const* type = &_typeIn; |
349 | 55 | m_context.callLowLevelFunction( |
350 | 55 | "$clearArray_" + _typeIn.identifier(), |
351 | 55 | 2, |
352 | 55 | 0, |
353 | 55 | [type](CompilerContext& _context) |
354 | 55 | { |
355 | 47 | ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); |
356 | 47 | unsigned stackHeightStart = _context.stackHeight(); |
357 | 47 | solAssert(_type.location() == DataLocation::Storage, ""); |
358 | 47 | if (_type.baseType()->storageBytes() < 32) |
359 | 9 | { |
360 | 9 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
361 | 9 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); |
362 | 9 | } |
363 | 47 | if (_type.baseType()->isValueType()) |
364 | 47 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); |
365 | | |
366 | 47 | _context << Instruction::POP; // remove byte offset |
367 | 47 | if (_type.isDynamicallySized()) |
368 | 38 | ArrayUtils(_context).clearDynamicArray(_type); |
369 | 9 | else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) |
370 | 0 | _context << Instruction::POP; |
371 | 9 | else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) |
372 | 5 | { |
373 | | // unroll loop for small arrays @todo choose a good value |
374 | | // Note that we loop over storage slots here, not elements. |
375 | 11 | for (unsigned i = 1; i < _type.storageSize(); ++i) |
376 | 6 | _context |
377 | 6 | << u256(0) << Instruction::DUP2 << Instruction::SSTORE |
378 | 6 | << u256(1) << Instruction::ADD; |
379 | 5 | _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; |
380 | 5 | } |
381 | 4 | else if (!_type.baseType()->isValueType() && _type.length() <= 4) |
382 | 0 | { |
383 | | // unroll loop for small arrays @todo choose a good value |
384 | 0 | solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); |
385 | 0 | for (unsigned i = 1; i < _type.length(); ++i) |
386 | 0 | { |
387 | 0 | _context << u256(0); |
388 | 0 | StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); |
389 | 0 | _context |
390 | 0 | << Instruction::POP |
391 | 0 | << u256(_type.baseType()->storageSize()) << Instruction::ADD; |
392 | 0 | } |
393 | 0 | _context << u256(0); |
394 | 0 | StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); |
395 | 0 | } |
396 | 4 | else |
397 | 4 | { |
398 | 4 | _context << _type.length(); |
399 | 4 | ArrayUtils(_context).convertLengthToSize(_type); |
400 | | // stack: storage_ref slot_count |
401 | 4 | if (_type.baseType()->storageBytes() < 32) |
402 | 0 | ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); |
403 | 4 | else |
404 | 4 | ArrayUtils(_context).clearStorageLoop(_type.baseType()); |
405 | 4 | } |
406 | 47 | solAssert(_context.stackHeight() == stackHeightStart - 2, ""); |
407 | 47 | } |
408 | 55 | ); |
409 | 55 | } |
410 | | |
411 | | void ArrayUtils::clearDynamicArray(ArrayType const& _type) const |
412 | 38 | { |
413 | 38 | solAssert(_type.location() == DataLocation::Storage, ""); |
414 | 38 | solAssert(_type.isDynamicallySized(), ""); |
415 | | |
416 | | // fetch length |
417 | 38 | retrieveLength(_type); |
418 | | // set length to zero |
419 | 38 | m_context << u256(0) << Instruction::DUP3 << Instruction::SSTORE; |
420 | | // Special case: short byte arrays are stored togeher with their length |
421 | 38 | evmasm::AssemblyItem endTag = m_context.newTag(); |
422 | 38 | if (_type.isByteArrayOrString()) |
423 | 6 | { |
424 | | // stack: ref old_length |
425 | 6 | m_context << Instruction::DUP1 << u256(31) << Instruction::LT; |
426 | 6 | evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); |
427 | | // Short byte array: no data slots to clear, just pop and exit |
428 | 6 | m_context << Instruction::POP << Instruction::POP; |
429 | 6 | m_context.appendJumpTo(endTag); |
430 | 6 | m_context.adjustStackOffset(2); // the longByteArray path has 2 more items on stack |
431 | 6 | m_context << longByteArray; |
432 | 6 | } |
433 | | // stack: ref old_length |
434 | 38 | convertLengthToSize(_type); |
435 | | // stack: ref slot_count |
436 | 38 | m_context << Instruction::SWAP1; |
437 | 38 | CompilerUtils(m_context).computeHashStatic(); |
438 | | // stack: slot_count data_pos |
439 | 38 | m_context << Instruction::SWAP1; |
440 | | // stack: data_pos slot_count |
441 | 38 | if (_type.storageStride() < 32) |
442 | 9 | clearStorageLoop(TypeProvider::uint256()); |
443 | 29 | else |
444 | 29 | clearStorageLoop(_type.baseType()); |
445 | | // cleanup |
446 | 38 | m_context << endTag; |
447 | 38 | } |
448 | | |
449 | | void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const |
450 | 2.48k | { |
451 | 2.48k | solAssert(_type.location() == DataLocation::Storage, ""); |
452 | 2.48k | solAssert(_type.isDynamicallySized(), ""); |
453 | 2.48k | if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) |
454 | 2.48k | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
455 | | |
456 | 2.48k | if (_type.isByteArrayOrString()) |
457 | 93 | { |
458 | | // We almost always just add 2 (length of byte arrays is shifted left by one) |
459 | | // except for the case where we transition from a short byte array |
460 | | // to a long byte array, there we have to copy. |
461 | | // This happens if the length is exactly 31, which means that the |
462 | | // lowest-order byte (we actually use a mask with fewer bits) must |
463 | | // be (31*2+0) = 62 |
464 | | |
465 | 93 | m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1; |
466 | 93 | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
467 | 93 | m_context.appendInlineAssembly(R"({ |
468 | 93 | // We have to copy if length is exactly 31, because that marks |
469 | 93 | // the transition between in-place and out-of-place storage. |
470 | 93 | switch length |
471 | 93 | case 31 |
472 | 93 | { |
473 | 93 | mstore(0, ref) |
474 | 93 | let data_area := keccak256(0, 0x20) |
475 | 93 | sstore(data_area, and(data, not(0xff))) |
476 | 93 | // Set old length in new format (31 * 2 + 1) |
477 | 93 | data := 63 |
478 | 93 | } |
479 | 93 | sstore(ref, add(data, 2)) |
480 | 93 | // return new length in ref |
481 | 93 | ref := add(length, 1) |
482 | 93 | })", {"ref", "data", "length"}); |
483 | 93 | m_context << Instruction::POP << Instruction::POP; |
484 | 93 | } |
485 | 2.39k | else |
486 | 2.39k | m_context.appendInlineAssembly(R"({ |
487 | 2.39k | let new_length := add(sload(ref), 1) |
488 | 2.39k | sstore(ref, new_length) |
489 | 2.39k | ref := new_length |
490 | 2.39k | })", {"ref"}); |
491 | 2.48k | } |
492 | | |
493 | | void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const |
494 | 220 | { |
495 | 220 | solAssert(_type.location() == DataLocation::Storage, ""); |
496 | 220 | solAssert(_type.isDynamicallySized(), ""); |
497 | 220 | if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) |
498 | 220 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
499 | | |
500 | 220 | if (_type.isByteArrayOrString()) |
501 | 82 | { |
502 | 82 | m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1; |
503 | 82 | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
504 | 82 | util::Whiskers code(R"({ |
505 | 82 | if iszero(length) { |
506 | 82 | mstore(0, <panicSelector>) |
507 | 82 | mstore(4, <emptyArrayPop>) |
508 | 82 | revert(0, 0x24) |
509 | 82 | } |
510 | 82 | switch gt(length, 31) |
511 | 82 | case 0 { |
512 | 82 | // short byte array |
513 | 82 | // Zero-out the suffix including the least significant byte. |
514 | 82 | let mask := sub(exp(0x100, sub(33, length)), 1) |
515 | 82 | length := sub(length, 1) |
516 | 82 | slot_value := or(and(not(mask), slot_value), mul(length, 2)) |
517 | 82 | } |
518 | 82 | case 1 { |
519 | 82 | // long byte array |
520 | 82 | mstore(0, ref) |
521 | 82 | let slot := keccak256(0, 0x20) |
522 | 82 | switch length |
523 | 82 | case 32 |
524 | 82 | { |
525 | 82 | let data := sload(slot) |
526 | 82 | sstore(slot, 0) |
527 | 82 | data := and(data, not(0xff)) |
528 | 82 | slot_value := or(data, 62) |
529 | 82 | } |
530 | 82 | default |
531 | 82 | { |
532 | 82 | let offset_inside_slot := and(sub(length, 1), 0x1f) |
533 | 82 | slot := add(slot, div(sub(length, 1), 32)) |
534 | 82 | let data := sload(slot) |
535 | 82 | |
536 | 82 | // Zero-out the suffix of the byte array by masking it. |
537 | 82 | // ((1<<(8 * (32 - offset))) - 1) |
538 | 82 | let mask := sub(exp(0x100, sub(32, offset_inside_slot)), 1) |
539 | 82 | data := and(not(mask), data) |
540 | 82 | sstore(slot, data) |
541 | 82 | |
542 | 82 | // Reduce the length by 1 |
543 | 82 | slot_value := sub(slot_value, 2) |
544 | 82 | } |
545 | 82 | } |
546 | 82 | sstore(ref, slot_value) |
547 | 82 | })"); |
548 | 82 | code("panicSelector", util::selectorFromSignatureU256("Panic(uint256)").str()); |
549 | 82 | code("emptyArrayPop", std::to_string(unsigned(util::PanicCode::EmptyArrayPop))); |
550 | 82 | m_context.appendInlineAssembly(code.render(), {"ref", "slot_value", "length"}); |
551 | 82 | m_context << Instruction::POP << Instruction::POP << Instruction::POP; |
552 | 82 | } |
553 | 138 | else |
554 | 138 | { |
555 | | // stack: ArrayReference |
556 | 138 | retrieveLength(_type); |
557 | | // stack: ArrayReference oldLength |
558 | 138 | m_context << Instruction::DUP1; |
559 | | // stack: ArrayReference oldLength oldLength |
560 | 138 | m_context << Instruction::ISZERO; |
561 | 138 | m_context.appendConditionalPanic(util::PanicCode::EmptyArrayPop); |
562 | | |
563 | | // Stack: ArrayReference oldLength |
564 | 138 | m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; |
565 | | // Stack ArrayReference newLength |
566 | | |
567 | 138 | if (_type.baseType()->category() != Type::Category::Mapping) |
568 | 135 | { |
569 | 135 | m_context << Instruction::DUP2 << Instruction::DUP2; |
570 | | // Stack ArrayReference newLength ArrayReference newLength; |
571 | 135 | accessIndex(_type, false); |
572 | | // Stack: ArrayReference newLength storage_slot byte_offset |
573 | 135 | StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); |
574 | 135 | } |
575 | | |
576 | | // Stack: ArrayReference newLength |
577 | 138 | m_context << Instruction::SWAP1 << Instruction::SSTORE; |
578 | 138 | } |
579 | 220 | } |
580 | | |
581 | | void ArrayUtils::clearStorageLoop(Type const* _type) const |
582 | 42 | { |
583 | 42 | solAssert(_type->storageBytes() >= 32, ""); |
584 | 42 | m_context.callLowLevelFunction( |
585 | 42 | "$clearStorageLoop_" + _type->identifier(), |
586 | 42 | 2, |
587 | 42 | 0, |
588 | 42 | [_type](CompilerContext& _context) |
589 | 42 | { |
590 | 42 | unsigned stackHeightStart = _context.stackHeight(); |
591 | 42 | if (_type->category() == Type::Category::Mapping) |
592 | 6 | { |
593 | 6 | _context << Instruction::POP << Instruction::POP; |
594 | 6 | return; |
595 | 6 | } |
596 | | // stack: start_pos slot_count |
597 | | // Initialize loop counter i = 0 |
598 | 36 | _context << u256(0); |
599 | | // stack: start_pos slot_count i |
600 | 36 | evmasm::AssemblyItem loopStart = _context.appendJumpToNew(); |
601 | 36 | _context << loopStart; |
602 | | // check for loop condition: !(slot_count > i) = (i >= slot_count) |
603 | 36 | _context << Instruction::DUP1 << Instruction::DUP3 << Instruction::GT << Instruction::ISZERO; |
604 | 36 | evmasm::AssemblyItem zeroLoopEnd = _context.newTag(); |
605 | 36 | _context.appendConditionalJumpTo(zeroLoopEnd); |
606 | | // stack: start_pos slot_count i |
607 | | // compute storage position: start_pos + i |
608 | 36 | _context << Instruction::DUP3 << Instruction::DUP2 << Instruction::ADD; |
609 | | // stack: start_pos slot_count i (start_pos+i) |
610 | | // delete storage slot |
611 | 36 | _context << u256(0); |
612 | 36 | StorageItem(_context, *_type).setToZero(SourceLocation(), /* _removeReference = */ true); |
613 | | // stack: start_pos slot_count i |
614 | | // increment counter: i += storageSize |
615 | 36 | _context << _type->storageSize() << Instruction::ADD; |
616 | 36 | _context.appendJumpTo(loopStart); |
617 | | // cleanup |
618 | 36 | _context << zeroLoopEnd; |
619 | | // stack: start_pos slot_count i |
620 | 36 | _context << Instruction::POP << Instruction::POP << Instruction::POP; |
621 | 36 | solAssert(_context.stackHeight() == stackHeightStart - 2, ""); |
622 | 36 | } |
623 | 42 | ); |
624 | 42 | } |
625 | | |
626 | | void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const |
627 | 3.42k | { |
628 | 3.42k | if (_arrayType.location() == DataLocation::Storage) |
629 | 42 | { |
630 | 42 | if (_arrayType.baseType()->storageSize() <= 1) |
631 | 40 | { |
632 | 40 | unsigned baseBytes = _arrayType.baseType()->storageBytes(); |
633 | 40 | if (baseBytes == 0) |
634 | 0 | m_context << Instruction::POP << u256(1); |
635 | 40 | else if (baseBytes <= 16) |
636 | 9 | { |
637 | 9 | unsigned itemsPerSlot = 32 / baseBytes; |
638 | 9 | m_context |
639 | 9 | << u256(itemsPerSlot - 1) << Instruction::ADD |
640 | 9 | << u256(itemsPerSlot) << Instruction::SWAP1 << Instruction::DIV; |
641 | 9 | } |
642 | 40 | } |
643 | 2 | else |
644 | 2 | m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; |
645 | 42 | } |
646 | 3.38k | else |
647 | 3.38k | { |
648 | 3.38k | if (!_arrayType.isByteArrayOrString()) |
649 | 771 | { |
650 | 771 | if (_arrayType.location() == DataLocation::Memory) |
651 | 760 | m_context << _arrayType.memoryStride(); |
652 | 11 | else |
653 | 11 | m_context << _arrayType.calldataStride(); |
654 | 771 | m_context << Instruction::MUL; |
655 | 771 | } |
656 | 2.60k | else if (_pad) |
657 | 2.60k | m_context << u256(31) << Instruction::ADD |
658 | 2.60k | << u256(32) << Instruction::DUP1 |
659 | 2.60k | << Instruction::SWAP2 << Instruction::DIV << Instruction::MUL; |
660 | 3.38k | } |
661 | 3.42k | } |
662 | | |
663 | | void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth) const |
664 | 122k | { |
665 | 122k | if (!_arrayType.isDynamicallySized()) |
666 | 17.7k | m_context << _arrayType.length(); |
667 | 104k | else |
668 | 104k | { |
669 | 104k | m_context << dupInstruction(1 + _stackDepth); |
670 | 104k | switch (_arrayType.location()) |
671 | 104k | { |
672 | 5.62k | case DataLocation::CallData: |
673 | | // length is stored on the stack |
674 | 5.62k | break; |
675 | 69.2k | case DataLocation::Memory: |
676 | 69.2k | m_context << Instruction::MLOAD; |
677 | 69.2k | break; |
678 | 29.6k | case DataLocation::Storage: |
679 | 29.6k | m_context << Instruction::SLOAD; |
680 | 29.6k | if (_arrayType.isByteArrayOrString()) |
681 | 3.60k | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
682 | 29.6k | break; |
683 | 0 | case DataLocation::Transient: |
684 | 0 | solUnimplemented("Transient data location is only supported for value types."); |
685 | 0 | break; |
686 | 104k | } |
687 | 104k | } |
688 | 122k | } |
689 | | |
690 | | void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const |
691 | 119k | { |
692 | | /// Stack: reference [length] index |
693 | 119k | DataLocation location = _arrayType.location(); |
694 | | |
695 | 119k | if (_doBoundsCheck) |
696 | 116k | { |
697 | | // retrieve length |
698 | 116k | ArrayUtils::retrieveLength(_arrayType, 1); |
699 | | // Stack: ref [length] index length |
700 | | // check out-of-bounds access |
701 | 116k | m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; |
702 | | // out-of-bounds access throws exception |
703 | 116k | m_context.appendConditionalPanic(util::PanicCode::ArrayOutOfBounds); |
704 | 116k | } |
705 | 119k | if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) |
706 | | // remove length if present |
707 | 4.48k | m_context << Instruction::SWAP1 << Instruction::POP; |
708 | | |
709 | | // stack: <base_ref> <index> |
710 | 119k | switch (location) |
711 | 119k | { |
712 | 79.9k | case DataLocation::Memory: |
713 | | // stack: <base_ref> <index> |
714 | 79.9k | if (!_arrayType.isByteArrayOrString()) |
715 | 77.7k | m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL; |
716 | 79.9k | if (_arrayType.isDynamicallySized()) |
717 | 69.0k | m_context << u256(32) << Instruction::ADD; |
718 | 79.9k | if (_keepReference) |
719 | 0 | m_context << Instruction::DUP2; |
720 | 79.9k | m_context << Instruction::ADD; |
721 | 79.9k | break; |
722 | 5.08k | case DataLocation::CallData: |
723 | 5.08k | if (!_arrayType.isByteArrayOrString()) |
724 | 4.95k | { |
725 | 4.95k | m_context << _arrayType.calldataStride(); |
726 | 4.95k | m_context << Instruction::MUL; |
727 | 4.95k | } |
728 | | // stack: <base_ref> <index * size> |
729 | 5.08k | if (_keepReference) |
730 | 3.94k | m_context << Instruction::DUP2; |
731 | 5.08k | m_context << Instruction::ADD; |
732 | 5.08k | break; |
733 | 34.6k | case DataLocation::Storage: |
734 | 34.6k | { |
735 | 34.6k | if (_keepReference) |
736 | 0 | m_context << Instruction::DUP2; |
737 | 34.6k | else |
738 | 34.6k | m_context << Instruction::SWAP1; |
739 | | // stack: [<base_ref>] <index> <base_ref> |
740 | | |
741 | 34.6k | evmasm::AssemblyItem endTag = m_context.newTag(); |
742 | 34.6k | if (_arrayType.isByteArrayOrString()) |
743 | 697 | { |
744 | | // Special case of short byte arrays. |
745 | 697 | m_context << Instruction::SWAP1; |
746 | 697 | m_context << Instruction::DUP2 << Instruction::SLOAD; |
747 | 697 | m_context << u256(1) << Instruction::AND << Instruction::ISZERO; |
748 | | // No action needed for short byte arrays. |
749 | 697 | m_context.appendConditionalJumpTo(endTag); |
750 | 697 | m_context << Instruction::SWAP1; |
751 | 697 | } |
752 | 34.6k | if (_arrayType.isDynamicallySized()) |
753 | 28.6k | CompilerUtils(m_context).computeHashStatic(); |
754 | 34.6k | m_context << Instruction::SWAP1; |
755 | 34.6k | if (_arrayType.baseType()->storageBytes() <= 16) |
756 | 2.03k | { |
757 | | // stack: <data_ref> <index> |
758 | | // goal: |
759 | | // <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize> |
760 | 2.03k | unsigned byteSize = _arrayType.baseType()->storageBytes(); |
761 | 2.03k | solAssert(byteSize != 0, ""); |
762 | 2.03k | unsigned itemsPerSlot = 32 / byteSize; |
763 | 2.03k | m_context << u256(itemsPerSlot) << Instruction::SWAP2; |
764 | | // stack: itemsPerSlot index data_ref |
765 | 2.03k | m_context |
766 | 2.03k | << Instruction::DUP3 << Instruction::DUP3 |
767 | 2.03k | << Instruction::DIV << Instruction::ADD |
768 | | // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) |
769 | 2.03k | << Instruction::SWAP2 << Instruction::SWAP1 |
770 | 2.03k | << Instruction::MOD; |
771 | 2.03k | if (byteSize != 1) |
772 | 536 | m_context << u256(byteSize) << Instruction::MUL; |
773 | 2.03k | } |
774 | 32.6k | else |
775 | 32.6k | { |
776 | 32.6k | if (_arrayType.baseType()->storageSize() != 1) |
777 | 10.1k | m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; |
778 | 32.6k | m_context << Instruction::ADD << u256(0); |
779 | 32.6k | } |
780 | 34.6k | m_context << endTag; |
781 | 34.6k | break; |
782 | 0 | } |
783 | 0 | case DataLocation::Transient: |
784 | 0 | solUnimplemented("Transient data location is only supported for value types."); |
785 | 0 | break; |
786 | 119k | } |
787 | 119k | } |
788 | | |
789 | | void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const |
790 | 5.07k | { |
791 | 5.07k | solAssert(_arrayType.location() == DataLocation::CallData, ""); |
792 | 5.07k | if (_arrayType.baseType()->isDynamicallyEncoded()) |
793 | 3.94k | { |
794 | | // stack layout: <base_ref> <length> <index> |
795 | 3.94k | ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true); |
796 | | // stack layout: <base_ref> <ptr_to_tail> |
797 | | |
798 | 3.94k | CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType()); |
799 | | // stack layout: <tail_ref> [length] |
800 | 3.94k | } |
801 | 1.13k | else |
802 | 1.13k | { |
803 | 1.13k | ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck); |
804 | 1.13k | if (_arrayType.baseType()->isValueType()) |
805 | 811 | { |
806 | 811 | solAssert(_arrayType.baseType()->storageBytes() <= 32, ""); |
807 | 811 | if ( |
808 | 811 | !_arrayType.isByteArrayOrString() && |
809 | 679 | _arrayType.baseType()->storageBytes() < 32 && |
810 | 448 | m_context.useABICoderV2() |
811 | 811 | ) |
812 | 446 | { |
813 | 446 | m_context << u256(32); |
814 | 446 | CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false); |
815 | 446 | } |
816 | 365 | else |
817 | 365 | CompilerUtils(m_context).loadFromMemoryDynamic( |
818 | 365 | *_arrayType.baseType(), |
819 | 365 | true, |
820 | 365 | !_arrayType.isByteArrayOrString(), |
821 | 365 | false |
822 | 365 | ); |
823 | 811 | } |
824 | 320 | else |
825 | 1.13k | solAssert( |
826 | 1.13k | _arrayType.baseType()->category() == Type::Category::Struct || |
827 | 1.13k | _arrayType.baseType()->category() == Type::Category::Array, |
828 | 1.13k | "Invalid statically sized non-value base type on array access." |
829 | 1.13k | ); |
830 | 1.13k | } |
831 | 5.07k | } |
832 | | |
833 | | void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const |
834 | 147 | { |
835 | 147 | solAssert(_byteSize < 32, ""); |
836 | 147 | solAssert(_byteSize != 0, ""); |
837 | | // We do the following, but avoiding jumps: |
838 | | // byteOffset += byteSize |
839 | | // if (byteOffset + byteSize > 32) |
840 | | // { |
841 | | // storageOffset++; |
842 | | // byteOffset = 0; |
843 | | // } |
844 | 147 | if (_byteOffsetPosition > 1) |
845 | 147 | m_context << swapInstruction(_byteOffsetPosition - 1); |
846 | 147 | m_context << u256(_byteSize) << Instruction::ADD; |
847 | 147 | if (_byteOffsetPosition > 1) |
848 | 147 | m_context << swapInstruction(_byteOffsetPosition - 1); |
849 | | // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 |
850 | 147 | m_context |
851 | 147 | << u256(32) << dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) |
852 | 147 | << Instruction::ADD << Instruction::DIV; |
853 | | // increment storage offset if X == 1 (just add X to it) |
854 | | // stack: X |
855 | 147 | m_context |
856 | 147 | << swapInstruction(_storageOffsetPosition) << dupInstruction(_storageOffsetPosition + 1) |
857 | 147 | << Instruction::ADD << swapInstruction(_storageOffsetPosition); |
858 | | // stack: X |
859 | | // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) |
860 | 147 | m_context << u256(1) << Instruction::SUB; |
861 | | // stack: 1 - X |
862 | 147 | if (_byteOffsetPosition == 1) |
863 | 0 | m_context << Instruction::MUL; |
864 | 147 | else |
865 | 147 | m_context |
866 | 147 | << dupInstruction(_byteOffsetPosition + 1) << Instruction::MUL |
867 | 147 | << swapInstruction(_byteOffsetPosition) << Instruction::POP; |
868 | 147 | } |