/src/solidity/libsolidity/codegen/ArrayUtils.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 | | * @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 std; |
40 | | using namespace solidity; |
41 | | using namespace solidity::evmasm; |
42 | | using namespace solidity::frontend; |
43 | | using namespace solidity::langutil; |
44 | | |
45 | | void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const |
46 | 59.3k | { |
47 | | // this copies source to target and also clears target if it was larger |
48 | | // need to leave "target_ref target_byte_off" on the stack at the end |
49 | | |
50 | | // stack layout: [source_ref] [source length] target_ref (top) |
51 | 59.3k | solAssert(_targetType.location() == DataLocation::Storage, ""); |
52 | | |
53 | 59.3k | Type const* targetBaseType = _targetType.baseType(); |
54 | 59.3k | Type const* sourceBaseType = _sourceType.baseType(); |
55 | | |
56 | | // TODO unroll loop for small sizes |
57 | | |
58 | 59.3k | bool sourceIsStorage = _sourceType.location() == DataLocation::Storage; |
59 | 59.3k | bool fromCalldata = _sourceType.location() == DataLocation::CallData; |
60 | 59.3k | bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; |
61 | 59.3k | bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->storageBytes() <= 16; |
62 | 59.3k | bool haveByteOffsetTarget = !directCopy && targetBaseType->storageBytes() <= 16; |
63 | 59.3k | unsigned byteOffsetSize = (haveByteOffsetSource ? 1u : 0u) + (haveByteOffsetTarget ? 1u : 0u); |
64 | | |
65 | | // stack: source_ref [source_length] target_ref |
66 | | // store target_ref |
67 | 118k | for (unsigned i = _sourceType.sizeOnStack(); i > 0; --i) |
68 | 59.3k | m_context << swapInstruction(i); |
69 | | // stack: target_ref source_ref [source_length] |
70 | | |
71 | 59.3k | if (_targetType.isByteArrayOrString()) |
72 | 59.0k | { |
73 | | // stack: target_ref source_ref [source_length] |
74 | 59.0k | if (fromCalldata && _sourceType.isDynamicallySized()) |
75 | 56 | { |
76 | | // stack: target_ref source_ref source_length |
77 | 56 | m_context << Instruction::SWAP1; |
78 | | // stack: target_ref source_length source_ref |
79 | 56 | m_context << Instruction::DUP3; |
80 | | // stack: target_ref source_length source_ref target_ref |
81 | 56 | m_context.callYulFunction( |
82 | 56 | m_context.utilFunctions().copyByteArrayToStorageFunction(_sourceType, _targetType), |
83 | 56 | 3, |
84 | 56 | 0 |
85 | 56 | ); |
86 | 56 | } |
87 | 58.9k | else |
88 | 58.9k | { |
89 | | // stack: target_ref source_ref |
90 | 58.9k | m_context << Instruction::DUP2; |
91 | | // stack: target_ref source_ref target_ref |
92 | 58.9k | m_context.callYulFunction( |
93 | 58.9k | m_context.utilFunctions().copyByteArrayToStorageFunction(_sourceType, _targetType), |
94 | 58.9k | 2, |
95 | 58.9k | 0 |
96 | 58.9k | ); |
97 | 58.9k | } |
98 | | // stack: target_ref |
99 | 59.0k | return; |
100 | 59.0k | } |
101 | | |
102 | | // retrieve source length |
103 | 285 | if (_sourceType.location() != DataLocation::CallData || !_sourceType.isDynamicallySized()) |
104 | 263 | retrieveLength(_sourceType); // otherwise, length is already there |
105 | 285 | if (_sourceType.location() == DataLocation::Memory && _sourceType.isDynamicallySized()) |
106 | 82 | { |
107 | | // increment source pointer to point to data |
108 | 82 | m_context << Instruction::SWAP1 << u256(0x20); |
109 | 82 | m_context << Instruction::ADD << Instruction::SWAP1; |
110 | 82 | } |
111 | | |
112 | | // stack: target_ref source_ref source_length |
113 | 285 | Type const* targetType = &_targetType; |
114 | 285 | Type const* sourceType = &_sourceType; |
115 | 285 | m_context.callLowLevelFunction( |
116 | 285 | "$copyArrayToStorage_" + sourceType->identifier() + "_to_" + targetType->identifier(), |
117 | 285 | 3, |
118 | 285 | 1, |
119 | 285 | [=](CompilerContext& _context) |
120 | 285 | { |
121 | 259 | ArrayUtils utils(_context); |
122 | 259 | ArrayType const& _sourceType = dynamic_cast<ArrayType const&>(*sourceType); |
123 | 259 | ArrayType const& _targetType = dynamic_cast<ArrayType const&>(*targetType); |
124 | | // stack: target_ref source_ref source_length |
125 | 259 | _context << Instruction::DUP3; |
126 | | // stack: target_ref source_ref source_length target_ref |
127 | 259 | utils.retrieveLength(_targetType); |
128 | | // stack: target_ref source_ref source_length target_ref target_length |
129 | 259 | if (_targetType.isDynamicallySized()) |
130 | 216 | { |
131 | | // store new target length |
132 | 216 | solAssert(!_targetType.isByteArrayOrString()); |
133 | 216 | _context << Instruction::DUP3 << Instruction::DUP3 << Instruction::SSTORE; |
134 | 216 | } |
135 | 259 | if (sourceBaseType->category() == Type::Category::Mapping) |
136 | 0 | { |
137 | 0 | solAssert(targetBaseType->category() == Type::Category::Mapping, ""); |
138 | 0 | solAssert(_sourceType.location() == DataLocation::Storage, ""); |
139 | | // nothing to copy |
140 | 0 | _context |
141 | 0 | << Instruction::POP << Instruction::POP |
142 | 0 | << Instruction::POP << Instruction::POP; |
143 | 0 | return; |
144 | 0 | } |
145 | | // stack: target_ref source_ref source_length target_ref target_length |
146 | | // compute hashes (data positions) |
147 | 259 | _context << Instruction::SWAP1; |
148 | 259 | if (_targetType.isDynamicallySized()) |
149 | 216 | CompilerUtils(_context).computeHashStatic(); |
150 | | // stack: target_ref source_ref source_length target_length target_data_pos |
151 | 259 | _context << Instruction::SWAP1; |
152 | 259 | utils.convertLengthToSize(_targetType); |
153 | 259 | _context << Instruction::DUP2 << Instruction::ADD; |
154 | | // stack: target_ref source_ref source_length target_data_pos target_data_end |
155 | 259 | _context << Instruction::SWAP3; |
156 | | // stack: target_ref target_data_end source_length target_data_pos source_ref |
157 | | |
158 | 259 | evmasm::AssemblyItem copyLoopEndWithoutByteOffset = _context.newTag(); |
159 | 259 | solAssert(!_targetType.isByteArrayOrString()); |
160 | | // skip copying if source length is zero |
161 | 259 | _context << Instruction::DUP3 << Instruction::ISZERO; |
162 | 259 | _context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); |
163 | | |
164 | 259 | if (_sourceType.location() == DataLocation::Storage && _sourceType.isDynamicallySized()) |
165 | 76 | CompilerUtils(_context).computeHashStatic(); |
166 | | // stack: target_ref target_data_end source_length target_data_pos source_data_pos |
167 | 259 | _context << Instruction::SWAP2; |
168 | 259 | utils.convertLengthToSize(_sourceType); |
169 | 259 | _context << Instruction::DUP3 << Instruction::ADD; |
170 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end |
171 | 259 | if (haveByteOffsetTarget) |
172 | 60 | _context << u256(0); |
173 | 259 | if (haveByteOffsetSource) |
174 | 28 | _context << u256(0); |
175 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] |
176 | 259 | evmasm::AssemblyItem copyLoopStart = _context.newTag(); |
177 | 259 | _context << copyLoopStart; |
178 | | // check for loop condition |
179 | 259 | _context |
180 | 259 | << dupInstruction(3 + byteOffsetSize) << dupInstruction(2 + byteOffsetSize) |
181 | 259 | << Instruction::GT << Instruction::ISZERO; |
182 | 259 | evmasm::AssemblyItem copyLoopEnd = _context.appendConditionalJump(); |
183 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] |
184 | | // copy |
185 | 259 | if (sourceBaseType->category() == Type::Category::Array) |
186 | 24 | { |
187 | 24 | solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); |
188 | 24 | auto const& sourceBaseArrayType = dynamic_cast<ArrayType const&>(*sourceBaseType); |
189 | | |
190 | 24 | solUnimplementedAssert( |
191 | 22 | _sourceType.location() != DataLocation::CallData || |
192 | 22 | !_sourceType.isDynamicallyEncoded() || |
193 | 22 | !sourceBaseArrayType.isDynamicallySized(), |
194 | 22 | "Copying nested calldata dynamic arrays to storage is not implemented in the old code generator." |
195 | 22 | ); |
196 | 22 | _context << Instruction::DUP3; |
197 | 22 | if (sourceBaseArrayType.location() == DataLocation::Memory) |
198 | 11 | _context << Instruction::MLOAD; |
199 | 22 | _context << Instruction::DUP3; |
200 | 22 | utils.copyArrayToStorage(dynamic_cast<ArrayType const&>(*targetBaseType), sourceBaseArrayType); |
201 | 22 | _context << Instruction::POP; |
202 | 22 | } |
203 | 235 | else if (directCopy) |
204 | 64 | { |
205 | 64 | solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); |
206 | 64 | _context |
207 | 64 | << Instruction::DUP3 << Instruction::SLOAD |
208 | 64 | << Instruction::DUP3 << Instruction::SSTORE; |
209 | 64 | } |
210 | 171 | else |
211 | 171 | { |
212 | | // Note that we have to copy each element on its own in case conversion is involved. |
213 | | // We might copy too much if there is padding at the last element, but this way end |
214 | | // checking is easier. |
215 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] |
216 | 171 | _context << dupInstruction(3 + byteOffsetSize); |
217 | 171 | if (_sourceType.location() == DataLocation::Storage) |
218 | 34 | { |
219 | 34 | if (haveByteOffsetSource) |
220 | 28 | _context << Instruction::DUP2; |
221 | 6 | else |
222 | 6 | _context << u256(0); |
223 | 34 | StorageItem(_context, *sourceBaseType).retrieveValue(SourceLocation(), true); |
224 | 34 | } |
225 | 137 | else if (sourceBaseType->isValueType()) |
226 | 129 | CompilerUtils(_context).loadFromMemoryDynamic(*sourceBaseType, fromCalldata, true, false); |
227 | 8 | else |
228 | 137 | solUnimplemented("Copying of type " + _sourceType.toString(false) + " to storage not yet supported."); |
229 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>... |
230 | 163 | assertThrow( |
231 | 163 | 2 + byteOffsetSize + sourceBaseType->sizeOnStack() <= 16, |
232 | 163 | StackTooDeepError, |
233 | 163 | util::stackTooDeepString |
234 | 163 | ); |
235 | | // fetch target storage reference |
236 | 163 | _context << dupInstruction(2 + byteOffsetSize + sourceBaseType->sizeOnStack()); |
237 | 163 | if (haveByteOffsetTarget) |
238 | 60 | _context << dupInstruction(1 + byteOffsetSize + sourceBaseType->sizeOnStack()); |
239 | 103 | else |
240 | 103 | _context << u256(0); |
241 | 163 | StorageItem(_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); |
242 | 163 | } |
243 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] |
244 | | // increment source |
245 | 249 | if (haveByteOffsetSource) |
246 | 28 | utils.incrementByteOffset(sourceBaseType->storageBytes(), 1, haveByteOffsetTarget ? 5 : 4); |
247 | 221 | else |
248 | 221 | { |
249 | 221 | _context << swapInstruction(2 + byteOffsetSize); |
250 | 221 | if (sourceIsStorage) |
251 | 78 | _context << sourceBaseType->storageSize(); |
252 | 143 | else if (_sourceType.location() == DataLocation::Memory) |
253 | 124 | _context << sourceBaseType->memoryHeadSize(); |
254 | 19 | else |
255 | 19 | _context << sourceBaseType->calldataHeadSize(); |
256 | 221 | _context |
257 | 221 | << Instruction::ADD |
258 | 221 | << swapInstruction(2 + byteOffsetSize); |
259 | 221 | } |
260 | | // increment target |
261 | 249 | if (haveByteOffsetTarget) |
262 | 60 | utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); |
263 | 189 | else |
264 | 189 | _context |
265 | 189 | << swapInstruction(1 + byteOffsetSize) |
266 | 189 | << targetBaseType->storageSize() |
267 | 189 | << Instruction::ADD |
268 | 189 | << swapInstruction(1 + byteOffsetSize); |
269 | 249 | _context.appendJumpTo(copyLoopStart); |
270 | 249 | _context << copyLoopEnd; |
271 | 249 | if (haveByteOffsetTarget) |
272 | 60 | { |
273 | | // clear elements that might be left over in the current slot in target |
274 | | // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] |
275 | 60 | _context << dupInstruction(byteOffsetSize) << Instruction::ISZERO; |
276 | 60 | evmasm::AssemblyItem copyCleanupLoopEnd = _context.appendConditionalJump(); |
277 | 60 | _context << dupInstruction(2 + byteOffsetSize) << dupInstruction(1 + byteOffsetSize); |
278 | 60 | StorageItem(_context, *targetBaseType).setToZero(SourceLocation(), true); |
279 | 60 | utils.incrementByteOffset(targetBaseType->storageBytes(), byteOffsetSize, byteOffsetSize + 2); |
280 | 60 | _context.appendJumpTo(copyLoopEnd); |
281 | | |
282 | 60 | _context << copyCleanupLoopEnd; |
283 | 60 | _context << Instruction::POP; // might pop the source, but then target is popped next |
284 | 60 | } |
285 | 249 | if (haveByteOffsetSource) |
286 | 28 | _context << Instruction::POP; |
287 | 249 | _context << copyLoopEndWithoutByteOffset; |
288 | | |
289 | | // zero-out leftovers in target |
290 | | // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end |
291 | 249 | _context << Instruction::POP << Instruction::SWAP1 << Instruction::POP; |
292 | | // stack: target_ref target_data_end target_data_pos_updated |
293 | 249 | if (targetBaseType->storageBytes() < 32) |
294 | 113 | utils.clearStorageLoop(TypeProvider::uint256()); |
295 | 136 | else |
296 | 136 | utils.clearStorageLoop(targetBaseType); |
297 | 249 | _context << Instruction::POP; |
298 | 249 | } |
299 | 285 | ); |
300 | 285 | } |
301 | | |
302 | | void ArrayUtils::copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries) const |
303 | 27.1k | { |
304 | 27.1k | solUnimplementedAssert( |
305 | 27.1k | !_sourceType.baseType()->isDynamicallySized(), |
306 | 27.1k | "Nested dynamic arrays not implemented here." |
307 | 27.1k | ); |
308 | 27.1k | CompilerUtils utils(m_context); |
309 | | |
310 | 27.1k | if (_sourceType.location() == DataLocation::CallData) |
311 | 24.8k | { |
312 | 24.8k | if (!_sourceType.isDynamicallySized()) |
313 | 9 | m_context << _sourceType.length(); |
314 | 24.8k | if (!_sourceType.isByteArrayOrString()) |
315 | 11 | convertLengthToSize(_sourceType); |
316 | | |
317 | 24.8k | string routine = "calldatacopy(target, source, len)\n"; |
318 | 24.8k | if (_padToWordBoundaries) |
319 | 24.8k | routine += R"( |
320 | 24.8k | // Set padding suffix to zero |
321 | 24.8k | mstore(add(target, len), 0) |
322 | 24.8k | len := and(add(len, 0x1f), not(0x1f)) |
323 | 24.8k | )"; |
324 | 24.8k | routine += "target := add(target, len)\n"; |
325 | 24.8k | m_context.appendInlineAssembly("{" + routine + "}", {"target", "source", "len"}); |
326 | 24.8k | m_context << Instruction::POP << Instruction::POP; |
327 | 24.8k | } |
328 | 2.27k | else if (_sourceType.location() == DataLocation::Memory) |
329 | 428 | { |
330 | 428 | retrieveLength(_sourceType); |
331 | | // stack: target source length |
332 | 428 | if (!_sourceType.baseType()->isValueType()) |
333 | 0 | { |
334 | | // copy using a loop |
335 | 0 | m_context << u256(0) << Instruction::SWAP3; |
336 | | // stack: counter source length target |
337 | 0 | auto repeat = m_context.newTag(); |
338 | 0 | m_context << repeat; |
339 | 0 | m_context << Instruction::DUP2 << Instruction::DUP5; |
340 | 0 | m_context << Instruction::LT << Instruction::ISZERO; |
341 | 0 | auto loopEnd = m_context.appendConditionalJump(); |
342 | 0 | m_context << Instruction::DUP3 << Instruction::DUP5; |
343 | 0 | accessIndex(_sourceType, false); |
344 | 0 | MemoryItem(m_context, *_sourceType.baseType(), true).retrieveValue(SourceLocation(), true); |
345 | 0 | if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType())) |
346 | 0 | copyArrayToMemory(*baseArray, _padToWordBoundaries); |
347 | 0 | else |
348 | 0 | utils.storeInMemoryDynamic(*_sourceType.baseType()); |
349 | 0 | m_context << Instruction::SWAP3 << u256(1) << Instruction::ADD; |
350 | 0 | m_context << Instruction::SWAP3; |
351 | 0 | m_context.appendJumpTo(repeat); |
352 | 0 | m_context << loopEnd; |
353 | 0 | m_context << Instruction::SWAP3; |
354 | 0 | utils.popStackSlots(3); |
355 | | // stack: updated_target_pos |
356 | 0 | return; |
357 | 0 | } |
358 | | |
359 | | // memcpy using the built-in contract |
360 | 428 | if (_sourceType.isDynamicallySized()) |
361 | 428 | { |
362 | | // change pointer to data part |
363 | 428 | m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; |
364 | 428 | m_context << Instruction::SWAP1; |
365 | 428 | } |
366 | 428 | if (!_sourceType.isByteArrayOrString()) |
367 | 0 | convertLengthToSize(_sourceType); |
368 | | // stack: <target> <source> <size> |
369 | 428 | m_context << Instruction::DUP1 << Instruction::DUP4 << Instruction::DUP4; |
370 | | // We can resort to copying full 32 bytes only if |
371 | | // - the length is known to be a multiple of 32 or |
372 | | // - we will pad to full 32 bytes later anyway. |
373 | 428 | if (!_sourceType.isByteArrayOrString() || _padToWordBoundaries) |
374 | 428 | utils.memoryCopy32(); |
375 | 0 | else |
376 | 0 | utils.memoryCopy(); |
377 | | |
378 | 428 | m_context << Instruction::SWAP1 << Instruction::POP; |
379 | | // stack: <target> <size> |
380 | | |
381 | 428 | bool paddingNeeded = _padToWordBoundaries && _sourceType.isByteArrayOrString(); |
382 | | |
383 | 428 | if (paddingNeeded) |
384 | 428 | { |
385 | | // stack: <target> <size> |
386 | 428 | m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD; |
387 | | // stack: <length> <target + size> |
388 | 428 | m_context << Instruction::SWAP1 << u256(31) << Instruction::AND; |
389 | | // stack: <target + size> <remainder = size % 32> |
390 | 428 | evmasm::AssemblyItem skip = m_context.newTag(); |
391 | 428 | if (_sourceType.isDynamicallySized()) |
392 | 428 | { |
393 | 428 | m_context << Instruction::DUP1 << Instruction::ISZERO; |
394 | 428 | m_context.appendConditionalJumpTo(skip); |
395 | 428 | } |
396 | | // round off, load from there. |
397 | | // stack <target + size> <remainder = size % 32> |
398 | 428 | m_context << Instruction::DUP1 << Instruction::DUP3; |
399 | 428 | m_context << Instruction::SUB; |
400 | | // stack: target+size remainder <target + size - remainder> |
401 | 428 | m_context << Instruction::DUP1 << Instruction::MLOAD; |
402 | | // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) |
403 | 428 | m_context << u256(1); |
404 | 428 | m_context << Instruction::DUP4 << u256(32) << Instruction::SUB; |
405 | | // stack: ...<v> 1 <32 - remainder> |
406 | 428 | m_context << u256(0x100) << Instruction::EXP << Instruction::SUB; |
407 | 428 | m_context << Instruction::NOT << Instruction::AND; |
408 | | // stack: target+size remainder target+size-remainder <v & ...> |
409 | 428 | m_context << Instruction::DUP2 << Instruction::MSTORE; |
410 | | // stack: target+size remainder target+size-remainder |
411 | 428 | m_context << u256(32) << Instruction::ADD; |
412 | | // stack: target+size remainder <new_padded_end> |
413 | 428 | m_context << Instruction::SWAP2 << Instruction::POP; |
414 | | |
415 | 428 | if (_sourceType.isDynamicallySized()) |
416 | 428 | m_context << skip.tag(); |
417 | | // stack <target + "size"> <remainder = size % 32> |
418 | 428 | m_context << Instruction::POP; |
419 | 428 | } |
420 | 0 | else |
421 | | // stack: <target> <size> |
422 | 0 | m_context << Instruction::ADD; |
423 | 428 | } |
424 | 1.85k | else |
425 | 1.85k | { |
426 | 1.85k | solAssert(_sourceType.location() == DataLocation::Storage, ""); |
427 | 1.85k | unsigned storageBytes = _sourceType.baseType()->storageBytes(); |
428 | 1.85k | u256 storageSize = _sourceType.baseType()->storageSize(); |
429 | 1.85k | solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); |
430 | | |
431 | 1.85k | retrieveLength(_sourceType); |
432 | | // stack here: memory_offset storage_offset length |
433 | | // jump to end if length is zero |
434 | 1.85k | m_context << Instruction::DUP1 << Instruction::ISZERO; |
435 | 1.85k | evmasm::AssemblyItem loopEnd = m_context.appendConditionalJump(); |
436 | | // Special case for tightly-stored byte arrays |
437 | 1.85k | if (_sourceType.isByteArrayOrString()) |
438 | 1.15k | { |
439 | | // stack here: memory_offset storage_offset length |
440 | 1.15k | m_context << Instruction::DUP1 << u256(31) << Instruction::LT; |
441 | 1.15k | evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); |
442 | | // store the short byte array (discard lower-order byte) |
443 | 1.15k | m_context << u256(0x100) << Instruction::DUP1; |
444 | 1.15k | m_context << Instruction::DUP4 << Instruction::SLOAD; |
445 | 1.15k | m_context << Instruction::DIV << Instruction::MUL; |
446 | 1.15k | m_context << Instruction::DUP4 << Instruction::MSTORE; |
447 | | // stack here: memory_offset storage_offset length |
448 | | // add 32 or length to memory offset |
449 | 1.15k | m_context << Instruction::SWAP2; |
450 | 1.15k | if (_padToWordBoundaries) |
451 | 1.15k | m_context << u256(32); |
452 | 0 | else |
453 | 0 | m_context << Instruction::DUP3; |
454 | 1.15k | m_context << Instruction::ADD; |
455 | 1.15k | m_context << Instruction::SWAP2; |
456 | 1.15k | m_context.appendJumpTo(loopEnd); |
457 | 1.15k | m_context << longByteArray; |
458 | 1.15k | } |
459 | 698 | else |
460 | | // convert length to memory size |
461 | 698 | m_context << _sourceType.baseType()->memoryHeadSize() << Instruction::MUL; |
462 | | |
463 | 1.85k | m_context << Instruction::DUP3 << Instruction::ADD << Instruction::SWAP2; |
464 | 1.85k | if (_sourceType.isDynamicallySized()) |
465 | 1.60k | { |
466 | | // actual array data is stored at KECCAK256(storage_offset) |
467 | 1.60k | m_context << Instruction::SWAP1; |
468 | 1.60k | utils.computeHashStatic(); |
469 | 1.60k | m_context << Instruction::SWAP1; |
470 | 1.60k | } |
471 | | |
472 | | // stack here: memory_end_offset storage_data_offset memory_offset |
473 | 1.85k | bool haveByteOffset = !_sourceType.isByteArrayOrString() && storageBytes <= 16; |
474 | 1.85k | if (haveByteOffset) |
475 | 403 | m_context << u256(0) << Instruction::SWAP1; |
476 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
477 | 1.85k | evmasm::AssemblyItem loopStart = m_context.newTag(); |
478 | 1.85k | m_context << loopStart; |
479 | | // load and store |
480 | 1.85k | if (_sourceType.isByteArrayOrString()) |
481 | 1.15k | { |
482 | | // Packed both in storage and memory. |
483 | 1.15k | m_context << Instruction::DUP2 << Instruction::SLOAD; |
484 | 1.15k | m_context << Instruction::DUP2 << Instruction::MSTORE; |
485 | | // increment storage_data_offset by 1 |
486 | 1.15k | m_context << Instruction::SWAP1 << u256(1) << Instruction::ADD; |
487 | | // increment memory offset by 32 |
488 | 1.15k | m_context << Instruction::SWAP1 << u256(32) << Instruction::ADD; |
489 | 1.15k | } |
490 | 698 | else |
491 | 698 | { |
492 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
493 | 698 | if (haveByteOffset) |
494 | 403 | m_context << Instruction::DUP3 << Instruction::DUP3; |
495 | 295 | else |
496 | 295 | m_context << Instruction::DUP2 << u256(0); |
497 | 698 | StorageItem(m_context, *_sourceType.baseType()).retrieveValue(SourceLocation(), true); |
498 | 698 | if (auto baseArray = dynamic_cast<ArrayType const*>(_sourceType.baseType())) |
499 | 0 | copyArrayToMemory(*baseArray, _padToWordBoundaries); |
500 | 698 | else |
501 | 698 | utils.storeInMemoryDynamic(*_sourceType.baseType()); |
502 | | // increment storage_data_offset and byte offset |
503 | 698 | if (haveByteOffset) |
504 | 403 | incrementByteOffset(storageBytes, 2, 3); |
505 | 295 | else |
506 | 295 | { |
507 | 295 | m_context << Instruction::SWAP1; |
508 | 295 | m_context << storageSize << Instruction::ADD; |
509 | 295 | m_context << Instruction::SWAP1; |
510 | 295 | } |
511 | 698 | } |
512 | | // check for loop condition |
513 | 1.85k | m_context << Instruction::DUP1 << dupInstruction(haveByteOffset ? 5 : 4); |
514 | 1.85k | m_context << Instruction::GT; |
515 | 1.85k | m_context.appendConditionalJumpTo(loopStart); |
516 | | // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset |
517 | 1.85k | if (haveByteOffset) |
518 | 403 | m_context << Instruction::SWAP1 << Instruction::POP; |
519 | 1.85k | if (!_sourceType.isByteArrayOrString()) |
520 | 698 | { |
521 | 698 | solAssert(_sourceType.calldataStride() % 32 == 0, ""); |
522 | 698 | solAssert(_sourceType.memoryStride() % 32 == 0, ""); |
523 | 698 | } |
524 | 1.85k | if (_padToWordBoundaries && _sourceType.isByteArrayOrString()) |
525 | 1.15k | { |
526 | | // memory_end_offset - start is the actual length (we want to compute the ceil of). |
527 | | // memory_offset - start is its next multiple of 32, but it might be off by 32. |
528 | | // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 |
529 | 1.15k | m_context << Instruction::DUP3 << Instruction::SWAP1 << Instruction::SUB; |
530 | 1.15k | m_context << u256(31) << Instruction::AND; |
531 | 1.15k | m_context << Instruction::DUP3 << Instruction::ADD; |
532 | 1.15k | m_context << Instruction::SWAP2; |
533 | 1.15k | } |
534 | 1.85k | m_context << loopEnd << Instruction::POP << Instruction::POP; |
535 | 1.85k | } |
536 | 27.1k | } |
537 | | |
538 | | void ArrayUtils::clearArray(ArrayType const& _typeIn) const |
539 | 123 | { |
540 | 123 | Type const* type = &_typeIn; |
541 | 123 | m_context.callLowLevelFunction( |
542 | 123 | "$clearArray_" + _typeIn.identifier(), |
543 | 123 | 2, |
544 | 123 | 0, |
545 | 123 | [type](CompilerContext& _context) |
546 | 123 | { |
547 | 98 | ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); |
548 | 98 | unsigned stackHeightStart = _context.stackHeight(); |
549 | 98 | solAssert(_type.location() == DataLocation::Storage, ""); |
550 | 98 | if (_type.baseType()->storageBytes() < 32) |
551 | 26 | { |
552 | 26 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
553 | 26 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid storage size for type."); |
554 | 26 | } |
555 | 98 | if (_type.baseType()->isValueType()) |
556 | 98 | solAssert(_type.baseType()->storageSize() <= 1, "Invalid size for value type."); |
557 | | |
558 | 98 | _context << Instruction::POP; // remove byte offset |
559 | 98 | if (_type.isDynamicallySized()) |
560 | 85 | ArrayUtils(_context).clearDynamicArray(_type); |
561 | 13 | else if (_type.length() == 0 || _type.baseType()->category() == Type::Category::Mapping) |
562 | 0 | _context << Instruction::POP; |
563 | 13 | else if (_type.baseType()->isValueType() && _type.storageSize() <= 5) |
564 | 10 | { |
565 | | // unroll loop for small arrays @todo choose a good value |
566 | | // Note that we loop over storage slots here, not elements. |
567 | 28 | for (unsigned i = 1; i < _type.storageSize(); ++i) |
568 | 18 | _context |
569 | 18 | << u256(0) << Instruction::DUP2 << Instruction::SSTORE |
570 | 18 | << u256(1) << Instruction::ADD; |
571 | 10 | _context << u256(0) << Instruction::SWAP1 << Instruction::SSTORE; |
572 | 10 | } |
573 | 3 | else if (!_type.baseType()->isValueType() && _type.length() <= 4) |
574 | 0 | { |
575 | | // unroll loop for small arrays @todo choose a good value |
576 | 0 | solAssert(_type.baseType()->storageBytes() >= 32, "Invalid storage size."); |
577 | 0 | for (unsigned i = 1; i < _type.length(); ++i) |
578 | 0 | { |
579 | 0 | _context << u256(0); |
580 | 0 | StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), false); |
581 | 0 | _context |
582 | 0 | << Instruction::POP |
583 | 0 | << u256(_type.baseType()->storageSize()) << Instruction::ADD; |
584 | 0 | } |
585 | 0 | _context << u256(0); |
586 | 0 | StorageItem(_context, *_type.baseType()).setToZero(SourceLocation(), true); |
587 | 0 | } |
588 | 3 | else |
589 | 3 | { |
590 | 3 | _context << Instruction::DUP1 << _type.length(); |
591 | 3 | ArrayUtils(_context).convertLengthToSize(_type); |
592 | 3 | _context << Instruction::ADD << Instruction::SWAP1; |
593 | 3 | if (_type.baseType()->storageBytes() < 32) |
594 | 0 | ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); |
595 | 3 | else |
596 | 3 | ArrayUtils(_context).clearStorageLoop(_type.baseType()); |
597 | 3 | _context << Instruction::POP; |
598 | 3 | } |
599 | 98 | solAssert(_context.stackHeight() == stackHeightStart - 2, ""); |
600 | 98 | } |
601 | 123 | ); |
602 | 123 | } |
603 | | |
604 | | void ArrayUtils::clearDynamicArray(ArrayType const& _type) const |
605 | 85 | { |
606 | 85 | solAssert(_type.location() == DataLocation::Storage, ""); |
607 | 85 | solAssert(_type.isDynamicallySized(), ""); |
608 | | |
609 | | // fetch length |
610 | 85 | retrieveLength(_type); |
611 | | // set length to zero |
612 | 85 | m_context << u256(0) << Instruction::DUP3 << Instruction::SSTORE; |
613 | | // Special case: short byte arrays are stored togeher with their length |
614 | 85 | evmasm::AssemblyItem endTag = m_context.newTag(); |
615 | 85 | if (_type.isByteArrayOrString()) |
616 | 13 | { |
617 | | // stack: ref old_length |
618 | 13 | m_context << Instruction::DUP1 << u256(31) << Instruction::LT; |
619 | 13 | evmasm::AssemblyItem longByteArray = m_context.appendConditionalJump(); |
620 | 13 | m_context << Instruction::POP; |
621 | 13 | m_context.appendJumpTo(endTag); |
622 | 13 | m_context.adjustStackOffset(1); // needed because of jump |
623 | 13 | m_context << longByteArray; |
624 | 13 | } |
625 | | // stack: ref old_length |
626 | 85 | convertLengthToSize(_type); |
627 | | // compute data positions |
628 | 85 | m_context << Instruction::SWAP1; |
629 | 85 | CompilerUtils(m_context).computeHashStatic(); |
630 | | // stack: len data_pos |
631 | 85 | m_context << Instruction::SWAP1 << Instruction::DUP2 << Instruction::ADD |
632 | 85 | << Instruction::SWAP1; |
633 | | // stack: data_pos_end data_pos |
634 | 85 | if (_type.storageStride() < 32) |
635 | 22 | clearStorageLoop(TypeProvider::uint256()); |
636 | 63 | else |
637 | 63 | clearStorageLoop(_type.baseType()); |
638 | | // cleanup |
639 | 85 | m_context << endTag; |
640 | 85 | m_context << Instruction::POP; |
641 | 85 | } |
642 | | |
643 | | void ArrayUtils::resizeDynamicArray(ArrayType const& _typeIn) const |
644 | 0 | { |
645 | 0 | Type const* type = &_typeIn; |
646 | 0 | m_context.callLowLevelFunction( |
647 | 0 | "$resizeDynamicArray_" + _typeIn.identifier(), |
648 | 0 | 2, |
649 | 0 | 0, |
650 | 0 | [type](CompilerContext& _context) |
651 | 0 | { |
652 | 0 | ArrayType const& _type = dynamic_cast<ArrayType const&>(*type); |
653 | 0 | solAssert(_type.location() == DataLocation::Storage, ""); |
654 | 0 | solAssert(_type.isDynamicallySized(), ""); |
655 | 0 | if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) |
656 | 0 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
657 | | |
658 | 0 | unsigned stackHeightStart = _context.stackHeight(); |
659 | 0 | evmasm::AssemblyItem resizeEnd = _context.newTag(); |
660 | | |
661 | | // stack: ref new_length |
662 | | // fetch old length |
663 | 0 | ArrayUtils(_context).retrieveLength(_type, 1); |
664 | | // stack: ref new_length old_length |
665 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "2"); |
666 | | |
667 | | // Special case for short byte arrays, they are stored together with their length |
668 | 0 | if (_type.isByteArrayOrString()) |
669 | 0 | { |
670 | 0 | evmasm::AssemblyItem regularPath = _context.newTag(); |
671 | | // We start by a large case-distinction about the old and new length of the byte array. |
672 | |
|
673 | 0 | _context << Instruction::DUP3 << Instruction::SLOAD; |
674 | | // stack: ref new_length current_length ref_value |
675 | |
|
676 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); |
677 | 0 | _context << Instruction::DUP2 << u256(31) << Instruction::LT; |
678 | 0 | evmasm::AssemblyItem currentIsLong = _context.appendConditionalJump(); |
679 | 0 | _context << Instruction::DUP3 << u256(31) << Instruction::LT; |
680 | 0 | evmasm::AssemblyItem newIsLong = _context.appendConditionalJump(); |
681 | | |
682 | | // Here: short -> short |
683 | | |
684 | | // Compute 1 << (256 - 8 * new_size) |
685 | 0 | evmasm::AssemblyItem shortToShort = _context.newTag(); |
686 | 0 | _context << shortToShort; |
687 | 0 | _context << Instruction::DUP3 << u256(8) << Instruction::MUL; |
688 | 0 | _context << u256(0x100) << Instruction::SUB; |
689 | 0 | _context << u256(2) << Instruction::EXP; |
690 | | // Divide and multiply by that value, clearing bits. |
691 | 0 | _context << Instruction::DUP1 << Instruction::SWAP2; |
692 | 0 | _context << Instruction::DIV << Instruction::MUL; |
693 | | // Insert 2*length. |
694 | 0 | _context << Instruction::DUP3 << Instruction::DUP1 << Instruction::ADD; |
695 | 0 | _context << Instruction::OR; |
696 | | // Store. |
697 | 0 | _context << Instruction::DUP4 << Instruction::SSTORE; |
698 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); |
699 | 0 | _context.appendJumpTo(resizeEnd); |
700 | |
|
701 | 0 | _context.adjustStackOffset(1); // we have to do that because of the jumps |
702 | | // Here: short -> long |
703 | |
|
704 | 0 | _context << newIsLong; |
705 | | // stack: ref new_length current_length ref_value |
706 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); |
707 | | // Zero out lower-order byte. |
708 | 0 | _context << u256(0xff) << Instruction::NOT << Instruction::AND; |
709 | | // Store at data location. |
710 | 0 | _context << Instruction::DUP4; |
711 | 0 | CompilerUtils(_context).computeHashStatic(); |
712 | 0 | _context << Instruction::SSTORE; |
713 | | // stack: ref new_length current_length |
714 | | // Store new length: Compule 2*length + 1 and store it. |
715 | 0 | _context << Instruction::DUP2 << Instruction::DUP1 << Instruction::ADD; |
716 | 0 | _context << u256(1) << Instruction::ADD; |
717 | | // stack: ref new_length current_length 2*new_length+1 |
718 | 0 | _context << Instruction::DUP4 << Instruction::SSTORE; |
719 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 3 - 2, "3"); |
720 | 0 | _context.appendJumpTo(resizeEnd); |
721 | |
|
722 | 0 | _context.adjustStackOffset(1); // we have to do that because of the jumps |
723 | |
|
724 | 0 | _context << currentIsLong; |
725 | 0 | _context << Instruction::DUP3 << u256(31) << Instruction::LT; |
726 | 0 | _context.appendConditionalJumpTo(regularPath); |
727 | | |
728 | | // Here: long -> short |
729 | | // Read the first word of the data and store it on the stack. Clear the data location and |
730 | | // then jump to the short -> short case. |
731 | | |
732 | | // stack: ref new_length current_length ref_value |
733 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); |
734 | 0 | _context << Instruction::POP << Instruction::DUP3; |
735 | 0 | CompilerUtils(_context).computeHashStatic(); |
736 | 0 | _context << Instruction::DUP1 << Instruction::SLOAD << Instruction::SWAP1; |
737 | | // stack: ref new_length current_length first_word data_location |
738 | 0 | _context << Instruction::DUP3; |
739 | 0 | ArrayUtils(_context).convertLengthToSize(_type); |
740 | 0 | _context << Instruction::DUP2 << Instruction::ADD << Instruction::SWAP1; |
741 | | // stack: ref new_length current_length first_word data_location_end data_location |
742 | 0 | ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); |
743 | 0 | _context << Instruction::POP; |
744 | | // stack: ref new_length current_length first_word |
745 | 0 | solAssert(_context.stackHeight() - stackHeightStart == 4 - 2, "3"); |
746 | 0 | _context.appendJumpTo(shortToShort); |
747 | |
|
748 | 0 | _context << regularPath; |
749 | | // stack: ref new_length current_length ref_value |
750 | 0 | _context << Instruction::POP; |
751 | 0 | } |
752 | | |
753 | | // Change of length for a regular array (i.e. length at location, data at KECCAK256(location)). |
754 | | // stack: ref new_length old_length |
755 | | // store new length |
756 | 0 | _context << Instruction::DUP2; |
757 | 0 | if (_type.isByteArrayOrString()) |
758 | | // For a "long" byte array, store length as 2*length+1 |
759 | 0 | _context << Instruction::DUP1 << Instruction::ADD << u256(1) << Instruction::ADD; |
760 | 0 | _context << Instruction::DUP4 << Instruction::SSTORE; |
761 | | // skip if size is not reduced |
762 | 0 | _context << Instruction::DUP2 << Instruction::DUP2 |
763 | 0 | << Instruction::GT << Instruction::ISZERO; |
764 | 0 | _context.appendConditionalJumpTo(resizeEnd); |
765 | | |
766 | | // size reduced, clear the end of the array |
767 | | // stack: ref new_length old_length |
768 | 0 | ArrayUtils(_context).convertLengthToSize(_type); |
769 | 0 | _context << Instruction::DUP2; |
770 | 0 | ArrayUtils(_context).convertLengthToSize(_type); |
771 | | // stack: ref new_length old_size new_size |
772 | | // compute data positions |
773 | 0 | _context << Instruction::DUP4; |
774 | 0 | CompilerUtils(_context).computeHashStatic(); |
775 | | // stack: ref new_length old_size new_size data_pos |
776 | 0 | _context << Instruction::SWAP2 << Instruction::DUP3 << Instruction::ADD; |
777 | | // stack: ref new_length data_pos new_size delete_end |
778 | 0 | _context << Instruction::SWAP2 << Instruction::ADD; |
779 | | // stack: ref new_length delete_end delete_start |
780 | 0 | if (_type.storageStride() < 32) |
781 | 0 | ArrayUtils(_context).clearStorageLoop(TypeProvider::uint256()); |
782 | 0 | else |
783 | 0 | ArrayUtils(_context).clearStorageLoop(_type.baseType()); |
784 | |
|
785 | 0 | _context << resizeEnd; |
786 | | // cleanup |
787 | 0 | _context << Instruction::POP << Instruction::POP << Instruction::POP; |
788 | 0 | solAssert(_context.stackHeight() == stackHeightStart - 2, ""); |
789 | 0 | } |
790 | 0 | ); |
791 | 0 | } |
792 | | |
793 | | void ArrayUtils::incrementDynamicArraySize(ArrayType const& _type) const |
794 | 74.3k | { |
795 | 74.3k | solAssert(_type.location() == DataLocation::Storage, ""); |
796 | 74.3k | solAssert(_type.isDynamicallySized(), ""); |
797 | 74.3k | if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) |
798 | 74.3k | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
799 | | |
800 | 74.3k | if (_type.isByteArrayOrString()) |
801 | 105 | { |
802 | | // We almost always just add 2 (length of byte arrays is shifted left by one) |
803 | | // except for the case where we transition from a short byte array |
804 | | // to a long byte array, there we have to copy. |
805 | | // This happens if the length is exactly 31, which means that the |
806 | | // lowest-order byte (we actually use a mask with fewer bits) must |
807 | | // be (31*2+0) = 62 |
808 | | |
809 | 105 | m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1; |
810 | 105 | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
811 | 105 | m_context.appendInlineAssembly(R"({ |
812 | 105 | // We have to copy if length is exactly 31, because that marks |
813 | 105 | // the transition between in-place and out-of-place storage. |
814 | 105 | switch length |
815 | 105 | case 31 |
816 | 105 | { |
817 | 105 | mstore(0, ref) |
818 | 105 | let data_area := keccak256(0, 0x20) |
819 | 105 | sstore(data_area, and(data, not(0xff))) |
820 | 105 | // Set old length in new format (31 * 2 + 1) |
821 | 105 | data := 63 |
822 | 105 | } |
823 | 105 | sstore(ref, add(data, 2)) |
824 | 105 | // return new length in ref |
825 | 105 | ref := add(length, 1) |
826 | 105 | })", {"ref", "data", "length"}); |
827 | 105 | m_context << Instruction::POP << Instruction::POP; |
828 | 105 | } |
829 | 74.2k | else |
830 | 74.2k | m_context.appendInlineAssembly(R"({ |
831 | 74.2k | let new_length := add(sload(ref), 1) |
832 | 74.2k | sstore(ref, new_length) |
833 | 74.2k | ref := new_length |
834 | 74.2k | })", {"ref"}); |
835 | 74.3k | } |
836 | | |
837 | | void ArrayUtils::popStorageArrayElement(ArrayType const& _type) const |
838 | 247 | { |
839 | 247 | solAssert(_type.location() == DataLocation::Storage, ""); |
840 | 247 | solAssert(_type.isDynamicallySized(), ""); |
841 | 247 | if (!_type.isByteArrayOrString() && _type.baseType()->storageBytes() < 32) |
842 | 247 | solAssert(_type.baseType()->isValueType(), "Invalid storage size for non-value type."); |
843 | | |
844 | 247 | if (_type.isByteArrayOrString()) |
845 | 45 | { |
846 | 45 | m_context << Instruction::DUP1 << Instruction::SLOAD << Instruction::DUP1; |
847 | 45 | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
848 | 45 | util::Whiskers code(R"({ |
849 | 45 | if iszero(length) { |
850 | 45 | mstore(0, <panicSelector>) |
851 | 45 | mstore(4, <emptyArrayPop>) |
852 | 45 | revert(0, 0x24) |
853 | 45 | } |
854 | 45 | switch gt(length, 31) |
855 | 45 | case 0 { |
856 | 45 | // short byte array |
857 | 45 | // Zero-out the suffix including the least significant byte. |
858 | 45 | let mask := sub(exp(0x100, sub(33, length)), 1) |
859 | 45 | length := sub(length, 1) |
860 | 45 | slot_value := or(and(not(mask), slot_value), mul(length, 2)) |
861 | 45 | } |
862 | 45 | case 1 { |
863 | 45 | // long byte array |
864 | 45 | mstore(0, ref) |
865 | 45 | let slot := keccak256(0, 0x20) |
866 | 45 | switch length |
867 | 45 | case 32 |
868 | 45 | { |
869 | 45 | let data := sload(slot) |
870 | 45 | sstore(slot, 0) |
871 | 45 | data := and(data, not(0xff)) |
872 | 45 | slot_value := or(data, 62) |
873 | 45 | } |
874 | 45 | default |
875 | 45 | { |
876 | 45 | let offset_inside_slot := and(sub(length, 1), 0x1f) |
877 | 45 | slot := add(slot, div(sub(length, 1), 32)) |
878 | 45 | let data := sload(slot) |
879 | 45 | |
880 | 45 | // Zero-out the suffix of the byte array by masking it. |
881 | 45 | // ((1<<(8 * (32 - offset))) - 1) |
882 | 45 | let mask := sub(exp(0x100, sub(32, offset_inside_slot)), 1) |
883 | 45 | data := and(not(mask), data) |
884 | 45 | sstore(slot, data) |
885 | 45 | |
886 | 45 | // Reduce the length by 1 |
887 | 45 | slot_value := sub(slot_value, 2) |
888 | 45 | } |
889 | 45 | } |
890 | 45 | sstore(ref, slot_value) |
891 | 45 | })"); |
892 | 45 | code("panicSelector", util::selectorFromSignature("Panic(uint256)").str()); |
893 | 45 | code("emptyArrayPop", to_string(unsigned(util::PanicCode::EmptyArrayPop))); |
894 | 45 | m_context.appendInlineAssembly(code.render(), {"ref", "slot_value", "length"}); |
895 | 45 | m_context << Instruction::POP << Instruction::POP << Instruction::POP; |
896 | 45 | } |
897 | 202 | else |
898 | 202 | { |
899 | | // stack: ArrayReference |
900 | 202 | retrieveLength(_type); |
901 | | // stack: ArrayReference oldLength |
902 | 202 | m_context << Instruction::DUP1; |
903 | | // stack: ArrayReference oldLength oldLength |
904 | 202 | m_context << Instruction::ISZERO; |
905 | 202 | m_context.appendConditionalPanic(util::PanicCode::EmptyArrayPop); |
906 | | |
907 | | // Stack: ArrayReference oldLength |
908 | 202 | m_context << u256(1) << Instruction::SWAP1 << Instruction::SUB; |
909 | | // Stack ArrayReference newLength |
910 | | |
911 | 202 | if (_type.baseType()->category() != Type::Category::Mapping) |
912 | 198 | { |
913 | 198 | m_context << Instruction::DUP2 << Instruction::DUP2; |
914 | | // Stack ArrayReference newLength ArrayReference newLength; |
915 | 198 | accessIndex(_type, false); |
916 | | // Stack: ArrayReference newLength storage_slot byte_offset |
917 | 198 | StorageItem(m_context, *_type.baseType()).setToZero(SourceLocation(), true); |
918 | 198 | } |
919 | | |
920 | | // Stack: ArrayReference newLength |
921 | 202 | m_context << Instruction::SWAP1 << Instruction::SSTORE; |
922 | 202 | } |
923 | 247 | } |
924 | | |
925 | | void ArrayUtils::clearStorageLoop(Type const* _type) const |
926 | 337 | { |
927 | 337 | solAssert(_type->storageBytes() >= 32, ""); |
928 | 337 | m_context.callLowLevelFunction( |
929 | 337 | "$clearStorageLoop_" + _type->identifier(), |
930 | 337 | 2, |
931 | 337 | 1, |
932 | 337 | [_type](CompilerContext& _context) |
933 | 337 | { |
934 | 304 | unsigned stackHeightStart = _context.stackHeight(); |
935 | 304 | if (_type->category() == Type::Category::Mapping) |
936 | 3 | { |
937 | 3 | _context << Instruction::POP; |
938 | 3 | return; |
939 | 3 | } |
940 | | // stack: end_pos pos |
941 | | |
942 | 301 | evmasm::AssemblyItem loopStart = _context.appendJumpToNew(); |
943 | 301 | _context << loopStart; |
944 | | // check for loop condition |
945 | 301 | _context << |
946 | 301 | Instruction::DUP1 << |
947 | 301 | Instruction::DUP3 << |
948 | 301 | Instruction::GT << |
949 | 301 | Instruction::ISZERO; |
950 | 301 | evmasm::AssemblyItem zeroLoopEnd = _context.newTag(); |
951 | 301 | _context.appendConditionalJumpTo(zeroLoopEnd); |
952 | | // delete |
953 | 301 | _context << u256(0); |
954 | 301 | StorageItem(_context, *_type).setToZero(SourceLocation(), false); |
955 | 301 | _context << Instruction::POP; |
956 | | // increment |
957 | 301 | _context << _type->storageSize() << Instruction::ADD; |
958 | 301 | _context.appendJumpTo(loopStart); |
959 | | // cleanup |
960 | 301 | _context << zeroLoopEnd; |
961 | 301 | _context << Instruction::POP; |
962 | | |
963 | 301 | solAssert(_context.stackHeight() == stackHeightStart - 1, ""); |
964 | 301 | } |
965 | 337 | ); |
966 | 337 | } |
967 | | |
968 | | void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const |
969 | 29.0k | { |
970 | 29.0k | if (_arrayType.location() == DataLocation::Storage) |
971 | 453 | { |
972 | 453 | if (_arrayType.baseType()->storageSize() <= 1) |
973 | 416 | { |
974 | 416 | unsigned baseBytes = _arrayType.baseType()->storageBytes(); |
975 | 416 | if (baseBytes == 0) |
976 | 0 | m_context << Instruction::POP << u256(1); |
977 | 416 | else if (baseBytes <= 16) |
978 | 178 | { |
979 | 178 | unsigned itemsPerSlot = 32 / baseBytes; |
980 | 178 | m_context |
981 | 178 | << u256(itemsPerSlot - 1) << Instruction::ADD |
982 | 178 | << u256(itemsPerSlot) << Instruction::SWAP1 << Instruction::DIV; |
983 | 178 | } |
984 | 416 | } |
985 | 37 | else |
986 | 37 | m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; |
987 | 453 | } |
988 | 28.6k | else |
989 | 28.6k | { |
990 | 28.6k | if (!_arrayType.isByteArrayOrString()) |
991 | 2.66k | { |
992 | 2.66k | if (_arrayType.location() == DataLocation::Memory) |
993 | 2.62k | m_context << _arrayType.memoryStride(); |
994 | 36 | else |
995 | 36 | m_context << _arrayType.calldataStride(); |
996 | 2.66k | m_context << Instruction::MUL; |
997 | 2.66k | } |
998 | 25.9k | else if (_pad) |
999 | 25.9k | m_context << u256(31) << Instruction::ADD |
1000 | 25.9k | << u256(32) << Instruction::DUP1 |
1001 | 25.9k | << Instruction::SWAP2 << Instruction::DIV << Instruction::MUL; |
1002 | 28.6k | } |
1003 | 29.0k | } |
1004 | | |
1005 | | void ArrayUtils::retrieveLength(ArrayType const& _arrayType, unsigned _stackDepth) const |
1006 | 5.34M | { |
1007 | 5.34M | if (!_arrayType.isDynamicallySized()) |
1008 | 2.03M | m_context << _arrayType.length(); |
1009 | 3.31M | else |
1010 | 3.31M | { |
1011 | 3.31M | m_context << dupInstruction(1 + _stackDepth); |
1012 | 3.31M | switch (_arrayType.location()) |
1013 | 3.31M | { |
1014 | 406k | case DataLocation::CallData: |
1015 | | // length is stored on the stack |
1016 | 406k | break; |
1017 | 2.38M | case DataLocation::Memory: |
1018 | 2.38M | m_context << Instruction::MLOAD; |
1019 | 2.38M | break; |
1020 | 519k | case DataLocation::Storage: |
1021 | 519k | m_context << Instruction::SLOAD; |
1022 | 519k | if (_arrayType.isByteArrayOrString()) |
1023 | 2.52k | m_context.callYulFunction(m_context.utilFunctions().extractByteArrayLengthFunction(), 1, 1); |
1024 | 519k | break; |
1025 | 3.31M | } |
1026 | 3.31M | } |
1027 | 5.34M | } |
1028 | | |
1029 | | void ArrayUtils::accessIndex(ArrayType const& _arrayType, bool _doBoundsCheck, bool _keepReference) const |
1030 | 5.38M | { |
1031 | | /// Stack: reference [length] index |
1032 | 5.38M | DataLocation location = _arrayType.location(); |
1033 | | |
1034 | 5.38M | if (_doBoundsCheck) |
1035 | 5.30M | { |
1036 | | // retrieve length |
1037 | 5.30M | ArrayUtils::retrieveLength(_arrayType, 1); |
1038 | | // Stack: ref [length] index length |
1039 | | // check out-of-bounds access |
1040 | 5.30M | m_context << Instruction::DUP2 << Instruction::LT << Instruction::ISZERO; |
1041 | | // out-of-bounds access throws exception |
1042 | 5.30M | m_context.appendConditionalPanic(util::PanicCode::ArrayOutOfBounds); |
1043 | 5.30M | } |
1044 | 5.38M | if (location == DataLocation::CallData && _arrayType.isDynamicallySized()) |
1045 | | // remove length if present |
1046 | 382k | m_context << Instruction::SWAP1 << Instruction::POP; |
1047 | | |
1048 | | // stack: <base_ref> <index> |
1049 | 5.38M | switch (location) |
1050 | 5.38M | { |
1051 | 3.69M | case DataLocation::Memory: |
1052 | | // stack: <base_ref> <index> |
1053 | 3.69M | if (!_arrayType.isByteArrayOrString()) |
1054 | 3.64M | m_context << u256(_arrayType.memoryHeadSize()) << Instruction::MUL; |
1055 | 3.69M | if (_arrayType.isDynamicallySized()) |
1056 | 2.38M | m_context << u256(32) << Instruction::ADD; |
1057 | 3.69M | if (_keepReference) |
1058 | 0 | m_context << Instruction::DUP2; |
1059 | 3.69M | m_context << Instruction::ADD; |
1060 | 3.69M | break; |
1061 | 599k | case DataLocation::CallData: |
1062 | 599k | if (!_arrayType.isByteArrayOrString()) |
1063 | 599k | { |
1064 | 599k | m_context << _arrayType.calldataStride(); |
1065 | 599k | m_context << Instruction::MUL; |
1066 | 599k | } |
1067 | | // stack: <base_ref> <index * size> |
1068 | 599k | if (_keepReference) |
1069 | 444k | m_context << Instruction::DUP2; |
1070 | 599k | m_context << Instruction::ADD; |
1071 | 599k | break; |
1072 | 1.09M | case DataLocation::Storage: |
1073 | 1.09M | { |
1074 | 1.09M | if (_keepReference) |
1075 | 0 | m_context << Instruction::DUP2; |
1076 | 1.09M | else |
1077 | 1.09M | m_context << Instruction::SWAP1; |
1078 | | // stack: [<base_ref>] <index> <base_ref> |
1079 | | |
1080 | 1.09M | evmasm::AssemblyItem endTag = m_context.newTag(); |
1081 | 1.09M | if (_arrayType.isByteArrayOrString()) |
1082 | 247 | { |
1083 | | // Special case of short byte arrays. |
1084 | 247 | m_context << Instruction::SWAP1; |
1085 | 247 | m_context << Instruction::DUP2 << Instruction::SLOAD; |
1086 | 247 | m_context << u256(1) << Instruction::AND << Instruction::ISZERO; |
1087 | | // No action needed for short byte arrays. |
1088 | 247 | m_context.appendConditionalJumpTo(endTag); |
1089 | 247 | m_context << Instruction::SWAP1; |
1090 | 247 | } |
1091 | 1.09M | if (_arrayType.isDynamicallySized()) |
1092 | 589k | CompilerUtils(m_context).computeHashStatic(); |
1093 | 1.09M | m_context << Instruction::SWAP1; |
1094 | 1.09M | if (_arrayType.baseType()->storageBytes() <= 16) |
1095 | 53.6k | { |
1096 | | // stack: <data_ref> <index> |
1097 | | // goal: |
1098 | | // <ref> <byte_number> = <base_ref + index / itemsPerSlot> <(index % itemsPerSlot) * byteSize> |
1099 | 53.6k | unsigned byteSize = _arrayType.baseType()->storageBytes(); |
1100 | 53.6k | solAssert(byteSize != 0, ""); |
1101 | 53.6k | unsigned itemsPerSlot = 32 / byteSize; |
1102 | 53.6k | m_context << u256(itemsPerSlot) << Instruction::SWAP2; |
1103 | | // stack: itemsPerSlot index data_ref |
1104 | 53.6k | m_context |
1105 | 53.6k | << Instruction::DUP3 << Instruction::DUP3 |
1106 | 53.6k | << Instruction::DIV << Instruction::ADD |
1107 | | // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) |
1108 | 53.6k | << Instruction::SWAP2 << Instruction::SWAP1 |
1109 | 53.6k | << Instruction::MOD; |
1110 | 53.6k | if (byteSize != 1) |
1111 | 20.7k | m_context << u256(byteSize) << Instruction::MUL; |
1112 | 53.6k | } |
1113 | 1.03M | else |
1114 | 1.03M | { |
1115 | 1.03M | if (_arrayType.baseType()->storageSize() != 1) |
1116 | 579k | m_context << _arrayType.baseType()->storageSize() << Instruction::MUL; |
1117 | 1.03M | m_context << Instruction::ADD << u256(0); |
1118 | 1.03M | } |
1119 | 1.09M | m_context << endTag; |
1120 | 1.09M | break; |
1121 | 1.09M | } |
1122 | 5.38M | } |
1123 | 5.38M | } |
1124 | | |
1125 | | void ArrayUtils::accessCallDataArrayElement(ArrayType const& _arrayType, bool _doBoundsCheck) const |
1126 | 599k | { |
1127 | 599k | solAssert(_arrayType.location() == DataLocation::CallData, ""); |
1128 | 599k | if (_arrayType.baseType()->isDynamicallyEncoded()) |
1129 | 444k | { |
1130 | | // stack layout: <base_ref> <length> <index> |
1131 | 444k | ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck, true); |
1132 | | // stack layout: <base_ref> <ptr_to_tail> |
1133 | | |
1134 | 444k | CompilerUtils(m_context).accessCalldataTail(*_arrayType.baseType()); |
1135 | | // stack layout: <tail_ref> [length] |
1136 | 444k | } |
1137 | 155k | else |
1138 | 155k | { |
1139 | 155k | ArrayUtils(m_context).accessIndex(_arrayType, _doBoundsCheck); |
1140 | 155k | if (_arrayType.baseType()->isValueType()) |
1141 | 48.2k | { |
1142 | 48.2k | solAssert(_arrayType.baseType()->storageBytes() <= 32, ""); |
1143 | 48.2k | if ( |
1144 | 48.2k | !_arrayType.isByteArrayOrString() && |
1145 | 48.2k | _arrayType.baseType()->storageBytes() < 32 && |
1146 | 48.2k | m_context.useABICoderV2() |
1147 | 48.2k | ) |
1148 | 45.9k | { |
1149 | 45.9k | m_context << u256(32); |
1150 | 45.9k | CompilerUtils(m_context).abiDecodeV2({_arrayType.baseType()}, false); |
1151 | 45.9k | } |
1152 | 2.26k | else |
1153 | 2.26k | CompilerUtils(m_context).loadFromMemoryDynamic( |
1154 | 2.26k | *_arrayType.baseType(), |
1155 | 2.26k | true, |
1156 | 2.26k | !_arrayType.isByteArrayOrString(), |
1157 | 2.26k | false |
1158 | 2.26k | ); |
1159 | 48.2k | } |
1160 | 106k | else |
1161 | 155k | solAssert( |
1162 | 155k | _arrayType.baseType()->category() == Type::Category::Struct || |
1163 | 155k | _arrayType.baseType()->category() == Type::Category::Array, |
1164 | 155k | "Invalid statically sized non-value base type on array access." |
1165 | 155k | ); |
1166 | 155k | } |
1167 | 599k | } |
1168 | | |
1169 | | void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const |
1170 | 551 | { |
1171 | 551 | solAssert(_byteSize < 32, ""); |
1172 | 551 | solAssert(_byteSize != 0, ""); |
1173 | | // We do the following, but avoiding jumps: |
1174 | | // byteOffset += byteSize |
1175 | | // if (byteOffset + byteSize > 32) |
1176 | | // { |
1177 | | // storageOffset++; |
1178 | | // byteOffset = 0; |
1179 | | // } |
1180 | 551 | if (_byteOffsetPosition > 1) |
1181 | 441 | m_context << swapInstruction(_byteOffsetPosition - 1); |
1182 | 551 | m_context << u256(_byteSize) << Instruction::ADD; |
1183 | 551 | if (_byteOffsetPosition > 1) |
1184 | 441 | m_context << swapInstruction(_byteOffsetPosition - 1); |
1185 | | // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 |
1186 | 551 | m_context |
1187 | 551 | << u256(32) << dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) |
1188 | 551 | << Instruction::ADD << Instruction::DIV; |
1189 | | // increment storage offset if X == 1 (just add X to it) |
1190 | | // stack: X |
1191 | 551 | m_context |
1192 | 551 | << swapInstruction(_storageOffsetPosition) << dupInstruction(_storageOffsetPosition + 1) |
1193 | 551 | << Instruction::ADD << swapInstruction(_storageOffsetPosition); |
1194 | | // stack: X |
1195 | | // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) |
1196 | 551 | m_context << u256(1) << Instruction::SUB; |
1197 | | // stack: 1 - X |
1198 | 551 | if (_byteOffsetPosition == 1) |
1199 | 110 | m_context << Instruction::MUL; |
1200 | 441 | else |
1201 | 441 | m_context |
1202 | 441 | << dupInstruction(_byteOffsetPosition + 1) << Instruction::MUL |
1203 | 441 | << swapInstruction(_byteOffsetPosition) << Instruction::POP; |
1204 | 551 | } |