Coverage Report

Created: 2026-06-30 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsolidity/analysis/ViewPureChecker.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
#include <libsolidity/analysis/ViewPureChecker.h>
20
#include <libsolidity/ast/ExperimentalFeatures.h>
21
#include <libyul/backends/evm/EVMDialect.h>
22
#include <libyul/AST.h>
23
#include <libyul/Utilities.h>
24
#include <liblangutil/ErrorReporter.h>
25
#include <libevmasm/SemanticInformation.h>
26
27
#include <functional>
28
#include <utility>
29
#include <variant>
30
31
using namespace solidity;
32
using namespace solidity::langutil;
33
using namespace solidity::frontend;
34
35
namespace
36
{
37
38
class AssemblyViewPureChecker
39
{
40
public:
41
  explicit AssemblyViewPureChecker(
42
    yul::Dialect const& _dialect,
43
    std::function<void(StateMutability, SourceLocation const&)> _reportMutability
44
  ):
45
1.97k
    m_dialect(_dialect),
46
1.97k
    m_reportMutability(std::move(_reportMutability)) {}
47
48
9.02k
  void operator()(yul::Literal const&) {}
49
596
  void operator()(yul::Identifier const&) {}
50
  void operator()(yul::ExpressionStatement const& _expr)
51
3.47k
  {
52
3.47k
    std::visit(*this, _expr.expression);
53
3.47k
  }
54
  void operator()(yul::Assignment const& _assignment)
55
792
  {
56
792
    std::visit(*this, *_assignment.value);
57
792
  }
58
  void operator()(yul::VariableDeclaration const& _varDecl)
59
123
  {
60
123
    if (_varDecl.value)
61
107
      std::visit(*this, *_varDecl.value);
62
123
  }
63
  void operator()(yul::FunctionDefinition const& _funDef)
64
147
  {
65
147
    (*this)(_funDef.body);
66
147
  }
67
  void operator()(yul::FunctionCall const& _funCall)
68
5.66k
  {
69
5.66k
    if (yul::EVMDialect const* dialect = dynamic_cast<decltype(dialect)>(&m_dialect))
70
5.66k
      if (yul::BuiltinFunctionForEVM const* builtin = resolveBuiltinFunctionForEVM(_funCall.functionName, *dialect))
71
5.55k
        if (builtin->instruction)
72
5.55k
          checkInstruction(nativeLocationOf(_funCall), *builtin->instruction);
73
74
5.66k
    for (auto const& arg: _funCall.arguments)
75
10.7k
      std::visit(*this, arg);
76
5.66k
  }
77
  void operator()(yul::If const& _if)
78
31
  {
79
31
    std::visit(*this, *_if.condition);
80
31
    (*this)(_if.body);
81
31
  }
82
  void operator()(yul::Switch const& _switch)
83
40
  {
84
40
    std::visit(*this, *_switch.expression);
85
40
    for (auto const& _case: _switch.cases)
86
69
    {
87
69
      if (_case.value)
88
55
        (*this)(*_case.value);
89
69
      (*this)(_case.body);
90
69
    }
91
40
  }
92
  void operator()(yul::ForLoop const& _for)
93
48
  {
94
48
    (*this)(_for.pre);
95
48
    std::visit(*this, *_for.condition);
96
48
    (*this)(_for.body);
97
48
    (*this)(_for.post);
98
48
  }
99
  void operator()(yul::Break const&)
100
13
  {
101
13
  }
102
  void operator()(yul::Continue const&)
103
10
  {
104
10
  }
105
  void operator()(yul::Leave const&)
106
5
  {
107
5
  }
108
  void operator()(yul::Block const& _block)
109
2.38k
  {
110
2.38k
    for (auto const& s: _block.statements)
111
4.70k
      std::visit(*this, s);
112
2.38k
  }
113
114
private:
115
  void checkInstruction(SourceLocation _location, evmasm::Instruction _instruction)
116
5.55k
  {
117
5.55k
    if (evmasm::SemanticInformation::invalidInViewFunctions(_instruction))
118
2.10k
      m_reportMutability(StateMutability::NonPayable, _location);
119
3.45k
    else if (evmasm::SemanticInformation::invalidInPureFunctions(_instruction))
120
190
      m_reportMutability(StateMutability::View, _location);
121
5.55k
  }
122
123
  yul::Dialect const& m_dialect;
124
  std::function<void(StateMutability, SourceLocation const&)> m_reportMutability;
125
};
126
127
}
128
129
bool ViewPureChecker::check()
130
12.6k
{
131
12.6k
  for (auto const& source: m_ast)
132
14.1k
    source->accept(*this);
133
134
12.6k
  return !m_errors;
135
12.6k
}
136
137
bool ViewPureChecker::visit(ImportDirective const&)
138
757
{
139
757
  return false;
140
757
}
141
142
bool ViewPureChecker::visit(FunctionDefinition const& _funDef)
143
17.5k
{
144
17.5k
  solAssert(!m_currentFunction, "");
145
17.5k
  m_currentFunction = &_funDef;
146
17.5k
  m_bestMutabilityAndLocation = {StateMutability::Pure, _funDef.location()};
147
17.5k
  return true;
148
17.5k
}
149
150
void ViewPureChecker::endVisit(FunctionDefinition const& _funDef)
151
17.5k
{
152
17.5k
  solAssert(m_currentFunction == &_funDef, "");
153
17.5k
  if (
154
17.5k
    m_bestMutabilityAndLocation.mutability < _funDef.stateMutability() &&
155
6.02k
    _funDef.stateMutability() != StateMutability::Payable &&
156
5.44k
    _funDef.isImplemented() &&
157
5.14k
    !_funDef.body().statements().empty() &&
158
3.33k
    !_funDef.isConstructor() &&
159
3.00k
    !_funDef.isFallback() &&
160
2.96k
    !_funDef.isReceive() &&
161
2.96k
    !_funDef.virtualSemantics()
162
17.5k
  )
163
2.90k
    m_errorReporter.warning(
164
2.90k
      2018_error,
165
2.90k
      _funDef.location(),
166
2.90k
      "Function state mutability can be restricted to " + stateMutabilityToString(m_bestMutabilityAndLocation.mutability)
167
2.90k
    );
168
17.5k
  m_currentFunction = nullptr;
169
17.5k
}
170
171
bool ViewPureChecker::visit(ModifierDefinition const& _modifier)
172
463
{
173
463
  solAssert(m_currentFunction == nullptr, "");
174
463
  m_bestMutabilityAndLocation = {StateMutability::Pure, _modifier.location()};
175
463
  return true;
176
463
}
177
178
void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef)
179
463
{
180
463
  solAssert(m_currentFunction == nullptr, "");
181
463
  m_inferredMutability[&_modifierDef] = std::move(m_bestMutabilityAndLocation);
182
463
}
183
184
void ViewPureChecker::endVisit(Identifier const& _identifier)
185
129k
{
186
129k
  Declaration const* declaration = _identifier.annotation().referencedDeclaration;
187
129k
  solAssert(declaration, "");
188
189
129k
  StateMutability mutability = StateMutability::Pure;
190
191
129k
  bool writes = _identifier.annotation().willBeWrittenTo;
192
129k
  if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration))
193
110k
  {
194
110k
    if (varDecl->immutable())
195
401
    {
196
      // Immutables that are assigned literals are pure.
197
401
      if (!(varDecl->value() && varDecl->value()->annotation().type->category() == Type::Category::RationalNumber))
198
342
        mutability = StateMutability::View;
199
401
    }
200
110k
    else if (varDecl->isStateVariable() && !varDecl->isConstant())
201
24.8k
      mutability = writes ? StateMutability::NonPayable : StateMutability::View;
202
110k
  }
203
18.8k
  else if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration))
204
7.36k
  {
205
7.36k
    switch (magicVar->type()->category())
206
7.36k
    {
207
2.99k
    case Type::Category::Contract:
208
2.99k
      solAssert(_identifier.name() == "this", "");
209
2.99k
      if (dynamic_cast<ContractType const*>(magicVar->type()))
210
        // reads the address
211
2.99k
        mutability = StateMutability::View;
212
2.99k
      break;
213
0
    case Type::Category::Integer:
214
0
      solAssert(_identifier.name() == "now", "");
215
0
      mutability = StateMutability::View;
216
0
      break;
217
4.37k
    default:
218
4.37k
      break;
219
7.36k
    }
220
7.36k
  }
221
222
129k
  reportMutability(mutability, _identifier.location());
223
129k
}
224
225
void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly)
226
1.97k
{
227
1.97k
  AssemblyViewPureChecker{
228
1.97k
    _inlineAssembly.dialect(),
229
2.29k
    [&](StateMutability _mutability, SourceLocation const& _location) { reportMutability(_mutability, _location); }
230
1.97k
  }(_inlineAssembly.operations().root());
231
1.97k
}
232
233
void ViewPureChecker::reportMutability(
234
  StateMutability _mutability,
235
  SourceLocation const& _location,
236
  std::optional<SourceLocation> const& _nestedLocation
237
)
238
347k
{
239
347k
  if (_mutability > m_bestMutabilityAndLocation.mutability)
240
10.3k
    m_bestMutabilityAndLocation = MutabilityAndLocation{_mutability, _location};
241
347k
  if (!m_currentFunction || _mutability <= m_currentFunction->stateMutability())
242
347k
    return;
243
244
  // Check for payable here, because any occurrence of `msg.value`
245
  // will set mutability to payable.
246
65
  if (_mutability == StateMutability::View || (
247
45
    _mutability == StateMutability::Payable &&
248
34
    m_currentFunction->stateMutability() == StateMutability::Pure
249
45
  ))
250
23
  {
251
23
    m_errorReporter.typeError(
252
23
      2527_error,
253
23
      _location,
254
23
      "Function declared as pure, but this expression (potentially) reads from the "
255
23
      "environment or state and thus requires \"view\"."
256
23
    );
257
23
    m_errors = true;
258
23
  }
259
42
  else if (_mutability == StateMutability::NonPayable)
260
11
  {
261
11
    m_errorReporter.typeError(
262
11
      8961_error,
263
11
      _location,
264
11
      "Function cannot be declared as " +
265
11
      stateMutabilityToString(m_currentFunction->stateMutability()) +
266
11
      " because this expression (potentially) modifies the state."
267
11
    );
268
11
    m_errors = true;
269
11
  }
270
31
  else if (_mutability == StateMutability::Payable)
271
31
  {
272
    // We do not warn for library functions because they cannot be payable anyway.
273
    // Also internal functions should be allowed to use `msg.value`.
274
31
    if ((m_currentFunction->isConstructor() || m_currentFunction->isPublic()) && !m_currentFunction->libraryFunction())
275
6
    {
276
6
      if (_nestedLocation)
277
2
        m_errorReporter.typeError(
278
2
          4006_error,
279
2
          _location,
280
2
          SecondarySourceLocation().append("\"msg.value\" or \"callvalue()\" appear here inside the modifier.", *_nestedLocation),
281
2
          m_currentFunction->isConstructor()  ?
282
0
            "This modifier uses \"msg.value\" or \"callvalue()\" and thus the constructor has to be payable."
283
2
            : "This modifier uses \"msg.value\" or \"callvalue()\" and thus the function has to be payable or internal."
284
2
        );
285
4
      else
286
4
        m_errorReporter.typeError(
287
4
          5887_error,
288
4
          _location,
289
4
          m_currentFunction->isConstructor()  ?
290
2
            "\"msg.value\" and \"callvalue()\" can only be used in payable constructors. Make the constructor \"payable\" to avoid this error."
291
4
            : "\"msg.value\" and \"callvalue()\" can only be used in payable public functions. Make the function \"payable\" or use an internal function to avoid this error."
292
4
        );
293
6
      m_errors = true;
294
6
    }
295
31
  }
296
0
  else
297
31
    solAssert(false, "");
298
299
65
  solAssert(
300
65
    m_currentFunction->stateMutability() == StateMutability::View ||
301
65
    m_currentFunction->stateMutability() == StateMutability::Pure ||
302
65
    m_currentFunction->stateMutability() == StateMutability::NonPayable,
303
65
    ""
304
65
  );
305
65
}
306
307
ViewPureChecker::MutabilityAndLocation const& ViewPureChecker::modifierMutability(
308
  ModifierDefinition const& _modifier
309
)
310
426
{
311
426
  if (!m_inferredMutability.count(&_modifier))
312
67
  {
313
67
    MutabilityAndLocation bestMutabilityAndLocation{};
314
67
    FunctionDefinition const* currentFunction = nullptr;
315
67
    std::swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation);
316
67
    std::swap(currentFunction, m_currentFunction);
317
318
67
    _modifier.accept(*this);
319
320
67
    std::swap(bestMutabilityAndLocation, m_bestMutabilityAndLocation);
321
67
    std::swap(currentFunction, m_currentFunction);
322
67
  }
323
426
  return m_inferredMutability.at(&_modifier);
324
426
}
325
326
void ViewPureChecker::reportFunctionCallMutability(StateMutability _mutability, langutil::SourceLocation const& _location)
327
23.3k
{
328
  // We only require "nonpayable" to call a payable function.
329
23.3k
  if (_mutability == StateMutability::Payable)
330
727
    _mutability = StateMutability::NonPayable;
331
23.3k
  reportMutability(_mutability, _location);
332
23.3k
}
333
334
void ViewPureChecker::endVisit(BinaryOperation const& _binaryOperation)
335
54.6k
{
336
54.6k
  if (*_binaryOperation.annotation().userDefinedFunction != nullptr)
337
43
    reportFunctionCallMutability((*_binaryOperation.annotation().userDefinedFunction)->stateMutability(), _binaryOperation.location());
338
54.6k
}
339
340
void ViewPureChecker::endVisit(UnaryOperation const& _unaryOperation)
341
16.8k
{
342
16.8k
  if (*_unaryOperation.annotation().userDefinedFunction != nullptr)
343
13
    reportFunctionCallMutability((*_unaryOperation.annotation().userDefinedFunction)->stateMutability(), _unaryOperation.location());
344
16.8k
}
345
346
void ViewPureChecker::endVisit(FunctionCall const& _functionCall)
347
32.4k
{
348
32.4k
  if (*_functionCall.annotation().kind != FunctionCallKind::FunctionCall)
349
9.16k
    return;
350
351
23.3k
  reportFunctionCallMutability(
352
23.3k
    dynamic_cast<FunctionType const&>(*_functionCall.expression().annotation().type).stateMutability(),
353
23.3k
    _functionCall.location()
354
23.3k
  );
355
23.3k
}
356
357
bool ViewPureChecker::visit(MemberAccess const& _memberAccess)
358
161k
{
359
  // Catch the special case of `this.f.selector` which is a pure expression.
360
161k
  ASTString const& member = _memberAccess.memberName();
361
161k
  if (
362
161k
    _memberAccess.expression().annotation().type->category() == Type::Category::Function &&
363
684
    member == "selector"
364
161k
  )
365
629
    if (auto const* expr = dynamic_cast<MemberAccess const*>(&_memberAccess.expression()))
366
596
      if (auto const* exprInt = dynamic_cast<Identifier const*>(&expr->expression()))
367
594
        if (exprInt->name() == "this")
368
          // Do not continue visiting.
369
501
          return false;
370
160k
  return true;
371
161k
}
372
373
void ViewPureChecker::endVisit(MemberAccess const& _memberAccess)
374
161k
{
375
161k
  StateMutability mutability = StateMutability::Pure;
376
161k
  bool writes = _memberAccess.annotation().willBeWrittenTo;
377
378
161k
  ASTString const& member = _memberAccess.memberName();
379
161k
  switch (_memberAccess.expression().annotation().type->category())
380
161k
  {
381
905
  case Type::Category::Address:
382
905
    if (member == "balance" || member == "code" || member == "codehash")
383
130
      mutability = StateMutability::View;
384
905
    break;
385
1.48k
  case Type::Category::Magic:
386
1.48k
  {
387
1.48k
    using MagicMember = std::pair<MagicType::Kind, std::string>;
388
1.48k
    std::set<MagicMember> static const pureMembers{
389
1.48k
      {MagicType::Kind::ABI, "decode"},
390
1.48k
      {MagicType::Kind::ABI, "encode"},
391
1.48k
      {MagicType::Kind::ABI, "encodePacked"},
392
1.48k
      {MagicType::Kind::ABI, "encodeWithSelector"},
393
1.48k
      {MagicType::Kind::ABI, "encodeCall"},
394
1.48k
      {MagicType::Kind::ABI, "encodeWithSignature"},
395
1.48k
      {MagicType::Kind::Message, "data"},
396
1.48k
      {MagicType::Kind::Message, "sig"},
397
1.48k
      {MagicType::Kind::MetaType, "creationCode"},
398
1.48k
      {MagicType::Kind::MetaType, "runtimeCode"},
399
1.48k
      {MagicType::Kind::MetaType, "name"},
400
1.48k
      {MagicType::Kind::MetaType, "interfaceId"},
401
1.48k
      {MagicType::Kind::MetaType, "min"},
402
1.48k
      {MagicType::Kind::MetaType, "max"},
403
1.48k
    };
404
1.48k
    std::set<MagicMember> static const payableMembers{
405
1.48k
      {MagicType::Kind::Message, "value"}
406
1.48k
    };
407
408
1.48k
    auto const& type = dynamic_cast<MagicType const&>(*_memberAccess.expression().annotation().type);
409
1.48k
    MagicMember magicMember(type.kind(), member);
410
411
1.48k
    if (!pureMembers.count(magicMember))
412
256
      mutability = StateMutability::View;
413
1.48k
    if (payableMembers.count(magicMember))
414
83
      mutability = StateMutability::Payable;
415
1.48k
    break;
416
0
  }
417
143k
  case Type::Category::Struct:
418
143k
  {
419
143k
    if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage))
420
33.9k
      mutability = writes ? StateMutability::NonPayable : StateMutability::View;
421
143k
    break;
422
0
  }
423
10.4k
  case Type::Category::Array:
424
10.4k
  {
425
10.4k
    auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type);
426
10.4k
    if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage))
427
393
      mutability = StateMutability::View;
428
10.4k
    break;
429
0
  }
430
4.20k
  default:
431
4.20k
  {
432
4.20k
    if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(
433
4.20k
      _memberAccess.annotation().referencedDeclaration
434
4.20k
    ))
435
243
      if (varDecl->isStateVariable() && !varDecl->isConstant())
436
228
        mutability = writes ? StateMutability::NonPayable : StateMutability::View;
437
4.20k
    break;
438
0
  }
439
161k
  }
440
161k
  reportMutability(mutability, _memberAccess.location());
441
161k
}
442
443
void ViewPureChecker::endVisit(IndexAccess const& _indexAccess)
444
118k
{
445
118k
  if (!_indexAccess.indexExpression())
446
118k
    solAssert(_indexAccess.annotation().type->category() == Type::Category::TypeType, "");
447
118k
  else
448
118k
  {
449
118k
    bool writes = _indexAccess.annotation().willBeWrittenTo;
450
118k
    if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
451
31.0k
      reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess.location());
452
118k
  }
453
118k
}
454
455
void ViewPureChecker::endVisit(IndexRangeAccess const& _indexRangeAccess)
456
430
{
457
430
  bool writes = _indexRangeAccess.annotation().willBeWrittenTo;
458
430
  if (_indexRangeAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage))
459
0
    reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexRangeAccess.location());
460
430
}
461
462
void ViewPureChecker::endVisit(ModifierInvocation const& _modifier)
463
666
{
464
666
  if (ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name().annotation().referencedDeclaration))
465
426
  {
466
426
    MutabilityAndLocation const& mutAndLocation = modifierMutability(*mod);
467
426
    reportMutability(mutAndLocation.mutability, _modifier.location(), mutAndLocation.location);
468
426
  }
469
240
  else
470
    solAssert(dynamic_cast<ContractDefinition const*>(_modifier.name().annotation().referencedDeclaration), "");
471
666
}