Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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
}