Coverage Report

Created: 2026-08-14 07:35

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/libsolidity/analysis/SyntaxChecker.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/SyntaxChecker.h>
20
21
#include <libsolidity/ast/AST.h>
22
#include <libsolidity/ast/ExperimentalFeatures.h>
23
#include <libsolidity/interface/Version.h>
24
25
#include <libyul/optimiser/Semantics.h>
26
#include <libyul/AST.h>
27
28
#include <liblangutil/ErrorReporter.h>
29
#include <liblangutil/SemVerHandler.h>
30
31
#include <libsolutil/UTF8.h>
32
33
#include <string>
34
35
using namespace solidity;
36
using namespace solidity::langutil;
37
using namespace solidity::frontend;
38
using namespace solidity::util;
39
40
bool SyntaxChecker::checkSyntax(ASTNode const& _astRoot)
41
28.2k
{
42
28.2k
  _astRoot.accept(*this);
43
28.2k
  return !Error::containsErrors(m_errorReporter.errors());
44
28.2k
}
45
46
bool SyntaxChecker::visit(SourceUnit const& _sourceUnit)
47
28.2k
{
48
28.2k
  m_versionPragmaFound = false;
49
28.2k
  m_sourceUnit = &_sourceUnit;
50
28.2k
  return true;
51
28.2k
}
52
53
void SyntaxChecker::endVisit(SourceUnit const& _sourceUnit)
54
28.2k
{
55
28.2k
  if (!m_versionPragmaFound)
56
26.6k
  {
57
26.6k
    std::string errorString("Source file does not specify required compiler version!");
58
26.6k
    SemVerVersion recommendedVersion{std::string(VersionString)};
59
26.6k
    if (!recommendedVersion.isPrerelease())
60
0
      errorString +=
61
0
        " Consider adding \"pragma solidity ^" +
62
0
        std::to_string(recommendedVersion.major()) +
63
0
        std::string(".") +
64
0
        std::to_string(recommendedVersion.minor()) +
65
0
        std::string(".") +
66
0
        std::to_string(recommendedVersion.patch()) +
67
0
        std::string(";\"");
68
69
    // when reporting the warning, print the source name only
70
26.6k
    m_errorReporter.warning(3420_error, {-1, -1, _sourceUnit.location().sourceName}, errorString);
71
26.6k
  }
72
28.2k
  if (!m_sourceUnit->annotation().useABICoderV2.set())
73
25.9k
    m_sourceUnit->annotation().useABICoderV2 = true;
74
28.2k
  m_sourceUnit = nullptr;
75
28.2k
}
76
77
bool SyntaxChecker::visit(PragmaDirective const& _pragma)
78
32.4k
{
79
32.4k
  solAssert(!_pragma.tokens().empty(), "");
80
32.4k
  solAssert(_pragma.tokens().size() == _pragma.literals().size(), "");
81
32.4k
  if (_pragma.tokens()[0] != Token::Identifier)
82
161
    m_errorReporter.syntaxError(5226_error, _pragma.location(), "Invalid pragma \"" + _pragma.literals()[0] + "\"");
83
32.2k
  else if (_pragma.literals()[0] == "experimental")
84
27.9k
  {
85
27.9k
    solAssert(m_sourceUnit, "");
86
27.9k
    std::vector<std::string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
87
27.9k
    if (literals.empty())
88
20
      m_errorReporter.syntaxError(
89
20
        9679_error,
90
20
        _pragma.location(),
91
20
        "Experimental feature name is missing."
92
20
      );
93
27.9k
    else if (literals.size() > 1)
94
53
      m_errorReporter.syntaxError(
95
53
        6022_error,
96
53
        _pragma.location(),
97
53
        "Stray arguments."
98
53
      );
99
27.8k
    else
100
27.8k
    {
101
27.8k
      std::string const literal = literals[0];
102
27.8k
      if (literal.empty())
103
1
        m_errorReporter.syntaxError(3250_error, _pragma.location(), "Empty experimental feature name is invalid.");
104
27.8k
      else if (literal == "solidity")
105
10
        m_errorReporter.syntaxError(3332_error, _pragma.location(), "The experimental Solidity prototype has been removed from the compiler.");
106
27.8k
      else if (!ExperimentalFeatureNames.count(literal))
107
121
        m_errorReporter.syntaxError(8491_error, _pragma.location(), "Unsupported experimental feature name.");
108
27.7k
      else if (m_sourceUnit->annotation().experimentalFeatures.count(ExperimentalFeatureNames.at(literal)))
109
63
        m_errorReporter.syntaxError(1231_error, _pragma.location(), "Duplicate experimental feature name.");
110
27.6k
      else
111
27.6k
      {
112
27.6k
        auto feature = ExperimentalFeatureNames.at(literal);
113
27.6k
        m_sourceUnit->annotation().experimentalFeatures.insert(feature);
114
27.6k
        if (!ExperimentalFeatureWithoutWarning.count(feature))
115
10
        {
116
10
          if (!m_experimental)
117
10
            m_errorReporter.syntaxError(
118
10
              2816_error,
119
10
              _pragma.location(),
120
10
              "Experimental pragmas can only be used if experimental mode is enabled. To enable experimental mode, use the --experimental flag."
121
10
            );
122
0
          else
123
0
            m_errorReporter.warning(2264_error, _pragma.location(), "Experimental features are turned on. Do not use experimental features on live deployments.");
124
10
        }
125
126
27.6k
        if (feature == ExperimentalFeature::ABIEncoderV2)
127
316
        {
128
316
          if (m_sourceUnit->annotation().useABICoderV2.set())
129
2
          {
130
2
            if (!*m_sourceUnit->annotation().useABICoderV2)
131
1
              m_errorReporter.syntaxError(
132
1
                8273_error,
133
1
                _pragma.location(),
134
1
                "ABI coder v1 has already been selected through \"pragma abicoder v1\"."
135
1
              );
136
2
          }
137
314
          else
138
314
            m_sourceUnit->annotation().useABICoderV2 = true;
139
316
        }
140
27.6k
      }
141
27.8k
    }
142
27.9k
  }
143
4.32k
  else if (_pragma.literals()[0] == "abicoder")
144
2.20k
  {
145
2.20k
    solAssert(m_sourceUnit, "");
146
2.20k
    if (
147
2.20k
      _pragma.literals().size() != 2 ||
148
2.12k
      !std::set<std::string>{"v1", "v2"}.count(_pragma.literals()[1])
149
2.20k
    )
150
216
      m_errorReporter.syntaxError(
151
216
        2745_error,
152
216
        _pragma.location(),
153
216
        "Expected either \"pragma abicoder v1\" or \"pragma abicoder v2\"."
154
216
      );
155
1.98k
    else if (m_sourceUnit->annotation().useABICoderV2.set())
156
54
      m_errorReporter.syntaxError(
157
54
        3845_error,
158
54
        _pragma.location(),
159
54
        "ABI coder has already been selected for this source unit."
160
54
      );
161
1.93k
    else
162
1.93k
      m_sourceUnit->annotation().useABICoderV2 = (_pragma.literals()[1] == "v2");
163
164
2.20k
    if (
165
2.20k
      _pragma.literals().size() > 1 &&
166
2.19k
      _pragma.literals()[1] == "v1"
167
2.20k
    )
168
399
      m_errorReporter.warning(
169
399
        9511_error,
170
399
        _pragma.location(),
171
399
        "ABI coder v1 is deprecated and scheduled for removal. Use ABI coder v2 instead."
172
399
      );
173
2.20k
  }
174
2.12k
  else if (_pragma.literals()[0] == "solidity")
175
1.70k
  {
176
1.70k
    try
177
1.70k
    {
178
1.70k
      std::vector<Token> tokens(_pragma.tokens().begin() + 1, _pragma.tokens().end());
179
1.70k
      std::vector<std::string> literals(_pragma.literals().begin() + 1, _pragma.literals().end());
180
1.70k
      SemVerMatchExpressionParser parser(tokens, literals);
181
1.70k
      SemVerMatchExpression matchExpression = parser.parse();
182
1.70k
      static SemVerVersion const currentVersion{std::string(VersionString)};
183
1.70k
      solAssert(matchExpression.matches(currentVersion));
184
1.70k
      m_versionPragmaFound = true;
185
1.70k
    }
186
1.70k
    catch (SemVerError const&)
187
1.70k
    {
188
      // An unparsable version pragma is an unrecoverable fatal error in the parser.
189
0
      solAssert(false);
190
0
    }
191
1.70k
  }
192
417
  else
193
417
    m_errorReporter.syntaxError(4936_error, _pragma.location(), "Unknown pragma \"" + _pragma.literals()[0] + "\"");
194
195
32.4k
  return true;
196
32.4k
}
197
198
bool SyntaxChecker::visit(ModifierDefinition const&)
199
3.44k
{
200
3.44k
  m_placeholderFound = false;
201
3.44k
  return true;
202
3.44k
}
203
204
void SyntaxChecker::endVisit(ModifierDefinition const& _modifier)
205
3.44k
{
206
3.44k
  if (_modifier.isImplemented() && !m_placeholderFound)
207
33
    m_errorReporter.syntaxError(2883_error, _modifier.body().location(), "Modifier body does not contain '_'.");
208
209
3.44k
  if (_modifier.markedVirtual())
210
247
    m_errorReporter.warning(
211
247
      8429_error,
212
247
      _modifier.location(),
213
247
      "Virtual modifiers are deprecated and scheduled for removal."
214
247
    );
215
216
3.44k
  m_placeholderFound = false;
217
3.44k
}
218
219
void SyntaxChecker::checkSingleStatementVariableDeclaration(ASTNode const& _statement)
220
49.2k
{
221
49.2k
  auto varDecl = dynamic_cast<VariableDeclarationStatement const*>(&_statement);
222
49.2k
  if (varDecl)
223
8
    m_errorReporter.syntaxError(9079_error, _statement.location(), "Variable declarations can only be used inside blocks.");
224
49.2k
}
225
226
bool SyntaxChecker::visit(IfStatement const& _ifStatement)
227
45.2k
{
228
45.2k
  checkSingleStatementVariableDeclaration(_ifStatement.trueStatement());
229
45.2k
  if (Statement const* _statement = _ifStatement.falseStatement())
230
391
    checkSingleStatementVariableDeclaration(*_statement);
231
45.2k
  return true;
232
45.2k
}
233
234
bool SyntaxChecker::visit(WhileStatement const& _whileStatement)
235
241
{
236
241
  m_inLoopDepth++;
237
241
  checkSingleStatementVariableDeclaration(_whileStatement.body());
238
241
  return true;
239
241
}
240
241
void SyntaxChecker::endVisit(WhileStatement const&)
242
241
{
243
241
  m_inLoopDepth--;
244
241
}
245
246
bool SyntaxChecker::visit(ForStatement const& _forStatement)
247
3.37k
{
248
3.37k
  m_inLoopDepth++;
249
3.37k
  checkSingleStatementVariableDeclaration(_forStatement.body());
250
3.37k
  return true;
251
3.37k
}
252
253
void SyntaxChecker::endVisit(ForStatement const&)
254
3.37k
{
255
3.37k
  m_inLoopDepth--;
256
3.37k
}
257
258
bool SyntaxChecker::visit(Block const& _block)
259
31.6k
{
260
31.6k
  if (_block.unchecked())
261
202
  {
262
202
    if (m_uncheckedArithmetic)
263
5
      m_errorReporter.syntaxError(
264
5
        1941_error,
265
5
        _block.location(),
266
5
        "\"unchecked\" blocks cannot be nested."
267
5
      );
268
269
202
    m_uncheckedArithmetic = true;
270
202
  }
271
31.6k
  return true;
272
31.6k
}
273
274
void SyntaxChecker::endVisit(Block const& _block)
275
31.6k
{
276
31.6k
  if (_block.unchecked())
277
202
    m_uncheckedArithmetic = false;
278
31.6k
}
279
280
bool SyntaxChecker::visit(Continue const& _continueStatement)
281
120
{
282
120
  if (m_inLoopDepth <= 0)
283
    // we're not in a for/while loop, report syntax error
284
6
    m_errorReporter.syntaxError(4123_error, _continueStatement.location(), "\"continue\" has to be in a \"for\" or \"while\" loop.");
285
120
  return true;
286
120
}
287
288
bool SyntaxChecker::visit(Break const& _breakStatement)
289
126
{
290
126
  if (m_inLoopDepth <= 0)
291
    // we're not in a for/while loop, report syntax error
292
3
    m_errorReporter.syntaxError(6102_error, _breakStatement.location(), "\"break\" has to be in a \"for\" or \"while\" loop.");
293
126
  return true;
294
126
}
295
296
bool SyntaxChecker::visit(Throw const& _throwStatement)
297
3
{
298
3
  m_errorReporter.syntaxError(
299
3
    4538_error,
300
3
    _throwStatement.location(),
301
3
    "\"throw\" is deprecated in favour of \"revert()\", \"require()\" and \"assert()\"."
302
3
  );
303
304
3
  return true;
305
3
}
306
307
bool SyntaxChecker::visit(Literal const& _literal)
308
339k
{
309
339k
  size_t invalidSequence;
310
339k
  if ((_literal.token() == Token::UnicodeStringLiteral) && !validateUTF8(_literal.value(), invalidSequence))
311
140
    m_errorReporter.syntaxError(
312
140
      8452_error,
313
140
      _literal.location(),
314
140
      "Contains invalid UTF-8 sequence at position " + toString(invalidSequence) + "."
315
140
    );
316
317
339k
  if (_literal.token() != Token::Number)
318
49.7k
    return true;
319
320
290k
  ASTString const& value = _literal.value();
321
290k
  solAssert(!value.empty(), "");
322
323
  // Generic checks no matter what base this number literal is of:
324
290k
  if (value.back() == '_')
325
224
  {
326
224
    m_errorReporter.syntaxError(2090_error, _literal.location(), "Invalid use of underscores in number literal. No trailing underscores allowed.");
327
224
    return true;
328
224
  }
329
330
289k
  if (value.find("__") != ASTString::npos)
331
148
  {
332
148
    m_errorReporter.syntaxError(2990_error, _literal.location(), "Invalid use of underscores in number literal. Only one consecutive underscore between digits is allowed.");
333
148
    return true;
334
148
  }
335
336
289k
  if (!_literal.isHexNumber()) // decimal literal
337
282k
  {
338
282k
    if (value.find("._") != ASTString::npos)
339
599
      m_errorReporter.syntaxError(3891_error, _literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed.");
340
341
282k
    if (value.find("_.") != ASTString::npos)
342
321
      m_errorReporter.syntaxError(1023_error, _literal.location(), "Invalid use of underscores in number literal. No underscores in front of the fraction part allowed.");
343
344
282k
    if (value.find("_e") != ASTString::npos)
345
377
      m_errorReporter.syntaxError(6415_error, _literal.location(), "Invalid use of underscores in number literal. No underscore at the end of the mantissa allowed.");
346
347
282k
    if (value.find("e_") != ASTString::npos)
348
102
      m_errorReporter.syntaxError(6165_error, _literal.location(), "Invalid use of underscores in number literal. No underscore in front of exponent allowed.");
349
282k
  }
350
351
289k
  return true;
352
289k
}
353
354
bool SyntaxChecker::visit(UnaryOperation const& _operation)
355
47.1k
{
356
47.1k
  solAssert(_operation.getOperator() != Token::Add);
357
47.1k
  return true;
358
47.1k
}
359
360
bool SyntaxChecker::visit(InlineAssembly const& _inlineAssembly)
361
4.34k
{
362
4.34k
  if (_inlineAssembly.flags())
363
44
    for (auto flag: *_inlineAssembly.flags())
364
88
    {
365
88
      if (*flag == "memory-safe")
366
31
      {
367
31
        if (_inlineAssembly.annotation().markedMemorySafe)
368
2
          m_errorReporter.syntaxError(
369
2
            7026_error,
370
2
            _inlineAssembly.location(),
371
2
            "Inline assembly marked memory-safe multiple times."
372
2
          );
373
31
        _inlineAssembly.annotation().markedMemorySafe = true;
374
31
      }
375
57
      else
376
57
        m_errorReporter.warning(
377
57
          4430_error,
378
57
          _inlineAssembly.location(),
379
57
          "Unknown inline assembly flag: \"" + *flag + "\""
380
57
        );
381
88
    }
382
383
4.34k
  if (!m_useYulOptimizer)
384
1.85k
    return false;
385
386
2.49k
  if (yul::MSizeFinder::containsMSize(_inlineAssembly.dialect(), _inlineAssembly.operations().root()))
387
0
    m_errorReporter.syntaxError(
388
0
      6553_error,
389
0
      _inlineAssembly.location(),
390
0
      "The msize instruction cannot be used when the Yul optimizer is activated because "
391
0
      "it can change its semantics. Either disable the Yul optimizer or do not use the instruction."
392
0
    );
393
394
2.49k
  return false;
395
4.34k
}
396
397
bool SyntaxChecker::visit(PlaceholderStatement const& _placeholder)
398
989
{
399
989
  if (m_uncheckedArithmetic)
400
1
    m_errorReporter.syntaxError(
401
1
      2573_error,
402
1
      _placeholder.location(),
403
1
      "The placeholder statement \"_\" cannot be used inside an \"unchecked\" block."
404
1
    );
405
406
989
  m_placeholderFound = true;
407
989
  return true;
408
989
}
409
410
bool SyntaxChecker::visit(ContractDefinition const& _contract)
411
30.1k
{
412
30.1k
  m_currentContractKind = _contract.contractKind();
413
414
30.1k
  ASTString const& contractName = _contract.name();
415
30.1k
  for (FunctionDefinition const* function: _contract.definedFunctions())
416
27.8k
    if (function->name() == contractName)
417
1
      m_errorReporter.syntaxError(
418
1
        5796_error,
419
1
        function->location(),
420
1
        "Functions are not allowed to have the same name as the contract. "
421
1
        "If you intend this to be a constructor, use \"constructor(...) { ... }\" to define it."
422
1
      );
423
30.1k
  return true;
424
30.1k
}
425
426
void SyntaxChecker::endVisit(ContractDefinition const&)
427
30.1k
{
428
30.1k
  m_currentContractKind = std::nullopt;
429
30.1k
}
430
431
bool SyntaxChecker::visit(UsingForDirective const& _usingFor)
432
779
{
433
779
  if (!_usingFor.usesBraces())
434
779
    solAssert(
435
779
      _usingFor.functionsAndOperators().size() == 1 &&
436
779
      !std::get<1>(_usingFor.functionsAndOperators().front())
437
779
    );
438
439
779
  if (!m_currentContractKind && !_usingFor.typeName())
440
12
    m_errorReporter.syntaxError(
441
12
      8118_error,
442
12
      _usingFor.location(),
443
12
      "The type has to be specified explicitly at file level (cannot use '*')."
444
12
    );
445
767
  else if (_usingFor.usesBraces() && !_usingFor.typeName())
446
10
    m_errorReporter.syntaxError(
447
10
      3349_error,
448
10
      _usingFor.location(),
449
10
      "The type has to be specified explicitly when attaching specific functions."
450
10
    );
451
779
  if (_usingFor.global() && !_usingFor.typeName())
452
4
    m_errorReporter.syntaxError(
453
4
      2854_error,
454
4
      _usingFor.location(),
455
4
      "Can only globally attach functions to specific types."
456
4
    );
457
779
  if (_usingFor.global() && m_currentContractKind)
458
8
    m_errorReporter.syntaxError(
459
8
      3367_error,
460
8
      _usingFor.location(),
461
8
      "\"global\" can only be used at file level."
462
8
    );
463
779
  if (m_currentContractKind == ContractKind::Interface)
464
3
    m_errorReporter.syntaxError(
465
3
      9088_error,
466
3
      _usingFor.location(),
467
3
      "The \"using for\" directive is not allowed inside interfaces."
468
3
    );
469
470
779
  return true;
471
779
}
472
473
bool SyntaxChecker::visit(FunctionDefinition const& _function)
474
31.3k
{
475
31.3k
  if (!_function.isFree() && !_function.isConstructor() && _function.noVisibilitySpecified())
476
1.53k
  {
477
1.53k
    std::string suggestedVisibility =
478
1.53k
      _function.isFallback() ||
479
1.50k
      _function.isReceive() ||
480
1.44k
      m_currentContractKind == ContractKind::Interface
481
1.53k
    ? "external" : "public";
482
1.53k
    m_errorReporter.syntaxError(
483
1.53k
      4937_error,
484
1.53k
      _function.location(),
485
1.53k
      "No visibility specified. Did you intend to add \"" + suggestedVisibility + "\"?"
486
1.53k
    );
487
1.53k
  }
488
29.8k
  else if (_function.isFree())
489
3.53k
  {
490
3.53k
    if (!_function.noVisibilitySpecified())
491
181
      m_errorReporter.syntaxError(
492
181
        4126_error,
493
181
        _function.location(),
494
181
        "Free functions cannot have visibility."
495
181
      );
496
3.53k
    if (!_function.isImplemented())
497
500
      m_errorReporter.typeError(4668_error, _function.location(), "Free functions must be implemented.");
498
3.53k
  }
499
500
31.3k
  if (m_currentContractKind == ContractKind::Interface && !_function.modifiers().empty())
501
65
    m_errorReporter.syntaxError(5842_error, _function.location(), "Functions in interfaces cannot have modifiers.");
502
31.2k
  else if (!_function.isImplemented() && !_function.modifiers().empty())
503
202
    m_errorReporter.syntaxError(2668_error, _function.location(), "Functions without implementation cannot have modifiers.");
504
505
31.3k
  return true;
506
31.3k
}
507
508
bool SyntaxChecker::visit(FunctionTypeName const& _node)
509
1.27k
{
510
1.27k
  for (auto const& decl: _node.parameterTypeList()->parameters())
511
625
    if (!decl->name().empty())
512
127
      m_errorReporter.warning(6162_error, decl->location(), "Naming function type parameters is deprecated.");
513
514
1.27k
  for (auto const& decl: _node.returnParameterTypeList()->parameters())
515
481
    if (!decl->name().empty())
516
8
      m_errorReporter.syntaxError(7304_error, decl->location(), "Return parameters in function types may not be named.");
517
518
1.27k
  return true;
519
1.27k
}
520
521
bool SyntaxChecker::visit(StructDefinition const& _struct)
522
9.78k
{
523
9.78k
  if (_struct.members().empty())
524
92
    m_errorReporter.syntaxError(5306_error, _struct.location(), "Defining empty structs is disallowed.");
525
526
9.78k
  return true;
527
9.78k
}
528