Coverage Report

Created: 2026-10-26 06:53

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/solidity/liblangutil/Scanner.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
 * This file is derived from the file "scanner.cc", which was part of the
18
 * V8 project. The original copyright header follows:
19
 *
20
 * Copyright 2006-2012, the V8 project authors. All rights reserved.
21
 * Redistribution and use in source and binary forms, with or without
22
 * modification, are permitted provided that the following conditions are
23
 * met:
24
 *
25
 * * Redistributions of source code must retain the above copyright
26
 *   notice, this list of conditions and the following disclaimer.
27
 * * Redistributions in binary form must reproduce the above
28
 *   copyright notice, this list of conditions and the following
29
 *   disclaimer in the documentation and/or other materials provided
30
 *   with the distribution.
31
 * * Neither the name of Google Inc. nor the names of its
32
 *   contributors may be used to endorse or promote products derived
33
 *   from this software without specific prior written permission.
34
 *
35
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
36
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
37
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
38
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
39
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
40
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
41
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
42
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
43
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
44
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
45
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
46
*/
47
/**
48
 * @author Christian <c@ethdev.com>
49
 * @date 2014
50
 * Solidity scanner.
51
 */
52
53
#include <liblangutil/Common.h>
54
#include <liblangutil/Exceptions.h>
55
#include <liblangutil/Scanner.h>
56
57
#include <boost/algorithm/string/classification.hpp>
58
59
#include <optional>
60
#include <string_view>
61
#include <tuple>
62
#include <array>
63
64
65
namespace solidity::langutil
66
{
67
68
std::string to_string(ScannerError _errorCode)
69
2.49k
{
70
2.49k
  switch (_errorCode)
71
2.49k
  {
72
0
    case ScannerError::NoError: return "No error.";
73
555
    case ScannerError::IllegalToken: return "Invalid token.";
74
109
    case ScannerError::IllegalHexString: return "Expected even number of hex-nibbles.";
75
44
    case ScannerError::IllegalHexDigit: return "Hexadecimal digit missing or invalid.";
76
505
    case ScannerError::IllegalCommentTerminator: return "Expected multi-line comment-terminator.";
77
317
    case ScannerError::IllegalEscapeSequence: return "Invalid escape sequence.";
78
5
    case ScannerError::UnicodeCharacterInNonUnicodeString: return "Invalid character in string. If you are trying to use Unicode characters, use a unicode\"...\" string literal.";
79
82
    case ScannerError::IllegalCharacterInString: return "Invalid character in string.";
80
457
    case ScannerError::IllegalStringEndQuote: return "Expected string end-quote.";
81
23
    case ScannerError::IllegalNumberSeparator: return "Invalid use of number separator '_'.";
82
95
    case ScannerError::IllegalExponent: return "Invalid exponent.";
83
173
    case ScannerError::IllegalNumberEnd: return "Identifier-start is not allowed at end of a number.";
84
26
    case ScannerError::OctalNotAllowed: return "Octal numbers not allowed.";
85
13
    case ScannerError::DirectionalOverrideUnderflow: return "Unicode direction override underflow in comment or string literal.";
86
92
    case ScannerError::DirectionalOverrideMismatch: return "Mismatching directional override markers in comment or string literal.";
87
0
    default:
88
0
      solAssert(false, "Unhandled case in to_string(ScannerError)");
89
0
      return "";
90
2.49k
  }
91
2.49k
}
92
93
94
std::ostream& operator<<(std::ostream& os, ScannerError _errorCode)
95
0
{
96
0
  return os << to_string(_errorCode);
97
0
}
98
99
/// Scoped helper for literal recording. Automatically drops the literal
100
/// if aborting the scanning before it's complete.
101
enum LiteralType
102
{
103
  LITERAL_TYPE_STRING,
104
  LITERAL_TYPE_NUMBER, // not really different from string type in behaviour
105
  LITERAL_TYPE_COMMENT
106
};
107
108
class LiteralScope
109
{
110
public:
111
  explicit LiteralScope(Scanner* _self, enum LiteralType _type):
112
35.6M
    m_type(_type),
113
35.6M
    m_scanner(_self),
114
35.6M
    m_complete(false)
115
35.6M
  {
116
35.6M
    if (_type == LITERAL_TYPE_COMMENT)
117
392k
      m_scanner->m_skippedComments[Scanner::NextNext].literal.clear();
118
35.2M
    else
119
35.2M
      m_scanner->m_tokens[Scanner::NextNext].literal.clear();
120
35.6M
  }
121
  ~LiteralScope()
122
35.6M
  {
123
35.6M
    if (!m_complete)
124
13.8k
    {
125
13.8k
      if (m_type == LITERAL_TYPE_COMMENT)
126
0
        m_scanner->m_skippedComments[Scanner::NextNext].literal.clear();
127
13.8k
      else
128
13.8k
        m_scanner->m_tokens[Scanner::NextNext].literal.clear();
129
13.8k
    }
130
35.6M
  }
131
35.6M
  void complete() { m_complete = true; }
132
133
private:
134
  enum LiteralType m_type;
135
  Scanner* m_scanner;
136
  bool m_complete;
137
};
138
139
void Scanner::reset()
140
628k
{
141
628k
  m_source.reset();
142
628k
  m_kind = ScannerKind::Solidity;
143
628k
  m_char = m_source.get();
144
628k
  skipWhitespace();
145
628k
  next();
146
628k
  next();
147
628k
  next();
148
628k
}
149
150
void Scanner::setPosition(size_t _offset)
151
0
{
152
0
  m_char = m_source.setPosition(_offset);
153
0
  scanToken();
154
0
  next();
155
0
  next();
156
0
}
157
158
bool Scanner::scanHexByte(char& o_scannedByte)
159
5.33M
{
160
5.33M
  char x = 0;
161
16.0M
  for (size_t i = 0; i < 2; i++)
162
10.6M
  {
163
10.6M
    int d = hexValue(m_char);
164
10.6M
    if (d < 0)
165
2.60k
    {
166
2.60k
      rollback(i);
167
2.60k
      return false;
168
2.60k
    }
169
10.6M
    x = static_cast<char>(x * 16 + d);
170
10.6M
    advance();
171
10.6M
  }
172
5.33M
  o_scannedByte = x;
173
5.33M
  return true;
174
5.33M
}
175
176
std::optional<unsigned> Scanner::scanUnicode()
177
14.0k
{
178
14.0k
  unsigned x = 0;
179
64.2k
  for (size_t i = 0; i < 4; i++)
180
52.4k
  {
181
52.4k
    int d = hexValue(m_char);
182
52.4k
    if (d < 0)
183
2.20k
    {
184
2.20k
      rollback(i);
185
2.20k
      return {};
186
2.20k
    }
187
50.2k
    x = x * 16 + static_cast<unsigned>(d);
188
50.2k
    advance();
189
50.2k
  }
190
11.8k
  return x;
191
14.0k
}
192
193
// This supports codepoints between 0000 and FFFF.
194
void Scanner::addUnicodeAsUTF8(unsigned codepoint)
195
11.8k
{
196
11.8k
  if (codepoint <= 0x7f)
197
2.47k
    addLiteralChar(char(codepoint));
198
9.36k
  else if (codepoint <= 0x7ff)
199
3.08k
  {
200
3.08k
    addLiteralChar(char(0xc0u | (codepoint >> 6u)));
201
3.08k
    addLiteralChar(char(0x80u | (codepoint & 0x3fu)));
202
3.08k
  }
203
6.28k
  else
204
6.28k
  {
205
6.28k
    addLiteralChar(char(0xe0u | (codepoint >> 12u)));
206
6.28k
    addLiteralChar(char(0x80u | ((codepoint >> 6u) & 0x3fu)));
207
6.28k
    addLiteralChar(char(0x80u | (codepoint & 0x3fu)));
208
6.28k
  }
209
11.8k
}
210
211
void Scanner::rescan()
212
1.04M
{
213
1.04M
  size_t rollbackTo = 0;
214
1.04M
  if (m_skippedComments[Current].literal.empty())
215
1.03M
    rollbackTo = static_cast<size_t>(m_tokens[Current].location.start);
216
16.7k
  else
217
16.7k
    rollbackTo = static_cast<size_t>(m_skippedComments[Current].location.start);
218
1.04M
  m_char = m_source.rollback(m_source.position() - rollbackTo);
219
1.04M
  next();
220
1.04M
  next();
221
1.04M
  next();
222
1.04M
}
223
224
// Ensure that tokens can be stored in a byte.
225
BOOST_STATIC_ASSERT(TokenTraits::count() <= 0x100);
226
227
Token Scanner::next()
228
78.1M
{
229
78.1M
  m_tokens[Current] = std::move(m_tokens[Next]);
230
78.1M
  m_tokens[Next] = std::move(m_tokens[NextNext]);
231
78.1M
  m_skippedComments[Current] = std::move(m_skippedComments[Next]);
232
78.1M
  m_skippedComments[Next] = std::move(m_skippedComments[NextNext]);
233
234
78.1M
  scanToken();
235
236
78.1M
  return m_tokens[Current].token;
237
78.1M
}
238
239
Token Scanner::selectToken(char _next, Token _then, Token _else)
240
14.3k
{
241
14.3k
  advance();
242
14.3k
  if (m_char == _next)
243
1.00k
    return selectToken(_then);
244
13.3k
  else
245
13.3k
    return _else;
246
14.3k
}
247
248
bool Scanner::skipWhitespace()
249
36.8M
{
250
36.8M
  size_t const startPosition = sourcePos();
251
156M
  while (isWhiteSpace(m_char))
252
119M
    advance();
253
  // Return whether or not we skipped any characters.
254
36.8M
  return sourcePos() != startPosition;
255
36.8M
}
256
257
bool Scanner::skipWhitespaceExceptUnicodeLinebreak()
258
676k
{
259
676k
  size_t const startPosition = sourcePos();
260
5.08M
  while (isWhiteSpace(m_char) && !isUnicodeLinebreak())
261
4.40M
    advance();
262
  // Return whether or not we skipped any characters.
263
676k
  return sourcePos() != startPosition;
264
676k
}
265
266
267
namespace
268
{
269
270
/// Tries to scan for an RLO/LRO/RLE/LRE/PDF and keeps track of script writing direction override depth.
271
///
272
/// @returns ScannerError::NoError in case of successful parsing and directional encodings are paired
273
///          and error code in case the input's lexical parser state is invalid and this error should be reported
274
///          to the user.
275
static ScannerError validateBiDiMarkup(CharStream& _stream, size_t _startPosition)
276
224k
{
277
224k
  static std::array<std::pair<std::string_view, int>, 5> constexpr directionalSequences{
278
224k
    std::pair<std::string_view, int>{"\xE2\x80\xAD", 1}, // U+202D (LRO - Left-to-Right Override)
279
224k
    std::pair<std::string_view, int>{"\xE2\x80\xAE", 1}, // U+202E (RLO - Right-to-Left Override)
280
224k
    std::pair<std::string_view, int>{"\xE2\x80\xAA", 1}, // U+202A (LRE - Left-to-Right Embedding)
281
224k
    std::pair<std::string_view, int>{"\xE2\x80\xAB", 1}, // U+202B (RLE - Right-to-Left Embedding)
282
224k
    std::pair<std::string_view, int>{"\xE2\x80\xAC", -1} // U+202C (PDF - Pop Directional Formatting
283
224k
  };
284
285
224k
  size_t endPosition = _stream.position();
286
224k
  _stream.setPosition(_startPosition);
287
288
224k
  int directionOverrideDepth = 0;
289
290
4.13M
  for (size_t currentPos = _startPosition; currentPos < endPosition; ++currentPos)
291
3.91M
  {
292
3.91M
    _stream.setPosition(currentPos);
293
294
3.91M
    for (auto const& [sequence, depthChange]: directionalSequences)
295
19.5M
      if (_stream.prefixMatch(sequence))
296
3.55k
        directionOverrideDepth += depthChange;
297
298
3.91M
    if (directionOverrideDepth < 0)
299
89
      return ScannerError::DirectionalOverrideUnderflow;
300
3.91M
  }
301
302
224k
  _stream.setPosition(endPosition);
303
304
224k
  return directionOverrideDepth > 0 ? ScannerError::DirectionalOverrideMismatch : ScannerError::NoError;
305
224k
}
306
307
}
308
309
Token Scanner::skipSingleLineComment()
310
221k
{
311
  // Line terminator is not part of the comment. If it is a
312
  // non-ascii line terminator, it will result in a parser error.
313
221k
  size_t startPosition = m_source.position();
314
4.06M
  while (!isUnicodeLinebreak())
315
3.84M
    if (!advance())
316
1.93k
      break;
317
318
221k
  ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition);
319
221k
  if (unicodeDirectionError != ScannerError::NoError)
320
394
    return setError(unicodeDirectionError);
321
322
221k
  return Token::Whitespace;
323
221k
}
324
325
bool Scanner::atEndOfLine() const
326
1.73M
{
327
1.73M
  return m_char == '\n' || m_char == '\r';
328
1.73M
}
329
330
bool Scanner::tryScanEndOfLine()
331
11.0M
{
332
11.0M
  if (m_char == '\n')
333
342k
  {
334
342k
    advance();
335
342k
    return true;
336
342k
  }
337
338
10.6M
  if (m_char == '\r')
339
4.43k
  {
340
4.43k
    if (advance() && m_char == '\n')
341
991
      advance();
342
4.43k
    return true;
343
4.43k
  }
344
345
10.6M
  return false;
346
10.6M
}
347
348
size_t Scanner::scanSingleLineDocComment()
349
331k
{
350
331k
  LiteralScope literal(this, LITERAL_TYPE_COMMENT);
351
331k
  size_t endPosition = m_source.position();
352
353
331k
  skipWhitespaceExceptUnicodeLinebreak();
354
355
10.9M
  while (!isSourcePastEndOfInput())
356
10.9M
  {
357
10.9M
    endPosition = m_source.position();
358
10.9M
    if (tryScanEndOfLine())
359
345k
    {
360
      // Check if next line is also a single-line comment.
361
      // If any whitespaces were skipped, use source position before.
362
345k
      if (!skipWhitespaceExceptUnicodeLinebreak())
363
58.8k
        endPosition = m_source.position();
364
365
345k
      if (!m_source.isPastEndOfInput(3) &&
366
343k
        m_source.get(0) == '/' &&
367
19.2k
        m_source.get(1) == '/' &&
368
18.2k
        m_source.get(2) == '/')
369
16.8k
      {
370
16.8k
        if (!m_source.isPastEndOfInput(4) && m_source.get(3) == '/')
371
738
          break; // "////" is not a documentation comment
372
16.1k
        m_char = m_source.advanceAndGet(3);
373
16.1k
        if (atEndOfLine())
374
2.67k
          continue;
375
13.4k
        addCommentLiteralChar('\n');
376
13.4k
      }
377
328k
      else
378
328k
        break; // next line is not a documentation comment, we are done
379
345k
    }
380
10.6M
    else if (isUnicodeLinebreak())
381
      // Any line terminator that is not '\n' is considered to end the
382
      // comment.
383
153
      break;
384
10.6M
    addCommentLiteralChar(m_char);
385
10.6M
    advance();
386
10.6M
  }
387
331k
  literal.complete();
388
331k
  return endPosition;
389
331k
}
390
391
Token Scanner::skipMultiLineComment()
392
2.69k
{
393
2.69k
  size_t startPosition = m_source.position();
394
22.6k
  while (!isSourcePastEndOfInput())
395
21.9k
  {
396
21.9k
    char prevChar = m_char;
397
21.9k
    advance();
398
399
    // If we have reached the end of the multi-line comment, we
400
    // consume the '/' and insert a whitespace. This way all
401
    // multi-line comments are treated as whitespace.
402
21.9k
    if (prevChar == '*' && m_char == '/')
403
2.01k
    {
404
2.01k
      ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition);
405
2.01k
      if (unicodeDirectionError != ScannerError::NoError)
406
148
        return setError(unicodeDirectionError);
407
408
1.86k
      m_char = ' ';
409
1.86k
      return Token::Whitespace;
410
2.01k
    }
411
21.9k
  }
412
  // Unterminated multi-line comment.
413
685
  return setError(ScannerError::IllegalCommentTerminator);
414
2.69k
}
415
416
Token Scanner::scanMultiLineDocComment()
417
61.1k
{
418
61.1k
  LiteralScope literal(this, LITERAL_TYPE_COMMENT);
419
61.1k
  bool endFound = false;
420
61.1k
  bool charsAdded = false;
421
422
116k
  while (isWhiteSpace(m_char) && !atEndOfLine())
423
55.3k
    advance();
424
425
1.65M
  while (!isSourcePastEndOfInput())
426
1.65M
  {
427
    // handle newlines in multiline comments
428
1.65M
    if (atEndOfLine())
429
26.4k
    {
430
26.4k
      skipWhitespace();
431
26.4k
      if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '*')
432
3.76k
      { // it is unknown if this leads to the end of the comment
433
3.76k
        addCommentLiteralChar('*');
434
3.76k
        advance();
435
3.76k
      }
436
22.6k
      else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) != '/')
437
7.38k
      { // skip first '*' in subsequent lines
438
7.38k
        m_char = m_source.advanceAndGet(1);
439
7.38k
        if (atEndOfLine()) // ignores empty lines
440
2.65k
          continue;
441
4.72k
        if (charsAdded)
442
3.00k
          addCommentLiteralChar('\n'); // corresponds to the end of previous line
443
4.72k
      }
444
15.2k
      else if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/')
445
1.37k
      { // if after newline the comment ends, don't insert the newline
446
1.37k
        m_char = m_source.advanceAndGet(2);
447
1.37k
        endFound = true;
448
1.37k
        break;
449
1.37k
      }
450
13.8k
      else if (charsAdded)
451
12.2k
        addCommentLiteralChar('\n');
452
26.4k
    }
453
454
1.64M
    if (!m_source.isPastEndOfInput(1) && m_source.get(0) == '*' && m_source.get(1) == '/')
455
57.0k
    {
456
57.0k
      m_char = m_source.advanceAndGet(2);
457
57.0k
      endFound = true;
458
57.0k
      break;
459
57.0k
    }
460
1.58M
    addCommentLiteralChar(m_char);
461
1.58M
    charsAdded = true;
462
1.58M
    advance();
463
1.58M
  }
464
61.1k
  literal.complete();
465
61.1k
  if (!endFound)
466
2.64k
    return setError(ScannerError::IllegalCommentTerminator);
467
58.4k
  else
468
58.4k
    return Token::CommentLiteral;
469
61.1k
}
470
471
Token Scanner::scanSlash()
472
627k
{
473
627k
  int firstSlashPosition = static_cast<int>(sourcePos());
474
627k
  advance();
475
627k
  if (m_char == '/')
476
553k
  {
477
553k
    if (!advance()) /* double slash comment directly before EOS */
478
368
      return Token::Whitespace;
479
553k
    else if (m_char == '/')
480
333k
    {
481
333k
      advance(); //consume the last '/' at ///
482
483
      // "////"
484
333k
      if (m_char == '/')
485
1.78k
        return skipSingleLineComment();
486
      // doxygen style /// comment
487
331k
      m_skippedComments[NextNext].location.start = firstSlashPosition;
488
331k
      m_skippedComments[NextNext].location.sourceName = m_sourceName;
489
331k
      m_skippedComments[NextNext].token = Token::CommentLiteral;
490
331k
      m_skippedComments[NextNext].location.end = static_cast<int>(scanSingleLineDocComment());
491
331k
      return Token::Whitespace;
492
333k
    }
493
219k
    else
494
219k
      return skipSingleLineComment();
495
553k
  }
496
73.7k
  else if (m_char == '*')
497
65.5k
  {
498
    // doxygen style /** natspec comment
499
65.5k
    if (!advance()) /* slash star comment before EOS */
500
101
      return setError(ScannerError::IllegalCommentTerminator);
501
65.4k
    else if (m_char == '*')
502
63.9k
    {
503
63.9k
      advance(); //consume the last '*' at /**
504
505
      // "/**/"
506
63.9k
      if (m_char == '/')
507
1.66k
      {
508
1.66k
        advance(); //skip the closing slash
509
1.66k
        return Token::Whitespace;
510
1.66k
      }
511
      // "/***"
512
62.2k
      if (m_char == '*')
513
        // "/***/" may be interpreted as empty natspec or skipped; skipping is simpler
514
1.13k
        return skipMultiLineComment();
515
      // we actually have a multiline documentation comment
516
61.1k
      m_skippedComments[NextNext].location.start = firstSlashPosition;
517
61.1k
      m_skippedComments[NextNext].location.sourceName = m_sourceName;
518
61.1k
      Token comment = scanMultiLineDocComment();
519
61.1k
      m_skippedComments[NextNext].location.end = static_cast<int>(sourcePos());
520
61.1k
      m_skippedComments[NextNext].token = comment;
521
61.1k
      if (comment == Token::Illegal)
522
2.64k
        return Token::Illegal; // error already set
523
58.4k
      else
524
58.4k
        return Token::Whitespace;
525
61.1k
    }
526
1.55k
    else
527
1.55k
      return skipMultiLineComment();
528
65.5k
  }
529
8.15k
  else if (m_char == '=')
530
276
    return selectToken(Token::AssignDiv);
531
7.88k
  else
532
7.88k
    return Token::Div;
533
627k
}
534
535
void Scanner::scanToken()
536
78.1M
{
537
78.1M
  m_tokens[NextNext] = {};
538
78.1M
  m_skippedComments[NextNext] = {};
539
540
78.1M
  Token token;
541
  // M and N are for the purposes of grabbing different type sizes
542
78.1M
  unsigned m = 0;
543
78.1M
  unsigned n = 0;
544
78.1M
  do
545
112M
  {
546
    // Remember the position of the next token
547
112M
    m_tokens[NextNext].location.start = static_cast<int>(sourcePos());
548
112M
    switch (m_char)
549
112M
    {
550
783k
    case '"':
551
794k
    case '\'':
552
794k
      token = scanString(false);
553
794k
      break;
554
10.6k
    case '<':
555
      // < <= << <<=
556
10.6k
      advance();
557
10.6k
      if (m_char == '=')
558
333
        token = selectToken(Token::LessThanOrEqual);
559
10.2k
      else if (m_char == '<')
560
2.05k
        token = selectToken('=', Token::AssignShl, Token::SHL);
561
8.24k
      else
562
8.24k
        token = Token::LessThan;
563
10.6k
      break;
564
6.98k
    case '>':
565
      // > >= >> >>= >>> >>>=
566
6.98k
      advance();
567
6.98k
      if (m_char == '=')
568
2.37k
        token = selectToken(Token::GreaterThanOrEqual);
569
4.61k
      else if (m_char == '>')
570
1.57k
      {
571
        // >> >>= >>> >>>=
572
1.57k
        advance();
573
1.57k
        if (m_char == '=')
574
151
          token = selectToken(Token::AssignSar);
575
1.42k
        else if (m_char == '>')
576
434
          token = selectToken('=', Token::AssignShr, Token::SHR);
577
990
        else
578
990
          token = Token::SAR;
579
1.57k
      }
580
3.04k
      else
581
3.04k
        token = Token::GreaterThan;
582
6.98k
      break;
583
86.4k
    case '=':
584
      // = == =>
585
86.4k
      advance();
586
86.4k
      if (m_char == '=')
587
6.31k
        token = selectToken(Token::Equal);
588
80.1k
      else if (m_char == '>')
589
1.55k
        token = selectToken(Token::DoubleArrow);
590
78.5k
      else
591
78.5k
        token = Token::Assign;
592
86.4k
      break;
593
83.9k
    case '!':
594
      // ! !=
595
83.9k
      advance();
596
83.9k
      if (m_char == '=')
597
67.3k
        token = selectToken(Token::NotEqual);
598
16.6k
      else
599
16.6k
        token = Token::Not;
600
83.9k
      break;
601
12.3k
    case '+':
602
      // + ++ +=
603
12.3k
      advance();
604
12.3k
      if (m_char == '+')
605
5.50k
        token = selectToken(Token::Inc);
606
6.87k
      else if (m_char == '=')
607
444
        token = selectToken(Token::AssignAdd);
608
6.43k
      else
609
6.43k
        token = Token::Add;
610
12.3k
      break;
611
710k
    case '-':
612
      // - -- -= ->
613
710k
      advance();
614
710k
      if (m_char == '-')
615
3.31k
        token = selectToken(Token::Dec);
616
706k
      else if (m_char == '=')
617
156
        token = selectToken(Token::AssignSub);
618
706k
      else if (m_char == '>')
619
609k
        token = selectToken(Token::RightArrow);
620
96.8k
      else
621
96.8k
        token = Token::Sub;
622
710k
      break;
623
20.2k
    case '*':
624
      // * ** *=
625
20.2k
      advance();
626
20.2k
      if (m_char == '*')
627
9.69k
        token = selectToken(Token::Exp);
628
10.5k
      else if (m_char == '=')
629
180
        token = selectToken(Token::AssignMul);
630
10.3k
      else
631
10.3k
        token = Token::Mul;
632
20.2k
      break;
633
6.70k
    case '%':
634
      // % %=
635
6.70k
      token = selectToken('=', Token::AssignMod, Token::Mod);
636
6.70k
      break;
637
627k
    case '/':
638
      // /  // /* /=
639
627k
      token = scanSlash();
640
627k
      break;
641
6.82k
    case '&':
642
      // & && &=
643
6.82k
      advance();
644
6.82k
      if (m_char == '&')
645
2.51k
        token = selectToken(Token::And);
646
4.30k
      else if (m_char == '=')
647
870
        token = selectToken(Token::AssignBitAnd);
648
3.43k
      else
649
3.43k
        token = Token::BitAnd;
650
6.82k
      break;
651
5.27k
    case '|':
652
      // | || |=
653
5.27k
      advance();
654
5.27k
      if (m_char == '|')
655
1.98k
        token = selectToken(Token::Or);
656
3.29k
      else if (m_char == '=')
657
118
        token = selectToken(Token::AssignBitOr);
658
3.17k
      else
659
3.17k
        token = Token::BitOr;
660
5.27k
      break;
661
5.19k
    case '^':
662
      // ^ ^=
663
5.19k
      token = selectToken('=', Token::AssignBitXor, Token::BitXor);
664
5.19k
      break;
665
320k
    case '.':
666
      // . Number
667
320k
      advance();
668
320k
      if (isDecimalDigit(m_char))
669
3.91k
        token = scanNumber('.');
670
316k
      else
671
316k
        token = Token::Period;
672
320k
      break;
673
3.29M
    case ':':
674
      // : :=
675
3.29M
      advance();
676
3.29M
      if (m_char == '=')
677
2.39M
        token = selectToken(Token::AssemblyAssign);
678
902k
      else
679
902k
        token = Token::Colon;
680
3.29M
      break;
681
300k
    case ';':
682
300k
      token = selectToken(Token::Semicolon);
683
300k
      break;
684
7.79M
    case ',':
685
7.79M
      token = selectToken(Token::Comma);
686
7.79M
      break;
687
9.40M
    case '(':
688
9.40M
      token = selectToken(Token::LParen);
689
9.40M
      break;
690
9.27M
    case ')':
691
9.27M
      token = selectToken(Token::RParen);
692
9.27M
      break;
693
295k
    case '[':
694
295k
      token = selectToken(Token::LBrack);
695
295k
      break;
696
290k
    case ']':
697
290k
      token = selectToken(Token::RBrack);
698
290k
      break;
699
4.52M
    case '{':
700
4.52M
      token = selectToken(Token::LBrace);
701
4.52M
      break;
702
4.01M
    case '}':
703
4.01M
      token = selectToken(Token::RBrace);
704
4.01M
      break;
705
3.23k
    case '?':
706
3.23k
      token = selectToken(Token::Conditional);
707
3.23k
      break;
708
12.4k
    case '~':
709
12.4k
      token = selectToken(Token::BitNot);
710
12.4k
      break;
711
70.4M
    default:
712
70.4M
      if (isIdentifierStart(m_char))
713
24.5M
      {
714
24.5M
        std::tie(token, m, n) = scanIdentifierOrKeyword();
715
716
        // Special case for hexadecimal literals
717
24.5M
        if (token == Token::Hex)
718
102k
        {
719
          // reset
720
102k
          m = 0;
721
102k
          n = 0;
722
723
          // Special quoted hex string must follow
724
102k
          if (m_char == '"' || m_char == '\'')
725
102k
            token = scanHexString();
726
294
          else
727
294
            token = setError(ScannerError::IllegalToken);
728
102k
        }
729
24.4M
        else if (token == Token::Unicode && m_kind != ScannerKind::Yul)
730
1.12k
        {
731
          // reset
732
1.12k
          m = 0;
733
1.12k
          n = 0;
734
735
          // Special quoted hex string must follow
736
1.12k
          if (m_char == '"' || m_char == '\'')
737
1.06k
            token = scanString(true);
738
60
          else
739
60
            token = setError(ScannerError::IllegalToken);
740
1.12k
        }
741
24.5M
      }
742
45.8M
      else if (isDecimalDigit(m_char))
743
9.74M
        token = scanNumber();
744
36.1M
      else if (skipWhitespace())
745
33.5M
        token = Token::Whitespace;
746
2.55M
      else if (isSourcePastEndOfInput())
747
2.53M
        token = Token::EOS;
748
22.2k
      else
749
22.2k
        token = selectErrorToken(ScannerError::IllegalToken);
750
70.4M
      break;
751
112M
    }
752
    // Continue scanning for tokens as long as we're just skipping
753
    // whitespace.
754
112M
  }
755
112M
  while (token == Token::Whitespace);
756
78.1M
  m_tokens[NextNext].location.end = static_cast<int>(sourcePos());
757
78.1M
  m_tokens[NextNext].location.sourceName = m_sourceName;
758
78.1M
  m_tokens[NextNext].token = token;
759
78.1M
  m_tokens[NextNext].extendedTokenInfo = std::make_tuple(m, n);
760
78.1M
}
761
762
bool Scanner::scanEscape()
763
37.2k
{
764
37.2k
  char c = m_char;
765
766
  // Skip escaped newlines.
767
37.2k
  if (tryScanEndOfLine())
768
1.74k
    return true;
769
35.4k
  advance();
770
771
35.4k
  switch (c)
772
35.4k
  {
773
1.85k
  case '\'':  // fall through
774
4.03k
  case '"':  // fall through
775
9.89k
  case '\\':
776
9.89k
    break;
777
1.85k
  case 'n':
778
1.85k
    c = '\n';
779
1.85k
    break;
780
1.27k
  case 'r':
781
1.27k
    c = '\r';
782
1.27k
    break;
783
2.72k
  case 't':
784
2.72k
    c = '\t';
785
2.72k
    break;
786
14.0k
  case 'u':
787
14.0k
  {
788
14.0k
    if (auto const codepoint = scanUnicode(); codepoint.has_value())
789
11.8k
      addUnicodeAsUTF8(*codepoint);
790
2.20k
    else
791
2.20k
      return false;
792
11.8k
    return true;
793
14.0k
  }
794
5.24k
  case 'x':
795
5.24k
    if (!scanHexByte(c))
796
359
      return false;
797
4.88k
    break;
798
4.88k
  default:
799
467
    return false;
800
35.4k
  }
801
802
20.6k
  addLiteralChar(c);
803
20.6k
  return true;
804
35.4k
}
805
806
bool Scanner::isUnicodeLinebreak()
807
27.8M
{
808
27.8M
  if (0x0a <= m_char && m_char <= 0x0d)
809
    // line feed, vertical tab, form feed, carriage return
810
242k
    return true;
811
27.6M
  if (!m_source.isPastEndOfInput(1) && uint8_t(m_source.get(0)) == 0xc2 && uint8_t(m_source.get(1)) == 0x85)
812
    // NEL - U+0085, C2 85 in utf8
813
61
    return true;
814
27.6M
  if (!m_source.isPastEndOfInput(2) && uint8_t(m_source.get(0)) == 0xe2 && uint8_t(m_source.get(1)) == 0x80 && (
815
6.16k
    uint8_t(m_source.get(2)) == 0xa8 || uint8_t(m_source.get(2)) == 0xa9
816
6.16k
  ))
817
    // LS - U+2028, E2 80 A8  in utf8
818
    // PS - U+2029, E2 80 A9  in utf8
819
103
    return true;
820
27.6M
  return false;
821
27.6M
}
822
823
Token Scanner::scanString(bool const _isUnicode)
824
795k
{
825
795k
  size_t startPosition = m_source.position();
826
795k
  char const quote = m_char;
827
795k
  advance();  // consume quote
828
795k
  LiteralScope literal(this, LITERAL_TYPE_STRING);
829
  // for source location comments we allow multiline string literals
830
9.51M
  while (m_char != quote && !isSourcePastEndOfInput() && (!isUnicodeLinebreak() || m_kind == ScannerKind::SpecialComment))
831
8.72M
  {
832
8.72M
    char c = m_char;
833
8.72M
    advance();
834
835
8.72M
    if (m_kind == ScannerKind::SpecialComment)
836
4.52M
    {
837
4.52M
      if (c == '\\')
838
81.1k
      {
839
81.1k
        if (isSourcePastEndOfInput())
840
121
          return setError(ScannerError::IllegalEscapeSequence);
841
81.0k
        advance();
842
81.0k
      }
843
4.44M
      else
844
4.44M
        addLiteralChar(c);
845
4.52M
    }
846
4.19M
    else
847
4.19M
    {
848
4.19M
      if (c == '\\')
849
37.4k
      {
850
37.4k
        if (isSourcePastEndOfInput() || !scanEscape())
851
3.27k
          return setError(ScannerError::IllegalEscapeSequence);
852
37.4k
      }
853
4.15M
      else
854
4.15M
      {
855
        // Report error on non-printable characters in string literals, however
856
        // allow anything for unicode string literals, because their validity will
857
        // be verified later (in the syntax checker).
858
        //
859
        // We are using a manual range and not isprint() to avoid
860
        // any potential complications with locale.
861
4.15M
        if (!_isUnicode && (static_cast<unsigned>(c) <= 0x1f || static_cast<unsigned>(c) >= 0x7f))
862
1.17k
        {
863
1.17k
          if (m_kind == ScannerKind::Yul)
864
224
            return setError(ScannerError::IllegalCharacterInString);
865
955
          return setError(ScannerError::UnicodeCharacterInNonUnicodeString);
866
1.17k
        }
867
4.15M
        addLiteralChar(c);
868
4.15M
      }
869
4.19M
    }
870
871
8.72M
  }
872
791k
  if (m_char != quote)
873
2.98k
    return setError(ScannerError::IllegalStringEndQuote);
874
875
788k
  if (_isUnicode)
876
935
  {
877
935
    ScannerError unicodeDirectionError = validateBiDiMarkup(m_source, startPosition);
878
935
    if (unicodeDirectionError != ScannerError::NoError)
879
21
      return setError(unicodeDirectionError);
880
935
  }
881
882
788k
  literal.complete();
883
788k
  advance();  // consume quote
884
788k
  return _isUnicode ? Token::UnicodeStringLiteral : Token::StringLiteral;
885
788k
}
886
887
Token Scanner::scanHexString()
888
102k
{
889
102k
  char const quote = m_char;
890
102k
  advance();  // consume quote
891
102k
  LiteralScope literal(this, LITERAL_TYPE_STRING);
892
102k
  bool allowUnderscore = false;
893
5.43M
  while (m_char != quote && !isSourcePastEndOfInput())
894
5.33M
  {
895
5.33M
    char c = m_char;
896
897
5.33M
    if (scanHexByte(c))
898
5.32M
    {
899
5.32M
      addLiteralChar(c);
900
5.32M
      allowUnderscore = true;
901
5.32M
    }
902
2.24k
    else if (c == '_')
903
1.21k
    {
904
1.21k
      advance();
905
1.21k
      if (!allowUnderscore || m_char == quote)
906
268
        return setError(ScannerError::IllegalNumberSeparator);
907
949
      allowUnderscore = false;
908
949
    }
909
1.02k
    else
910
1.02k
      return setError(ScannerError::IllegalHexString);
911
5.33M
  }
912
913
100k
  if (m_char != quote)
914
307
    return setError(ScannerError::IllegalStringEndQuote);
915
916
100k
  literal.complete();
917
100k
  advance();  // consume quote
918
100k
  return Token::HexStringLiteral;
919
100k
}
920
921
// Parse for regex [:digit:]+(_[:digit:]+)*
922
void Scanner::scanDecimalDigits()
923
6.99M
{
924
  // MUST begin with a decimal digit.
925
6.99M
  if (!isDecimalDigit(m_char))
926
2.17M
    return;
927
928
  // May continue with decimal digit or underscore for grouping.
929
4.81M
  do
930
15.7M
    addLiteralCharAndAdvance();
931
15.7M
  while (!m_source.isPastEndOfInput() && (isDecimalDigit(m_char) || m_char == '_'));
932
933
  // Defer further validation of underscore to SyntaxChecker.
934
4.81M
}
935
936
Token Scanner::scanNumber(char _charSeen)
937
9.74M
{
938
9.74M
  enum { DECIMAL, HEX, BINARY } kind = DECIMAL;
939
9.74M
  LiteralScope literal(this, LITERAL_TYPE_NUMBER);
940
9.74M
  if (_charSeen == '.')
941
3.91k
  {
942
    // we have already seen a decimal point of the float
943
3.91k
    addLiteralChar('.');
944
3.91k
    if (m_char == '_')
945
0
      return setError(ScannerError::IllegalToken);
946
3.91k
    scanDecimalDigits();  // we know we have at least one digit
947
3.91k
  }
948
9.74M
  else
949
9.74M
  {
950
9.74M
    solAssert(_charSeen == 0, "");
951
    // if the first character is '0' we must check for octals and hex
952
9.74M
    if (m_char == '0')
953
4.94M
    {
954
4.94M
      addLiteralCharAndAdvance();
955
      // either 0, 0exxx, 0Exxx, 0.xxx or a hex number
956
4.94M
      if (m_char == 'x')
957
2.76M
      {
958
        // hex number
959
2.76M
        kind = HEX;
960
2.76M
        addLiteralCharAndAdvance();
961
2.76M
        if (!isHexDigit(m_char))
962
604
          return setError(ScannerError::IllegalHexDigit); // we must have at least one hex digit after 'x'
963
964
65.4M
        while (isHexDigit(m_char) || m_char == '_') // We keep the underscores for later validation
965
62.7M
          addLiteralCharAndAdvance();
966
2.76M
      }
967
2.17M
      else if (isDecimalDigit(m_char))
968
        // We do not allow octal numbers
969
692
        return setError(ScannerError::OctalNotAllowed);
970
4.94M
    }
971
    // Parse decimal digits and allow trailing fractional part.
972
9.74M
    if (kind == DECIMAL)
973
6.97M
    {
974
6.97M
      scanDecimalDigits();  // optional
975
6.97M
      if (m_char == '.')
976
7.50k
      {
977
7.50k
        if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_')
978
1.08k
        {
979
          // Assume the input may be a floating point number with leading '_' in fraction part.
980
          // Recover by consuming it all but returning `Illegal` right away.
981
1.08k
          addLiteralCharAndAdvance(); // '.'
982
1.08k
          addLiteralCharAndAdvance(); // '_'
983
1.08k
          scanDecimalDigits();
984
1.08k
        }
985
7.50k
        if (m_source.isPastEndOfInput() || !isDecimalDigit(m_source.get(1)))
986
990
        {
987
          // A '.' has to be followed by a number.
988
990
          literal.complete();
989
990
          return Token::Number;
990
990
        }
991
6.51k
        addLiteralCharAndAdvance();
992
6.51k
        scanDecimalDigits();
993
6.51k
      }
994
6.97M
    }
995
9.74M
  }
996
  // scan exponent, if any
997
9.74M
  if (m_char == 'e' || m_char == 'E')
998
10.6k
  {
999
10.6k
    solAssert(kind != HEX, "'e'/'E' must be scanned as part of the hex number");
1000
10.6k
    if (kind != DECIMAL)
1001
0
      return setError(ScannerError::IllegalExponent);
1002
10.6k
    else if (!m_source.isPastEndOfInput(1) && m_source.get(1) == '_')
1003
684
    {
1004
      // Recover from wrongly placed underscore as delimiter in literal with scientific
1005
      // notation by consuming until the end.
1006
684
      addLiteralCharAndAdvance(); // 'e'
1007
684
      addLiteralCharAndAdvance(); // '_'
1008
684
      scanDecimalDigits();
1009
684
      literal.complete();
1010
684
      return Token::Number;
1011
684
    }
1012
    // scan exponent
1013
9.92k
    addLiteralCharAndAdvance(); // 'e' | 'E'
1014
9.92k
    if (m_char == '+' || m_char == '-')
1015
2.67k
      addLiteralCharAndAdvance();
1016
9.92k
    if (!isDecimalDigit(m_char)) // we must have at least one decimal digit after 'e'/'E'
1017
928
      return setError(ScannerError::IllegalExponent);
1018
8.99k
    scanDecimalDigits();
1019
8.99k
  }
1020
  // The source character immediately following a numeric literal must
1021
  // not be an identifier start or a decimal digit; see ECMA-262
1022
  // section 7.8.3, page 17 (note that we read only one decimal digit
1023
  // if the value is 0).
1024
9.74M
  if (isDecimalDigit(m_char) || isIdentifierStart(m_char))
1025
2.39k
    return setError(ScannerError::IllegalNumberEnd);
1026
9.74M
  literal.complete();
1027
9.74M
  return Token::Number;
1028
9.74M
}
1029
1030
std::tuple<Token, unsigned, unsigned> Scanner::scanIdentifierOrKeyword()
1031
24.5M
{
1032
24.5M
  solAssert(isIdentifierStart(m_char), "");
1033
24.5M
  LiteralScope literal(this, LITERAL_TYPE_STRING);
1034
24.5M
  addLiteralCharAndAdvance();
1035
  // Scan the rest of the identifier characters.
1036
174M
  while (isIdentifierPart(m_char) || (m_char == '.' && m_kind == ScannerKind::Yul))
1037
149M
    addLiteralCharAndAdvance();
1038
24.5M
  literal.complete();
1039
1040
24.5M
  auto const token = TokenTraits::fromIdentifierOrKeyword(m_tokens[NextNext].literal);
1041
24.5M
  switch (m_kind)
1042
24.5M
  {
1043
1.37k
  case ScannerKind::SpecialComment:
1044
    // there are no keywords in special comments
1045
1.37k
    return std::make_tuple(Token::Identifier, 0, 0);
1046
2.28M
  case ScannerKind::Solidity:
1047
2.28M
    break;
1048
22.3M
  case ScannerKind::Yul:
1049
    // Turn Solidity identifier into a Yul keyword
1050
22.3M
    if (m_tokens[NextNext].literal == "leave")
1051
103k
      return std::make_tuple(Token::Leave, 0, 0);
1052
    // Turn non-Yul keywords into identifiers.
1053
22.2M
    if (!TokenTraits::isYulKeyword(std::get<0>(token)))
1054
18.4M
      return std::make_tuple(Token::Identifier, 0, 0);
1055
3.77M
    break;
1056
24.5M
  }
1057
6.06M
  return token;
1058
24.5M
}
1059
1060
} // namespace solidity::langutil