/* This file is part of solidity. solidity is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. solidity is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with solidity. If not, see . */ // SPDX-License-Identifier: GPL-3.0 /** * Parser for Yul code and data object container. */ #include #include #include #include #include #include #include #include using namespace solidity; using namespace solidity::yul; using namespace solidity::util; using namespace solidity::langutil; std::shared_ptr ObjectParser::parse(std::shared_ptr const& _scanner, bool _reuseScanner) { m_recursionDepth = 0; try { std::shared_ptr object; m_scanner = _scanner; if (currentToken() == Token::LBrace) { // Special case: Code-only form. object = std::make_shared(); object->name = "object"_yulstring; auto sourceNameMapping = tryParseSourceNameMapping(); object->debugData = std::make_shared(ObjectDebugData{sourceNameMapping}); object->code = parseBlock(sourceNameMapping); if (!object->code) return nullptr; } else object = parseObject(); if (!_reuseScanner) expectToken(Token::EOS); return object; } catch (FatalError const&) { if (m_errorReporter.errors().empty()) throw; // Something is weird here, rather throw again. } return nullptr; } std::shared_ptr ObjectParser::parseObject(Object* _containingObject) { RecursionGuard guard(*this); std::shared_ptr ret = std::make_shared(); auto sourceNameMapping = tryParseSourceNameMapping(); ret->debugData = std::make_shared(ObjectDebugData{sourceNameMapping}); if (currentToken() != Token::Identifier || currentLiteral() != "object") fatalParserError(4294_error, "Expected keyword \"object\"."); advance(); ret->name = parseUniqueName(_containingObject); expectToken(Token::LBrace); ret->code = parseCode(std::move(sourceNameMapping)); while (currentToken() != Token::RBrace) { if (currentToken() == Token::Identifier && currentLiteral() == "object") parseObject(ret.get()); else if (currentToken() == Token::Identifier && currentLiteral() == "data") parseData(*ret); else fatalParserError(8143_error, "Expected keyword \"data\" or \"object\" or \"}\"."); } if (_containingObject) addNamedSubObject(*_containingObject, ret->name, ret); expectToken(Token::RBrace); return ret; } std::shared_ptr ObjectParser::parseCode(std::optional _sourceNames) { if (currentToken() != Token::Identifier || currentLiteral() != "code") fatalParserError(4846_error, "Expected keyword \"code\"."); advance(); return parseBlock(std::move(_sourceNames)); } std::optional ObjectParser::tryParseSourceNameMapping() const { // @use-src 0:"abc.sol", 1:"foo.sol", 2:"bar.sol" // // UseSrcList := UseSrc (',' UseSrc)* // UseSrc := [0-9]+ ':' FileName // FileName := "(([^\"]|\.)*)" // Matches some "@use-src TEXT". static std::regex const lineRE = std::regex( "(^|\\s)@use-src\\b", std::regex_constants::ECMAScript | std::regex_constants::optimize ); std::smatch sm; if (!std::regex_search(m_scanner->currentCommentLiteral(), sm, lineRE)) return std::nullopt; solAssert(sm.size() == 2, ""); auto text = m_scanner->currentCommentLiteral().substr(static_cast(sm.position() + sm.length())); CharStream charStream(text, ""); Scanner scanner(charStream); if (scanner.currentToken() == Token::EOS) return SourceNameMap{}; SourceNameMap sourceNames; while (scanner.currentToken() != Token::EOS) { if (scanner.currentToken() != Token::Number) break; auto sourceIndex = toUnsignedInt(scanner.currentLiteral()); if (!sourceIndex) break; if (scanner.next() != Token::Colon) break; if (scanner.next() != Token::StringLiteral) break; sourceNames[*sourceIndex] = std::make_shared(scanner.currentLiteral()); Token const next = scanner.next(); if (next == Token::EOS) return {std::move(sourceNames)}; if (next != Token::Comma) break; scanner.next(); } m_errorReporter.syntaxError( 9804_error, m_scanner->currentCommentLocation(), "Error parsing arguments to @use-src. Expected: \":\" \"\", ..." ); return std::nullopt; } std::shared_ptr ObjectParser::parseBlock(std::optional _sourceNames) { Parser parser(m_errorReporter, m_dialect, std::move(_sourceNames)); std::shared_ptr block = parser.parseInline(m_scanner); yulAssert(block || m_errorReporter.hasErrors(), "Invalid block but no error!"); return block; } void ObjectParser::parseData(Object& _containingObject) { yulAssert( currentToken() == Token::Identifier && currentLiteral() == "data", "parseData called on wrong input." ); advance(); YulString name = parseUniqueName(&_containingObject); if (currentToken() == Token::HexStringLiteral) expectToken(Token::HexStringLiteral, false); else expectToken(Token::StringLiteral, false); addNamedSubObject(_containingObject, name, std::make_shared(name, asBytes(currentLiteral()))); advance(); } YulString ObjectParser::parseUniqueName(Object const* _containingObject) { expectToken(Token::StringLiteral, false); YulString name{currentLiteral()}; if (name.empty()) parserError(3287_error, "Object name cannot be empty."); else if (_containingObject && _containingObject->name == name) parserError(8311_error, "Object name cannot be the same as the name of the containing object."); else if (_containingObject && _containingObject->subIndexByName.count(name)) parserError(8794_error, "Object name \"" + name.str() + "\" already exists inside the containing object."); advance(); return name; } void ObjectParser::addNamedSubObject(Object& _container, YulString _name, std::shared_ptr _subObject) { _container.subIndexByName[_name] = _container.subObjects.size(); _container.subObjects.emplace_back(std::move(_subObject)); }