Coverage Report

Created: 2026-10-26 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsolidity/codegen/LValue.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
 * LValues for use in the expression compiler.
22
 */
23
24
#include <libsolidity/codegen/LValue.h>
25
26
#include <libsolidity/ast/AST.h>
27
#include <libsolidity/ast/Types.h>
28
#include <libsolidity/codegen/CompilerUtils.h>
29
#include <libevmasm/Instruction.h>
30
31
#include <libsolutil/StackTooDeepString.h>
32
33
using namespace solidity;
34
using namespace solidity::evmasm;
35
using namespace solidity::frontend;
36
using namespace solidity::langutil;
37
using namespace solidity::util;
38
39
StackVariable::StackVariable(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
40
138k
  LValue(_compilerContext, _declaration.annotation().type),
41
138k
  m_baseStackOffset(m_context.baseStackOffsetOfVariable(_declaration)),
42
138k
  m_size(m_dataType->sizeOnStack())
43
138k
{
44
138k
}
45
46
void StackVariable::retrieveValue(SourceLocation const& _location, bool) const
47
138k
{
48
138k
  unsigned stackPos = m_context.baseToCurrentStackOffset(m_baseStackOffset);
49
138k
  if (stackPos + 1 > m_context.reachableStackDepth()) //@todo correct this by fetching earlier or moving to memory
50
138k
    BOOST_THROW_EXCEPTION(
51
138k
      StackTooDeepError() <<
52
138k
      errinfo_sourceLocation(_location) <<
53
138k
      util::errinfo_comment(util::stackTooDeepString)
54
138k
    );
55
138k
  solAssert(stackPos + 1 >= m_size, "Size and stack pos mismatch.");
56
282k
  for (unsigned i = 0; i < m_size; ++i)
57
144k
    m_context << dupInstruction(stackPos + 1);
58
138k
}
59
60
void StackVariable::storeValue(Type const&, SourceLocation const& _location, bool _move) const
61
8.37k
{
62
8.37k
  unsigned stackDiff = m_context.baseToCurrentStackOffset(m_baseStackOffset) - m_size + 1;
63
8.37k
  if (stackDiff > m_context.reachableStackDepth())
64
8.37k
    BOOST_THROW_EXCEPTION(
65
8.37k
      StackTooDeepError() <<
66
8.37k
      errinfo_sourceLocation(_location) <<
67
8.37k
      util::errinfo_comment(util::stackTooDeepString)
68
8.37k
    );
69
8.37k
  else if (stackDiff > 0)
70
16.8k
    for (unsigned i = 0; i < m_size; ++i)
71
8.43k
      m_context << swapInstruction(stackDiff) << Instruction::POP;
72
8.37k
  if (!_move)
73
3.58k
    retrieveValue(_location);
74
8.37k
}
75
76
void StackVariable::setToZero(SourceLocation const& _location, bool) const
77
35
{
78
35
  CompilerUtils(m_context).pushZeroValue(*m_dataType);
79
35
  storeValue(*m_dataType, _location, true);
80
35
}
81
82
MemoryItem::MemoryItem(CompilerContext& _compilerContext, Type const& _type, bool _padded):
83
357k
  LValue(_compilerContext, &_type),
84
357k
  m_padded(_padded)
85
357k
{
86
357k
}
87
88
void MemoryItem::retrieveValue(SourceLocation const&, bool _remove) const
89
338k
{
90
338k
  if (m_dataType->isValueType())
91
45.8k
  {
92
45.8k
    if (!_remove)
93
13
      m_context << Instruction::DUP1;
94
45.8k
    CompilerUtils(m_context).loadFromMemoryDynamic(*m_dataType, false, m_padded, false);
95
45.8k
  }
96
292k
  else
97
292k
    m_context << Instruction::MLOAD;
98
338k
}
99
100
void MemoryItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
101
19.5k
{
102
19.5k
  CompilerUtils utils(m_context);
103
19.5k
  if (m_dataType->isValueType())
104
15.1k
  {
105
15.1k
    solAssert(_sourceType.isValueType(), "");
106
15.1k
    utils.moveIntoStack(_sourceType.sizeOnStack());
107
15.1k
    utils.convertType(_sourceType, *m_dataType, true);
108
15.1k
    if (!_move)
109
15.1k
    {
110
15.1k
      utils.moveToStackTop(m_dataType->sizeOnStack());
111
15.1k
      utils.copyToStackTop(1 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack());
112
15.1k
    }
113
15.1k
    if (!m_padded)
114
1.54k
    {
115
1.54k
      solAssert(m_dataType->calldataEncodedSize(false) == 1, "Invalid non-padded type.");
116
1.54k
      solAssert(m_dataType->category() != Type::Category::UserDefinedValueType, "");
117
1.54k
      if (m_dataType->category() == Type::Category::FixedBytes)
118
1.54k
        m_context << u256(0) << Instruction::BYTE;
119
1.54k
      m_context << Instruction::SWAP1 << Instruction::MSTORE8;
120
1.54k
    }
121
13.5k
    else
122
13.5k
    {
123
13.5k
      utils.storeInMemoryDynamic(*m_dataType, m_padded);
124
13.5k
      m_context << Instruction::POP;
125
13.5k
    }
126
15.1k
  }
127
4.43k
  else
128
4.43k
  {
129
4.43k
    solUnimplementedAssert(_sourceType == *m_dataType, "Conversion not implemented for assignment to memory.");
130
131
4.43k
    solAssert(m_dataType->sizeOnStack() == 1, "");
132
4.43k
    if (!_move)
133
4.43k
      m_context << Instruction::DUP2 << Instruction::SWAP1;
134
    // stack: [value] value lvalue
135
    // only store the reference
136
4.43k
    m_context << Instruction::MSTORE;
137
4.43k
  }
138
19.5k
}
139
140
void MemoryItem::setToZero(SourceLocation const&, bool _removeReference) const
141
30
{
142
30
  CompilerUtils utils(m_context);
143
30
  solAssert(_removeReference, "");
144
30
  utils.pushZeroValue(*m_dataType);
145
  // stack pre: reference 0
146
30
  if (!m_padded)
147
0
  {
148
0
    solUnimplementedAssert(
149
0
      m_dataType->calldataEncodedSize(/* _padded */ false) == 1,
150
0
      "Zeroing of multi-byte non-padded types is not implemented."
151
0
    );
152
0
    solAssert(m_dataType->category() != Type::Category::UserDefinedValueType);
153
0
    m_context << Instruction::SWAP1 << Instruction::MSTORE8;
154
0
  }
155
30
  else
156
30
  {
157
30
    utils.storeInMemoryDynamic(*m_dataType, m_padded);
158
30
    m_context << Instruction::POP;
159
30
  }
160
30
}
161
162
163
ImmutableItem::ImmutableItem(CompilerContext& _compilerContext, VariableDeclaration const& _variable):
164
648
  LValue(_compilerContext, _variable.annotation().type), m_variable(_variable)
165
648
{
166
648
  solAssert(_variable.immutable(), "");
167
648
}
168
169
void ImmutableItem::retrieveValue(SourceLocation const&, bool) const
170
324
{
171
324
  solUnimplementedAssert(m_dataType->isValueType());
172
173
324
  if (m_context.runtimeContext())
174
176
    CompilerUtils(m_context).loadFromMemory(
175
176
      static_cast<unsigned>(m_context.immutableMemoryOffset(m_variable)),
176
176
      *m_dataType,
177
176
      false,
178
176
      true
179
176
    );
180
148
  else
181
148
    for (auto&& slotName: m_context.immutableVariableSlotNames(m_variable))
182
148
      m_context.appendImmutable(slotName);
183
324
}
184
185
void ImmutableItem::storeValue(Type const& _sourceType, SourceLocation const&, bool _move) const
186
419
{
187
419
  CompilerUtils utils(m_context);
188
419
  solUnimplementedAssert(m_dataType->isValueType());
189
419
  solAssert(_sourceType.isValueType(), "");
190
191
419
  utils.convertType(_sourceType, *m_dataType, true);
192
419
  m_context << m_context.immutableMemoryOffset(m_variable);
193
419
  if (_move)
194
140
    utils.moveIntoStack(m_dataType->sizeOnStack());
195
279
  else
196
279
    utils.copyToStackTop(m_dataType->sizeOnStack() + 1, m_dataType->sizeOnStack());
197
419
  utils.storeInMemoryDynamic(*m_dataType);
198
419
  m_context << Instruction::POP;
199
419
}
200
201
void ImmutableItem::setToZero(SourceLocation const&, bool _removeReference) const
202
5
{
203
5
  CompilerUtils utils(m_context);
204
5
  solUnimplementedAssert(m_dataType->isValueType());
205
5
  solAssert(_removeReference);
206
207
5
  m_context << m_context.immutableMemoryOffset(m_variable);
208
5
  utils.pushZeroValue(*m_dataType);
209
5
  utils.storeInMemoryDynamic(*m_dataType);
210
5
  m_context << Instruction::POP;
211
5
}
212
213
template<bool IsTransient>
214
GenericStorageItem<IsTransient>::GenericStorageItem(CompilerContext& _compilerContext, VariableDeclaration const& _declaration):
215
40.8k
  GenericStorageItem<IsTransient>(_compilerContext, *_declaration.annotation().type)
216
40.8k
{
217
40.8k
  solAssert(!_declaration.immutable(), "");
218
40.8k
  auto const& location = m_context.storageLocationOfVariable(_declaration);
219
40.8k
  m_context << location.first << u256(location.second);
220
40.8k
}
solidity::frontend::GenericStorageItem<false>::GenericStorageItem(solidity::frontend::CompilerContext&, solidity::frontend::VariableDeclaration const&)
Line
Count
Source
215
40.8k
  GenericStorageItem<IsTransient>(_compilerContext, *_declaration.annotation().type)
216
40.8k
{
217
  solAssert(!_declaration.immutable(), "");
218
40.8k
  auto const& location = m_context.storageLocationOfVariable(_declaration);
219
40.8k
  m_context << location.first << u256(location.second);
220
40.8k
}
Unexecuted instantiation: solidity::frontend::GenericStorageItem<true>::GenericStorageItem(solidity::frontend::CompilerContext&, solidity::frontend::VariableDeclaration const&)
221
222
template<bool IsTransient>
223
GenericStorageItem<IsTransient>::GenericStorageItem(CompilerContext& _compilerContext, Type const& _type):
224
151k
  LValue(_compilerContext, &_type)
225
151k
{
226
151k
  if (m_dataType->isValueType())
227
27.0k
  {
228
27.0k
    if (m_dataType->category() != Type::Category::Function)
229
27.0k
      solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), "");
230
27.0k
    solAssert(m_dataType->storageSize() == 1, "Invalid storage size.");
231
27.0k
  }
232
151k
}
solidity::frontend::GenericStorageItem<false>::GenericStorageItem(solidity::frontend::CompilerContext&, solidity::frontend::Type const&)
Line
Count
Source
224
151k
  LValue(_compilerContext, &_type)
225
151k
{
226
151k
  if (m_dataType->isValueType())
227
27.0k
  {
228
27.0k
    if (m_dataType->category() != Type::Category::Function)
229
27.0k
      solAssert(m_dataType->storageSize() == m_dataType->sizeOnStack(), "");
230
    solAssert(m_dataType->storageSize() == 1, "Invalid storage size.");
231
27.0k
  }
232
151k
}
Unexecuted instantiation: solidity::frontend::GenericStorageItem<true>::GenericStorageItem(solidity::frontend::CompilerContext&, solidity::frontend::Type const&)
233
234
template<bool IsTransient>
235
void GenericStorageItem<IsTransient>::retrieveValue(langutil::SourceLocation const&, bool _remove) const
236
120k
{
237
  // stack: storage_key storage_offset
238
120k
  if (!m_dataType->isValueType())
239
113k
  {
240
113k
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
241
113k
    solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size.");
242
113k
    if (_remove)
243
113k
      m_context << Instruction::POP; // remove byte offset
244
0
    else
245
0
      m_context << Instruction::DUP2;
246
113k
    return;
247
113k
  }
248
6.88k
  if (!_remove)
249
278
    CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
250
6.88k
  if (m_dataType->storageBytes() == 32)
251
4.44k
    m_context << Instruction::POP << s_loadInstruction;
252
2.44k
  else
253
2.44k
  {
254
2.44k
    Type const* type = m_dataType;
255
2.44k
    if (type->category() == Type::Category::UserDefinedValueType)
256
2
      type = type->encodingType();
257
2.44k
    bool cleaned = false;
258
    // Load the slot value at storage_key and shift it to the right using storage_offset.
259
    // Note that storage_offset is in bytes, so we need to shift by 8 * storage_offset bits.
260
2.44k
    m_context
261
2.44k
      << Instruction::SWAP1 << s_loadInstruction << Instruction::SWAP1
262
2.44k
      << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV;
263
2.44k
    if (type->category() == Type::Category::FixedPoint)
264
      // implementation should be very similar to the integer case.
265
2.44k
      solUnimplemented("Not yet implemented - FixedPointType.");
266
2.44k
    else if (auto const* fun = dynamic_cast<FunctionType const*>(type))
267
129
    {
268
129
      if (fun->kind() == FunctionType::Kind::External)
269
40
      {
270
40
        CompilerUtils(m_context).splitExternalFunctionType(false);
271
40
        cleaned = true;
272
40
      }
273
89
      else if (fun->kind() == FunctionType::Kind::Internal)
274
89
      {
275
        // Mask out the upper bits not belonging to the value of the function pointer.
276
89
        m_context << ((u256(0x1) << (8 * type->storageBytes())) - 1) << Instruction::AND;
277
89
        cleaned = true;
278
89
        m_context << Instruction::DUP1 << Instruction::ISZERO;
279
89
        CompilerUtils(m_context).pushZeroValue(*fun);
280
89
        m_context << Instruction::MUL << Instruction::OR;
281
89
      }
282
129
    }
283
2.31k
    else if (type->leftAligned())
284
262
    {
285
262
      CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * type->storageBytes());
286
262
      cleaned = true;
287
262
    }
288
2.05k
    else if (
289
2.05k
      type->category() == Type::Category::Integer &&
290
933
      dynamic_cast<IntegerType const&>(*type).isSigned()
291
2.05k
    )
292
251
    {
293
251
      m_context << u256(type->storageBytes() - 1) << Instruction::SIGNEXTEND;
294
251
      cleaned = true;
295
251
    }
296
297
2.44k
    if (!cleaned)
298
1.80k
    {
299
1.80k
      solAssert(type->sizeOnStack() == 1, "");
300
1.80k
      m_context << ((u256(0x1) << (8 * type->storageBytes())) - 1) << Instruction::AND;
301
1.80k
    }
302
2.44k
  }
303
6.88k
}
solidity::frontend::GenericStorageItem<false>::retrieveValue(solidity::langutil::SourceLocation const&, bool) const
Line
Count
Source
236
120k
{
237
  // stack: storage_key storage_offset
238
120k
  if (!m_dataType->isValueType())
239
113k
  {
240
113k
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
241
113k
    solAssert(m_dataType->sizeOnStack() == 1, "Invalid storage ref size.");
242
113k
    if (_remove)
243
113k
      m_context << Instruction::POP; // remove byte offset
244
0
    else
245
0
      m_context << Instruction::DUP2;
246
113k
    return;
247
113k
  }
248
6.88k
  if (!_remove)
249
278
    CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
250
6.88k
  if (m_dataType->storageBytes() == 32)
251
4.44k
    m_context << Instruction::POP << s_loadInstruction;
252
2.44k
  else
253
2.44k
  {
254
2.44k
    Type const* type = m_dataType;
255
2.44k
    if (type->category() == Type::Category::UserDefinedValueType)
256
2
      type = type->encodingType();
257
2.44k
    bool cleaned = false;
258
    // Load the slot value at storage_key and shift it to the right using storage_offset.
259
    // Note that storage_offset is in bytes, so we need to shift by 8 * storage_offset bits.
260
2.44k
    m_context
261
2.44k
      << Instruction::SWAP1 << s_loadInstruction << Instruction::SWAP1
262
2.44k
      << u256(0x100) << Instruction::EXP << Instruction::SWAP1 << Instruction::DIV;
263
2.44k
    if (type->category() == Type::Category::FixedPoint)
264
      // implementation should be very similar to the integer case.
265
2.44k
      solUnimplemented("Not yet implemented - FixedPointType.");
266
2.44k
    else if (auto const* fun = dynamic_cast<FunctionType const*>(type))
267
129
    {
268
129
      if (fun->kind() == FunctionType::Kind::External)
269
40
      {
270
40
        CompilerUtils(m_context).splitExternalFunctionType(false);
271
40
        cleaned = true;
272
40
      }
273
89
      else if (fun->kind() == FunctionType::Kind::Internal)
274
89
      {
275
        // Mask out the upper bits not belonging to the value of the function pointer.
276
89
        m_context << ((u256(0x1) << (8 * type->storageBytes())) - 1) << Instruction::AND;
277
89
        cleaned = true;
278
89
        m_context << Instruction::DUP1 << Instruction::ISZERO;
279
89
        CompilerUtils(m_context).pushZeroValue(*fun);
280
89
        m_context << Instruction::MUL << Instruction::OR;
281
89
      }
282
129
    }
283
2.31k
    else if (type->leftAligned())
284
262
    {
285
262
      CompilerUtils(m_context).leftShiftNumberOnStack(256 - 8 * type->storageBytes());
286
262
      cleaned = true;
287
262
    }
288
2.05k
    else if (
289
2.05k
      type->category() == Type::Category::Integer &&
290
933
      dynamic_cast<IntegerType const&>(*type).isSigned()
291
2.05k
    )
292
251
    {
293
251
      m_context << u256(type->storageBytes() - 1) << Instruction::SIGNEXTEND;
294
251
      cleaned = true;
295
251
    }
296
297
2.44k
    if (!cleaned)
298
1.80k
    {
299
      solAssert(type->sizeOnStack() == 1, "");
300
1.80k
      m_context << ((u256(0x1) << (8 * type->storageBytes())) - 1) << Instruction::AND;
301
1.80k
    }
302
2.44k
  }
303
6.88k
}
Unexecuted instantiation: solidity::frontend::GenericStorageItem<true>::retrieveValue(solidity::langutil::SourceLocation const&, bool) const
304
305
template<bool IsTransient>
306
void GenericStorageItem<IsTransient>::storeValue(Type const& _sourceType, langutil::SourceLocation const& _location, bool _move) const
307
31.8k
{
308
31.8k
  CompilerUtils utils(m_context);
309
31.8k
  solAssert(m_dataType, "");
310
311
  // stack: value storage_key storage_offset
312
31.8k
  if (m_dataType->isValueType())
313
20.5k
  {
314
20.5k
    solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size.");
315
20.5k
    solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size.");
316
20.5k
    if (m_dataType->storageBytes() == 32)
317
3.33k
    {
318
3.33k
      solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size.");
319
      // offset should be zero
320
3.33k
      m_context << Instruction::POP;
321
3.33k
      if (!_move)
322
2.82k
        m_context << Instruction::DUP2 << Instruction::SWAP1;
323
324
3.33k
      m_context << Instruction::SWAP1;
325
3.33k
      utils.convertType(_sourceType, *m_dataType, true);
326
3.33k
      m_context << Instruction::SWAP1;
327
328
3.33k
      m_context << s_storeInstruction;
329
3.33k
    }
330
17.2k
    else
331
17.2k
    {
332
      // OR the value into the other values in the storage slot
333
17.2k
      m_context << u256(0x100) << Instruction::EXP;
334
      // stack: value storage_ref multiplier
335
      // fetch old value
336
17.2k
      m_context << Instruction::DUP2 << s_loadInstruction;
337
      // stack: value storage_ref multiplier old_full_value
338
      // clear bytes in old value
339
17.2k
      m_context
340
17.2k
        << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1)
341
17.2k
        << Instruction::MUL;
342
17.2k
      m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1;
343
      // stack: value storage_ref cleared_value multiplier
344
17.2k
      utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack());
345
      // stack: value storage_ref cleared_value multiplier value
346
17.2k
      if (auto const* fun = dynamic_cast<FunctionType const*>(m_dataType))
347
185
      {
348
185
        solAssert(
349
185
          _sourceType.isImplicitlyConvertibleTo(*m_dataType),
350
185
          "function item stored but target is not implicitly convertible to source"
351
185
        );
352
185
        solAssert(!fun->hasBoundFirstArgument(), "");
353
185
        if (fun->kind() == FunctionType::Kind::External)
354
20
        {
355
20
          solAssert(fun->sizeOnStack() == 2, "");
356
          // Combine the two-item function type into a single stack slot.
357
20
          utils.combineExternalFunctionType(false);
358
20
        }
359
165
        else
360
165
        {
361
165
          solAssert(fun->sizeOnStack() == 1, "");
362
165
          m_context <<
363
165
            ((u256(1) << (8 * m_dataType->storageBytes())) - 1) <<
364
165
            Instruction::AND;
365
165
        }
366
185
      }
367
17.0k
      else if (m_dataType->leftAligned())
368
2.58k
      {
369
2.58k
        solAssert(_sourceType.category() == Type::Category::FixedBytes || (
370
2.58k
          _sourceType.encodingType() &&
371
2.58k
          _sourceType.encodingType()->category() == Type::Category::FixedBytes
372
2.58k
        ), "source not fixed bytes");
373
2.58k
        CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * m_dataType->storageBytes());
374
2.58k
      }
375
14.4k
      else
376
14.4k
      {
377
14.4k
        solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type.");
378
        // remove the higher order bits
379
14.4k
        utils.convertType(_sourceType, *m_dataType, true, true);
380
14.4k
      }
381
17.2k
      m_context  << Instruction::MUL << Instruction::OR;
382
      // stack: value storage_ref updated_value
383
17.2k
      m_context << Instruction::SWAP1 << s_storeInstruction;
384
17.2k
      if (_move)
385
515
        utils.popStackElement(*m_dataType);
386
17.2k
    }
387
20.5k
  }
388
11.2k
  else
389
11.2k
  {
390
11.2k
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
391
11.2k
    solAssert(
392
11.2k
      _sourceType.category() == m_dataType->category(),
393
11.2k
      "Wrong type conversation for assignment."
394
11.2k
    );
395
11.2k
    if (m_dataType->category() == Type::Category::Array)
396
11.1k
    {
397
11.1k
      m_context << Instruction::POP; // remove byte offset
398
11.1k
      ArrayUtils(m_context).copyArrayToStorage(
399
11.1k
        dynamic_cast<ArrayType const&>(*m_dataType),
400
11.1k
        dynamic_cast<ArrayType const&>(_sourceType)
401
11.1k
      );
402
11.1k
      if (_move)
403
1.29k
        m_context << Instruction::POP;
404
11.1k
    }
405
67
    else if (m_dataType->category() == Type::Category::Struct)
406
67
    {
407
      // stack layout: source_ref target_ref target_offset
408
      // note that we have structs, so offset should be zero and are ignored
409
67
      m_context << Instruction::POP;
410
67
      auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
411
67
      auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
412
67
      solAssert(
413
67
        structType.structDefinition() == sourceType.structDefinition(),
414
67
        "Struct assignment with conversion."
415
67
      );
416
67
      solAssert(!structType.containsNestedMapping(), "");
417
67
      if (sourceType.location() == DataLocation::CallData)
418
11
      {
419
11
        solAssert(sourceType.sizeOnStack() == 1, "");
420
11
        solAssert(structType.sizeOnStack() == 1, "");
421
11
        m_context << Instruction::DUP2 << Instruction::DUP2;
422
11
        m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, VariableDeclaration::Location::Unspecified, 0), 2, 0);
423
11
      }
424
56
      else
425
56
      {
426
56
        for (auto const& member: structType.members(nullptr))
427
119
        {
428
          // assign each member that can live outside of storage
429
119
          Type const* memberType = member.type;
430
119
          solAssert(memberType->nameable(), "");
431
119
          Type const* sourceMemberType = sourceType.memberType(member.name);
432
119
          if (sourceType.location() == DataLocation::Storage)
433
14
          {
434
            // stack layout: source_ref target_ref
435
14
            std::pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
436
14
            m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
437
14
            m_context << u256(offsets.second);
438
            // stack: source_ref target_ref source_member_ref source_member_off
439
14
            StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
440
            // stack: source_ref target_ref source_value...
441
14
          }
442
105
          else
443
105
          {
444
105
            solAssert(sourceType.location() == DataLocation::Memory, "");
445
            // stack layout: source_ref target_ref
446
105
            m_context << sourceType.memoryOffsetOfMember(member.name);
447
105
            m_context << Instruction::DUP3 << Instruction::ADD;
448
105
            MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
449
            // stack layout: source_ref target_ref source_value...
450
105
          }
451
119
          unsigned stackSize = sourceMemberType->sizeOnStack();
452
119
          std::pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
453
119
          m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
454
119
          m_context << u256(offsets.second);
455
          // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
456
119
          StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
457
119
        }
458
56
      }
459
      // stack layout: source_ref target_ref
460
67
      solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
461
67
      if (_move)
462
30
        utils.popStackSlots(2);
463
37
      else
464
37
        m_context << Instruction::SWAP1 << Instruction::POP;
465
67
    }
466
0
    else
467
67
      solAssert(false, "Invalid non-value type for assignment.");
468
11.2k
  }
469
31.8k
}
solidity::frontend::GenericStorageItem<false>::storeValue(solidity::frontend::Type const&, solidity::langutil::SourceLocation const&, bool) const
Line
Count
Source
307
31.8k
{
308
31.8k
  CompilerUtils utils(m_context);
309
31.8k
  solAssert(m_dataType, "");
310
311
  // stack: value storage_key storage_offset
312
31.8k
  if (m_dataType->isValueType())
313
20.5k
  {
314
20.5k
    solAssert(m_dataType->storageBytes() <= 32, "Invalid storage bytes size.");
315
20.5k
    solAssert(m_dataType->storageBytes() > 0, "Invalid storage bytes size.");
316
20.5k
    if (m_dataType->storageBytes() == 32)
317
3.33k
    {
318
3.33k
      solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size.");
319
      // offset should be zero
320
3.33k
      m_context << Instruction::POP;
321
3.33k
      if (!_move)
322
2.82k
        m_context << Instruction::DUP2 << Instruction::SWAP1;
323
324
3.33k
      m_context << Instruction::SWAP1;
325
3.33k
      utils.convertType(_sourceType, *m_dataType, true);
326
3.33k
      m_context << Instruction::SWAP1;
327
328
3.33k
      m_context << s_storeInstruction;
329
3.33k
    }
330
17.2k
    else
331
17.2k
    {
332
      // OR the value into the other values in the storage slot
333
17.2k
      m_context << u256(0x100) << Instruction::EXP;
334
      // stack: value storage_ref multiplier
335
      // fetch old value
336
17.2k
      m_context << Instruction::DUP2 << s_loadInstruction;
337
      // stack: value storage_ref multiplier old_full_value
338
      // clear bytes in old value
339
17.2k
      m_context
340
17.2k
        << Instruction::DUP2 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1)
341
17.2k
        << Instruction::MUL;
342
17.2k
      m_context << Instruction::NOT << Instruction::AND << Instruction::SWAP1;
343
      // stack: value storage_ref cleared_value multiplier
344
17.2k
      utils.copyToStackTop(3 + m_dataType->sizeOnStack(), m_dataType->sizeOnStack());
345
      // stack: value storage_ref cleared_value multiplier value
346
17.2k
      if (auto const* fun = dynamic_cast<FunctionType const*>(m_dataType))
347
185
      {
348
185
        solAssert(
349
185
          _sourceType.isImplicitlyConvertibleTo(*m_dataType),
350
185
          "function item stored but target is not implicitly convertible to source"
351
185
        );
352
185
        solAssert(!fun->hasBoundFirstArgument(), "");
353
185
        if (fun->kind() == FunctionType::Kind::External)
354
20
        {
355
20
          solAssert(fun->sizeOnStack() == 2, "");
356
          // Combine the two-item function type into a single stack slot.
357
20
          utils.combineExternalFunctionType(false);
358
20
        }
359
165
        else
360
165
        {
361
165
          solAssert(fun->sizeOnStack() == 1, "");
362
165
          m_context <<
363
165
            ((u256(1) << (8 * m_dataType->storageBytes())) - 1) <<
364
165
            Instruction::AND;
365
165
        }
366
185
      }
367
17.0k
      else if (m_dataType->leftAligned())
368
2.58k
      {
369
2.58k
        solAssert(_sourceType.category() == Type::Category::FixedBytes || (
370
2.58k
          _sourceType.encodingType() &&
371
2.58k
          _sourceType.encodingType()->category() == Type::Category::FixedBytes
372
2.58k
        ), "source not fixed bytes");
373
2.58k
        CompilerUtils(m_context).rightShiftNumberOnStack(256 - 8 * m_dataType->storageBytes());
374
2.58k
      }
375
14.4k
      else
376
14.4k
      {
377
14.4k
        solAssert(m_dataType->sizeOnStack() == 1, "Invalid stack size for opaque type.");
378
        // remove the higher order bits
379
14.4k
        utils.convertType(_sourceType, *m_dataType, true, true);
380
14.4k
      }
381
17.2k
      m_context  << Instruction::MUL << Instruction::OR;
382
      // stack: value storage_ref updated_value
383
17.2k
      m_context << Instruction::SWAP1 << s_storeInstruction;
384
17.2k
      if (_move)
385
515
        utils.popStackElement(*m_dataType);
386
17.2k
    }
387
20.5k
  }
388
11.2k
  else
389
11.2k
  {
390
11.2k
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
391
11.2k
    solAssert(
392
11.2k
      _sourceType.category() == m_dataType->category(),
393
11.2k
      "Wrong type conversation for assignment."
394
11.2k
    );
395
11.2k
    if (m_dataType->category() == Type::Category::Array)
396
11.1k
    {
397
11.1k
      m_context << Instruction::POP; // remove byte offset
398
11.1k
      ArrayUtils(m_context).copyArrayToStorage(
399
11.1k
        dynamic_cast<ArrayType const&>(*m_dataType),
400
11.1k
        dynamic_cast<ArrayType const&>(_sourceType)
401
11.1k
      );
402
11.1k
      if (_move)
403
1.29k
        m_context << Instruction::POP;
404
11.1k
    }
405
67
    else if (m_dataType->category() == Type::Category::Struct)
406
67
    {
407
      // stack layout: source_ref target_ref target_offset
408
      // note that we have structs, so offset should be zero and are ignored
409
67
      m_context << Instruction::POP;
410
67
      auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
411
67
      auto const& sourceType = dynamic_cast<StructType const&>(_sourceType);
412
67
      solAssert(
413
67
        structType.structDefinition() == sourceType.structDefinition(),
414
67
        "Struct assignment with conversion."
415
67
      );
416
67
      solAssert(!structType.containsNestedMapping(), "");
417
67
      if (sourceType.location() == DataLocation::CallData)
418
11
      {
419
11
        solAssert(sourceType.sizeOnStack() == 1, "");
420
11
        solAssert(structType.sizeOnStack() == 1, "");
421
11
        m_context << Instruction::DUP2 << Instruction::DUP2;
422
11
        m_context.callYulFunction(m_context.utilFunctions().updateStorageValueFunction(sourceType, structType, VariableDeclaration::Location::Unspecified, 0), 2, 0);
423
11
      }
424
56
      else
425
56
      {
426
56
        for (auto const& member: structType.members(nullptr))
427
119
        {
428
          // assign each member that can live outside of storage
429
119
          Type const* memberType = member.type;
430
119
          solAssert(memberType->nameable(), "");
431
119
          Type const* sourceMemberType = sourceType.memberType(member.name);
432
119
          if (sourceType.location() == DataLocation::Storage)
433
14
          {
434
            // stack layout: source_ref target_ref
435
14
            std::pair<u256, unsigned> const& offsets = sourceType.storageOffsetsOfMember(member.name);
436
14
            m_context << offsets.first << Instruction::DUP3 << Instruction::ADD;
437
14
            m_context << u256(offsets.second);
438
            // stack: source_ref target_ref source_member_ref source_member_off
439
14
            StorageItem(m_context, *sourceMemberType).retrieveValue(_location, true);
440
            // stack: source_ref target_ref source_value...
441
14
          }
442
105
          else
443
105
          {
444
105
            solAssert(sourceType.location() == DataLocation::Memory, "");
445
            // stack layout: source_ref target_ref
446
105
            m_context << sourceType.memoryOffsetOfMember(member.name);
447
105
            m_context << Instruction::DUP3 << Instruction::ADD;
448
105
            MemoryItem(m_context, *sourceMemberType).retrieveValue(_location, true);
449
            // stack layout: source_ref target_ref source_value...
450
105
          }
451
119
          unsigned stackSize = sourceMemberType->sizeOnStack();
452
119
          std::pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
453
119
          m_context << dupInstruction(1 + stackSize) << offsets.first << Instruction::ADD;
454
119
          m_context << u256(offsets.second);
455
          // stack: source_ref target_ref target_off source_value... target_member_ref target_member_byte_off
456
119
          StorageItem(m_context, *memberType).storeValue(*sourceMemberType, _location, true);
457
119
        }
458
56
      }
459
      // stack layout: source_ref target_ref
460
67
      solAssert(sourceType.sizeOnStack() == 1, "Unexpected source size.");
461
67
      if (_move)
462
30
        utils.popStackSlots(2);
463
37
      else
464
37
        m_context << Instruction::SWAP1 << Instruction::POP;
465
67
    }
466
0
    else
467
      solAssert(false, "Invalid non-value type for assignment.");
468
11.2k
  }
469
31.8k
}
Unexecuted instantiation: solidity::frontend::GenericStorageItem<true>::storeValue(solidity::frontend::Type const&, solidity::langutil::SourceLocation const&, bool) const
470
471
template<bool IsTransient>
472
void GenericStorageItem<IsTransient>::setToZero(langutil::SourceLocation const&, bool _removeReference) const
473
331
{
474
331
  if (m_dataType->category() == Type::Category::Array)
475
54
  {
476
54
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
477
54
    if (!_removeReference)
478
0
      CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
479
54
    ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType));
480
54
  }
481
277
  else if (m_dataType->category() == Type::Category::Struct)
482
22
  {
483
22
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
484
    // stack layout: storage_key storage_offset
485
    // @todo this can be improved: use StorageItem for non-value types, and just store 0 in
486
    // all slots that contain value types later.
487
22
    auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
488
22
    for (auto const& member: structType.members(nullptr))
489
40
    {
490
      // zero each member that is not a mapping
491
40
      Type const* memberType = member.type;
492
40
      if (memberType->category() == Type::Category::Mapping)
493
0
        continue;
494
40
      std::pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
495
40
      m_context
496
40
        << offsets.first << Instruction::DUP3 << Instruction::ADD
497
40
        << u256(offsets.second);
498
40
      StorageItem(m_context, *memberType).setToZero();
499
40
    }
500
22
    if (_removeReference)
501
22
      m_context << Instruction::POP << Instruction::POP;
502
22
  }
503
255
  else
504
255
  {
505
255
    solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString());
506
255
    if (!_removeReference)
507
0
      CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
508
255
    if (m_dataType->storageBytes() == 32)
509
204
    {
510
      // offset should be zero
511
204
      m_context
512
204
        << Instruction::POP << u256(0)
513
204
        << Instruction::SWAP1 << s_storeInstruction;
514
204
    }
515
51
    else
516
51
    {
517
51
      m_context << u256(0x100) << Instruction::EXP;
518
      // stack: storage_ref multiplier
519
      // fetch old value
520
51
      m_context << Instruction::DUP2 << s_loadInstruction;
521
      // stack: storage_ref multiplier old_full_value
522
      // clear bytes in old value
523
51
      m_context
524
51
        << Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1)
525
51
        << Instruction::MUL;
526
51
      m_context << Instruction::NOT << Instruction::AND;
527
      // stack: storage_ref cleared_value
528
51
      m_context << Instruction::SWAP1 << s_storeInstruction;
529
51
    }
530
255
  }
531
331
}
solidity::frontend::GenericStorageItem<false>::setToZero(solidity::langutil::SourceLocation const&, bool) const
Line
Count
Source
473
331
{
474
331
  if (m_dataType->category() == Type::Category::Array)
475
54
  {
476
54
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
477
54
    if (!_removeReference)
478
0
      CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
479
54
    ArrayUtils(m_context).clearArray(dynamic_cast<ArrayType const&>(*m_dataType));
480
54
  }
481
277
  else if (m_dataType->category() == Type::Category::Struct)
482
22
  {
483
22
    solUnimplementedAssert(!IsTransient, "Transient storage reference types are not supported yet.");
484
    // stack layout: storage_key storage_offset
485
    // @todo this can be improved: use StorageItem for non-value types, and just store 0 in
486
    // all slots that contain value types later.
487
22
    auto const& structType = dynamic_cast<StructType const&>(*m_dataType);
488
22
    for (auto const& member: structType.members(nullptr))
489
40
    {
490
      // zero each member that is not a mapping
491
40
      Type const* memberType = member.type;
492
40
      if (memberType->category() == Type::Category::Mapping)
493
0
        continue;
494
40
      std::pair<u256, unsigned> const& offsets = structType.storageOffsetsOfMember(member.name);
495
40
      m_context
496
40
        << offsets.first << Instruction::DUP3 << Instruction::ADD
497
40
        << u256(offsets.second);
498
40
      StorageItem(m_context, *memberType).setToZero();
499
40
    }
500
22
    if (_removeReference)
501
22
      m_context << Instruction::POP << Instruction::POP;
502
22
  }
503
255
  else
504
255
  {
505
255
    solAssert(m_dataType->isValueType(), "Clearing of unsupported type requested: " + m_dataType->toString());
506
255
    if (!_removeReference)
507
0
      CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack());
508
255
    if (m_dataType->storageBytes() == 32)
509
204
    {
510
      // offset should be zero
511
204
      m_context
512
204
        << Instruction::POP << u256(0)
513
204
        << Instruction::SWAP1 << s_storeInstruction;
514
204
    }
515
51
    else
516
51
    {
517
51
      m_context << u256(0x100) << Instruction::EXP;
518
      // stack: storage_ref multiplier
519
      // fetch old value
520
51
      m_context << Instruction::DUP2 << s_loadInstruction;
521
      // stack: storage_ref multiplier old_full_value
522
      // clear bytes in old value
523
51
      m_context
524
51
        << Instruction::SWAP1 << ((u256(1) << (8 * m_dataType->storageBytes())) - 1)
525
51
        << Instruction::MUL;
526
51
      m_context << Instruction::NOT << Instruction::AND;
527
      // stack: storage_ref cleared_value
528
51
      m_context << Instruction::SWAP1 << s_storeInstruction;
529
51
    }
530
255
  }
531
331
}
Unexecuted instantiation: solidity::frontend::GenericStorageItem<true>::setToZero(solidity::langutil::SourceLocation const&, bool) const
532
533
StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext):
534
782
  LValue(_compilerContext, TypeProvider::byte())
535
782
{
536
782
}
537
538
void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove) const
539
445
{
540
  // stack: ref byte_number
541
445
  if (_remove)
542
445
    m_context << Instruction::SWAP1 << Instruction::SLOAD
543
445
      << Instruction::SWAP1 << Instruction::BYTE;
544
0
  else
545
0
    m_context << Instruction::DUP2 << Instruction::SLOAD
546
0
      << Instruction::DUP2 << Instruction::BYTE;
547
445
  m_context << (u256(1) << (256 - 8)) << Instruction::MUL;
548
445
}
549
550
void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const
551
328
{
552
  // stack: value ref byte_number
553
328
  m_context << u256(31) << Instruction::SUB << u256(0x100) << Instruction::EXP;
554
  // stack: value ref (1<<(8*(31-byte_number)))
555
328
  m_context << Instruction::DUP2 << Instruction::SLOAD;
556
  // stack: value ref (1<<(8*(31-byte_number))) old_full_value
557
  // clear byte in old value
558
328
  m_context << Instruction::DUP2 << u256(0xff) << Instruction::MUL
559
328
    << Instruction::NOT << Instruction::AND;
560
  // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte
561
328
  m_context << Instruction::SWAP1;
562
328
  m_context << (u256(1) << (256 - 8)) << Instruction::DUP5 << Instruction::DIV
563
328
    << Instruction::MUL << Instruction::OR;
564
  // stack: value ref new_full_value
565
328
  m_context << Instruction::SWAP1 << Instruction::SSTORE;
566
328
  if (_move)
567
296
    m_context << Instruction::POP;
568
328
}
569
570
void StorageByteArrayElement::setToZero(SourceLocation const&, bool _removeReference) const
571
9
{
572
  // stack: ref byte_number
573
9
  solAssert(_removeReference, "");
574
9
  m_context << u256(31) << Instruction::SUB << u256(0x100) << Instruction::EXP;
575
  // stack: ref (1<<(8*(31-byte_number)))
576
9
  m_context << Instruction::DUP2 << Instruction::SLOAD;
577
  // stack: ref (1<<(8*(31-byte_number))) old_full_value
578
  // clear byte in old value
579
9
  m_context << Instruction::SWAP1 << u256(0xff) << Instruction::MUL;
580
9
  m_context << Instruction::NOT << Instruction::AND;
581
  // stack: ref old_full_value_with_cleared_byte
582
9
  m_context << Instruction::SWAP1 << Instruction::SSTORE;
583
9
}
584
585
TupleObject::TupleObject(
586
  CompilerContext& _compilerContext,
587
  std::vector<std::unique_ptr<LValue>>&& _lvalues
588
):
589
637
  LValue(_compilerContext), m_lvalues(std::move(_lvalues))
590
637
{
591
637
}
592
593
unsigned TupleObject::sizeOnStack() const
594
691
{
595
691
  unsigned size = 0;
596
691
  for (auto const& lv: m_lvalues)
597
1.45k
    if (lv)
598
903
      size += lv->sizeOnStack();
599
691
  return size;
600
691
}
601
602
void TupleObject::retrieveValue(SourceLocation const&, bool) const
603
0
{
604
0
  solAssert(false, "Tried to retrieve value of tuple.");
605
0
}
606
607
void TupleObject::storeValue(Type const& _sourceType, SourceLocation const& _location, bool) const
608
637
{
609
  // values are below the lvalue references
610
637
  unsigned valuePos = sizeOnStack();
611
637
  TypePointers const& valueTypes = dynamic_cast<TupleType const&>(_sourceType).components();
612
637
  solAssert(valueTypes.size() == m_lvalues.size(), "");
613
  // valuePos .... refPos ...
614
  // We will assign from right to left to optimize stack layout.
615
1.98k
  for (size_t i = 0; i < m_lvalues.size(); ++i)
616
1.35k
  {
617
1.35k
    std::unique_ptr<LValue> const& lvalue = m_lvalues[m_lvalues.size() - i - 1];
618
1.35k
    Type const* valType = valueTypes[valueTypes.size() - i - 1];
619
1.35k
    unsigned stackHeight = m_context.stackHeight();
620
1.35k
    solAssert(!valType == !lvalue, "");
621
1.35k
    if (!lvalue)
622
510
      continue;
623
841
    valuePos += valType->sizeOnStack();
624
    // copy value to top
625
841
    CompilerUtils(m_context).copyToStackTop(valuePos, valType->sizeOnStack());
626
    // move lvalue ref above value
627
841
    CompilerUtils(m_context).moveToStackTop(valType->sizeOnStack(), lvalue->sizeOnStack());
628
841
    lvalue->storeValue(*valType, _location, true);
629
841
    valuePos += m_context.stackHeight() - stackHeight;
630
841
  }
631
  // As the type of an assignment to a tuple type is the empty tuple, we always move.
632
637
  CompilerUtils(m_context).popStackElement(_sourceType);
633
637
}
634
635
void TupleObject::setToZero(SourceLocation const&, bool) const
636
0
{
637
  solAssert(false, "Tried to delete tuple.");
638
0
}
639
640
template class solidity::frontend::GenericStorageItem<false>;
641
template class solidity::frontend::GenericStorageItem<true>;