Coverage Report

Created: 2022-08-24 06:39

/src/solidity/libyul/ObjectParser.cpp
Line
Count
Source (jump to first uncovered line)
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
 * Parser for Yul code and data object container.
20
 */
21
22
#include <libyul/AST.h>
23
#include <libyul/ObjectParser.h>
24
25
#include <libyul/AsmParser.h>
26
#include <libyul/Exceptions.h>
27
28
#include <liblangutil/Token.h>
29
#include <liblangutil/Scanner.h>
30
31
#include <libsolutil/StringUtils.h>
32
33
#include <regex>
34
35
using namespace std;
36
using namespace solidity;
37
using namespace solidity::yul;
38
using namespace solidity::util;
39
using namespace solidity::langutil;
40
41
shared_ptr<Object> ObjectParser::parse(shared_ptr<Scanner> const& _scanner, bool _reuseScanner)
42
5.52k
{
43
5.52k
  m_recursionDepth = 0;
44
5.52k
  try
45
5.52k
  {
46
5.52k
    shared_ptr<Object> object;
47
5.52k
    m_scanner = _scanner;
48
49
5.52k
    if (currentToken() == Token::LBrace)
50
5.52k
    {
51
      // Special case: Code-only form.
52
5.52k
      object = make_shared<Object>();
53
5.52k
      object->name = "object"_yulstring;
54
5.52k
      auto sourceNameMapping = tryParseSourceNameMapping();
55
5.52k
      object->debugData = make_shared<ObjectDebugData>(ObjectDebugData{sourceNameMapping});
56
5.52k
      object->code = parseBlock(sourceNameMapping);
57
5.52k
      if (!object->code)
58
0
        return nullptr;
59
5.52k
    }
60
0
    else
61
0
      object = parseObject();
62
5.52k
    if (!_reuseScanner)
63
5.52k
      expectToken(Token::EOS);
64
5.52k
    return object;
65
5.52k
  }
66
5.52k
  catch (FatalError const&)
67
5.52k
  {
68
0
    if (m_errorReporter.errors().empty())
69
0
      throw; // Something is weird here, rather throw again.
70
0
  }
71
0
  return nullptr;
72
5.52k
}
73
74
shared_ptr<Object> ObjectParser::parseObject(Object* _containingObject)
75
0
{
76
0
  RecursionGuard guard(*this);
77
78
0
  shared_ptr<Object> ret = make_shared<Object>();
79
80
0
  auto sourceNameMapping = tryParseSourceNameMapping();
81
0
  ret->debugData = make_shared<ObjectDebugData>(ObjectDebugData{sourceNameMapping});
82
83
0
  if (currentToken() != Token::Identifier || currentLiteral() != "object")
84
0
    fatalParserError(4294_error, "Expected keyword \"object\".");
85
0
  advance();
86
87
0
  ret->name = parseUniqueName(_containingObject);
88
89
0
  expectToken(Token::LBrace);
90
91
0
  ret->code = parseCode(move(sourceNameMapping));
92
93
0
  while (currentToken() != Token::RBrace)
94
0
  {
95
0
    if (currentToken() == Token::Identifier && currentLiteral() == "object")
96
0
      parseObject(ret.get());
97
0
    else if (currentToken() == Token::Identifier && currentLiteral() == "data")
98
0
      parseData(*ret);
99
0
    else
100
0
      fatalParserError(8143_error, "Expected keyword \"data\" or \"object\" or \"}\".");
101
0
  }
102
0
  if (_containingObject)
103
0
    addNamedSubObject(*_containingObject, ret->name, ret);
104
105
0
  expectToken(Token::RBrace);
106
107
0
  return ret;
108
0
}
109
110
shared_ptr<Block> ObjectParser::parseCode(optional<SourceNameMap> _sourceNames)
111
0
{
112
0
  if (currentToken() != Token::Identifier || currentLiteral() != "code")
113
0
    fatalParserError(4846_error, "Expected keyword \"code\".");
114
0
  advance();
115
116
0
  return parseBlock(move(_sourceNames));
117
0
}
118
119
optional<SourceNameMap> ObjectParser::tryParseSourceNameMapping() const
120
5.52k
{
121
  // @use-src 0:"abc.sol", 1:"foo.sol", 2:"bar.sol"
122
  //
123
  // UseSrcList := UseSrc (',' UseSrc)*
124
  // UseSrc     := [0-9]+ ':' FileName
125
  // FileName   := "(([^\"]|\.)*)"
126
127
  // Matches some "@use-src TEXT".
128
5.52k
  static std::regex const lineRE = std::regex(
129
5.52k
    "(^|\\s)@use-src\\b",
130
5.52k
    std::regex_constants::ECMAScript | std::regex_constants::optimize
131
5.52k
  );
132
5.52k
  std::smatch sm;
133
5.52k
  if (!std::regex_search(m_scanner->currentCommentLiteral(), sm, lineRE))
134
5.52k
    return nullopt;
135
136
0
  solAssert(sm.size() == 2, "");
137
0
  auto text = m_scanner->currentCommentLiteral().substr(static_cast<size_t>(sm.position() + sm.length()));
138
0
  CharStream charStream(text, "");
139
0
  Scanner scanner(charStream);
140
0
  if (scanner.currentToken() == Token::EOS)
141
0
    return SourceNameMap{};
142
0
  SourceNameMap sourceNames;
143
144
0
  while (scanner.currentToken() != Token::EOS)
145
0
  {
146
0
    if (scanner.currentToken() != Token::Number)
147
0
      break;
148
0
    auto sourceIndex = toUnsignedInt(scanner.currentLiteral());
149
0
    if (!sourceIndex)
150
0
      break;
151
0
    if (scanner.next() != Token::Colon)
152
0
      break;
153
0
    if (scanner.next() != Token::StringLiteral)
154
0
      break;
155
0
    sourceNames[*sourceIndex] = make_shared<string const>(scanner.currentLiteral());
156
157
0
    Token const next = scanner.next();
158
0
    if (next == Token::EOS)
159
0
      return {move(sourceNames)};
160
0
    if (next != Token::Comma)
161
0
      break;
162
0
    scanner.next();
163
0
  }
164
165
0
  m_errorReporter.syntaxError(
166
0
    9804_error,
167
0
    m_scanner->currentCommentLocation(),
168
0
    "Error parsing arguments to @use-src. Expected: <number> \":\" \"<filename>\", ..."
169
0
  );
170
0
  return nullopt;
171
0
}
172
173
shared_ptr<Block> ObjectParser::parseBlock(optional<SourceNameMap> _sourceNames)
174
5.52k
{
175
5.52k
  Parser parser(m_errorReporter, m_dialect, move(_sourceNames));
176
5.52k
  shared_ptr<Block> block = parser.parseInline(m_scanner);
177
5.52k
  yulAssert(block || m_errorReporter.hasErrors(), "Invalid block but no error!");
178
5.52k
  return block;
179
5.52k
}
180
181
void ObjectParser::parseData(Object& _containingObject)
182
0
{
183
0
  yulAssert(
184
0
    currentToken() == Token::Identifier && currentLiteral() == "data",
185
0
    "parseData called on wrong input."
186
0
  );
187
0
  advance();
188
189
0
  YulString name = parseUniqueName(&_containingObject);
190
191
0
  if (currentToken() == Token::HexStringLiteral)
192
0
    expectToken(Token::HexStringLiteral, false);
193
0
  else
194
0
    expectToken(Token::StringLiteral, false);
195
0
  addNamedSubObject(_containingObject, name, make_shared<Data>(name, asBytes(currentLiteral())));
196
0
  advance();
197
0
}
198
199
YulString ObjectParser::parseUniqueName(Object const* _containingObject)
200
0
{
201
0
  expectToken(Token::StringLiteral, false);
202
0
  YulString name{currentLiteral()};
203
0
  if (name.empty())
204
0
    parserError(3287_error, "Object name cannot be empty.");
205
0
  else if (_containingObject && _containingObject->name == name)
206
0
    parserError(8311_error, "Object name cannot be the same as the name of the containing object.");
207
0
  else if (_containingObject && _containingObject->subIndexByName.count(name))
208
0
    parserError(8794_error, "Object name \"" + name.str() + "\" already exists inside the containing object.");
209
0
  advance();
210
0
  return name;
211
0
}
212
213
void ObjectParser::addNamedSubObject(Object& _container, YulString _name, shared_ptr<ObjectNode> _subObject)
214
0
{
215
0
  _container.subIndexByName[_name] = _container.subObjects.size();
216
0
  _container.subObjects.emplace_back(std::move(_subObject));
217
0
}