diff --git a/libsolidity/AST.h b/libsolidity/AST.h index bedb5f106..cbb780871 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -72,6 +72,9 @@ public: virtual void accept(ASTVisitor& _visitor) override; const ASTString& getName() const { return *m_name; } + vecptr const& getDefinedStructs() { return m_definedStructs; } + vecptr const& getStateVariables() { return m_stateVariables; } + vecptr const& getDefinedFunctions() { return m_definedFunctions; } private: ptr m_name; vecptr m_definedStructs; @@ -105,6 +108,8 @@ public: : ASTNode(_location), m_parameters(_parameters) {} virtual void accept(ASTVisitor& _visitor) override; + + vecptr const& getParameters() { return m_parameters; } private: vecptr m_parameters; }; @@ -126,12 +131,16 @@ public: const ASTString& getName() const { return *m_name; } bool isPublic() const { return m_isPublic; } bool isDeclaredConst() const { return m_isDeclaredConst; } + vecptr const& getParameters() { return m_parameters->getParameters(); } + bool hasReturnParameters() const { return m_returnParameters.get() != nullptr; } + vecptr const& getReturnParameters() { return m_returnParameters->getParameters(); } + Block& getBody() { return *m_body; } private: ptr m_name; bool m_isPublic; ptr m_parameters; bool m_isDeclaredConst; - ptr m_returnParameters; + ptr m_returnParameters; //< either "null"pointer or pointer to non-empty parameter list ptr m_body; }; @@ -145,6 +154,7 @@ public: {} virtual void accept(ASTVisitor& _visitor) override; + TypeName* getTypeName() const { return m_type.get(); } const ASTString& getName() const { return *m_name; } private: ptr m_type; ///< can be empty ("var") @@ -416,8 +426,13 @@ public: virtual void accept(ASTVisitor& _visitor) override; ASTString const& getName() const { return *m_name; } + void setReferencedObject(ASTNode& _referencedObject) { m_referencedObject = &_referencedObject; } + ASTNode* getReferencedVariable() { return m_referencedObject; } private: ptr m_name; + + //! Node the name refers to. Has to be a declaration of some sort. + ASTNode* m_referencedObject; }; class ElementaryTypeNameExpression : public PrimaryExpression diff --git a/libsolidity/ASTForward.h b/libsolidity/ASTForward.h index b930a2238..963e3d47d 100644 --- a/libsolidity/ASTForward.h +++ b/libsolidity/ASTForward.h @@ -36,6 +36,7 @@ class FunctionCall; class MemberAccess; class IndexAccess; class PrimaryExpression; +class Identifier; class ElementaryTypeNameExpression; class Literal; diff --git a/libsolidity/ASTPrinter.cpp b/libsolidity/ASTPrinter.cpp index a5fcf959f..129696213 100644 --- a/libsolidity/ASTPrinter.cpp +++ b/libsolidity/ASTPrinter.cpp @@ -202,6 +202,13 @@ bool ASTPrinter::visit(PrimaryExpression& _node) return goDeeper(); } +bool ASTPrinter::visit(Identifier& _node) +{ + writeLine(std::string("Identifier ") + _node.getName()); + printSourcePart(_node); + return goDeeper(); +} + bool ASTPrinter::visit(ElementaryTypeNameExpression& _node) { writeLine(std::string("ElementaryTypeNameExpression ") + Token::String(_node.getType())); @@ -356,6 +363,11 @@ void ASTPrinter::endVisit(PrimaryExpression&) m_indentation--; } +void ASTPrinter::endVisit(Identifier&) +{ + m_indentation--; +} + void ASTPrinter::endVisit(ElementaryTypeNameExpression&) { m_indentation--; diff --git a/libsolidity/ASTPrinter.h b/libsolidity/ASTPrinter.h index d3cad11c2..8e908a84a 100644 --- a/libsolidity/ASTPrinter.h +++ b/libsolidity/ASTPrinter.h @@ -15,64 +15,66 @@ public: /// Output the string representation of the AST to _stream. void print(std::ostream& _stream); - bool visit(ContractDefinition& _node); - bool visit(StructDefinition& _node); - bool visit(ParameterList& _node); - bool visit(FunctionDefinition& _node); - bool visit(VariableDeclaration& _node); - bool visit(TypeName& _node); - bool visit(ElementaryTypeName& _node); - bool visit(UserDefinedTypeName& _node); - bool visit(Mapping& _node); - bool visit(Statement& _node); - bool visit(Block& _node); - bool visit(IfStatement& _node); - bool visit(BreakableStatement& _node); - bool visit(WhileStatement& _node); - bool visit(Continue& _node); - bool visit(Break& _node); - bool visit(Return& _node); - bool visit(VariableDefinition& _node); - bool visit(Expression& _node); - bool visit(Assignment& _node); - bool visit(UnaryOperation& _node); - bool visit(BinaryOperation& _node); - bool visit(FunctionCall& _node); - bool visit(MemberAccess& _node); - bool visit(IndexAccess& _node); - bool visit(PrimaryExpression& _node); - bool visit(ElementaryTypeNameExpression& _node); - bool visit(Literal& _node); + bool visit(ContractDefinition& _node) override; + bool visit(StructDefinition& _node) override; + bool visit(ParameterList& _node) override; + bool visit(FunctionDefinition& _node) override; + bool visit(VariableDeclaration& _node) override; + bool visit(TypeName& _node) override; + bool visit(ElementaryTypeName& _node) override; + bool visit(UserDefinedTypeName& _node) override; + bool visit(Mapping& _node) override; + bool visit(Statement& _node) override; + bool visit(Block& _node) override; + bool visit(IfStatement& _node) override; + bool visit(BreakableStatement& _node) override; + bool visit(WhileStatement& _node) override; + bool visit(Continue& _node) override; + bool visit(Break& _node) override; + bool visit(Return& _node) override; + bool visit(VariableDefinition& _node) override; + bool visit(Expression& _node) override; + bool visit(Assignment& _node) override; + bool visit(UnaryOperation& _node) override; + bool visit(BinaryOperation& _node) override; + bool visit(FunctionCall& _node) override; + bool visit(MemberAccess& _node) override; + bool visit(IndexAccess& _node) override; + bool visit(PrimaryExpression& _node) override; + bool visit(Identifier& _node) override; + bool visit(ElementaryTypeNameExpression& _node) override; + bool visit(Literal& _node) override; - void endVisit(ASTNode & _node); - void endVisit(ContractDefinition&); - void endVisit(StructDefinition&); - void endVisit(ParameterList&); - void endVisit(FunctionDefinition&); - void endVisit(VariableDeclaration&); - void endVisit(TypeName&); - void endVisit(ElementaryTypeName&); - void endVisit(UserDefinedTypeName&); - void endVisit(Mapping&); - void endVisit(Statement&); - void endVisit(Block&); - void endVisit(IfStatement&); - void endVisit(BreakableStatement&); - void endVisit(WhileStatement&); - void endVisit(Continue&); - void endVisit(Break&); - void endVisit(Return&); - void endVisit(VariableDefinition&); - void endVisit(Expression&); - void endVisit(Assignment&); - void endVisit(UnaryOperation&); - void endVisit(BinaryOperation&); - void endVisit(FunctionCall&); - void endVisit(MemberAccess&); - void endVisit(IndexAccess&); - void endVisit(PrimaryExpression&); - void endVisit(ElementaryTypeNameExpression&); - void endVisit(Literal&); + void endVisit(ASTNode & _node) override; + void endVisit(ContractDefinition&) override; + void endVisit(StructDefinition&) override; + void endVisit(ParameterList&) override; + void endVisit(FunctionDefinition&) override; + void endVisit(VariableDeclaration&) override; + void endVisit(TypeName&) override; + void endVisit(ElementaryTypeName&) override; + void endVisit(UserDefinedTypeName&) override; + void endVisit(Mapping&) override; + void endVisit(Statement&) override; + void endVisit(Block&) override; + void endVisit(IfStatement&) override; + void endVisit(BreakableStatement&) override; + void endVisit(WhileStatement&) override; + void endVisit(Continue&) override; + void endVisit(Break&) override; + void endVisit(Return&) override; + void endVisit(VariableDefinition&) override; + void endVisit(Expression&) override; + void endVisit(Assignment&) override; + void endVisit(UnaryOperation&) override; + void endVisit(BinaryOperation&) override; + void endVisit(FunctionCall&) override; + void endVisit(MemberAccess&) override; + void endVisit(IndexAccess&) override; + void endVisit(PrimaryExpression&) override; + void endVisit(Identifier&) override; + void endVisit(ElementaryTypeNameExpression&) override; + void endVisit(Literal&) override; private: void printSourcePart(ASTNode const& _node); diff --git a/libsolidity/ASTVisitor.h b/libsolidity/ASTVisitor.h index a68d76aee..f3ae3be71 100644 --- a/libsolidity/ASTVisitor.h +++ b/libsolidity/ASTVisitor.h @@ -39,6 +39,7 @@ public: virtual bool visit(MemberAccess&) { return true; } virtual bool visit(IndexAccess&) { return true; } virtual bool visit(PrimaryExpression&) { return true; } + virtual bool visit(Identifier&) { return true; } virtual bool visit(ElementaryTypeNameExpression&) { return true; } virtual bool visit(Literal&) { return true; } @@ -69,6 +70,7 @@ public: virtual void endVisit(MemberAccess&) { } virtual void endVisit(IndexAccess&) { } virtual void endVisit(PrimaryExpression&) { } + virtual void endVisit(Identifier&) { } virtual void endVisit(ElementaryTypeNameExpression&) { } virtual void endVisit(Literal&) { } }; diff --git a/libsolidity/NameAndTypeResolver.cpp b/libsolidity/NameAndTypeResolver.cpp new file mode 100644 index 000000000..c4f0612bd --- /dev/null +++ b/libsolidity/NameAndTypeResolver.cpp @@ -0,0 +1,142 @@ +#include + +#include +#include + +namespace dev { +namespace solidity { + + +class NameAndTypeResolver::ScopeHelper { +public: + ScopeHelper(NameAndTypeResolver& _resolver, ASTString const& _name, ASTNode& _declaration) + : m_resolver(_resolver) + { + m_resolver.registerName(_name, _declaration); + m_resolver.enterNewSubScope(_declaration); + } + ~ScopeHelper() + { + m_resolver.closeCurrentScope(); + } + +private: + NameAndTypeResolver& m_resolver; +}; + + +NameAndTypeResolver::NameAndTypeResolver() +{ +} + +void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) +{ + reset(); + + handleContract(_contract); +} + +void NameAndTypeResolver::handleContract(ContractDefinition& _contract) +{ + ScopeHelper scopeHelper(*this, _contract.getName(), _contract); + + for (ptr const& variable : _contract.getStateVariables()) + registerName(variable->getName(), *variable); + // @todo structs + + for (ptr const& function : _contract.getDefinedFunctions()) + handleFunction(*function); + + // @todo resolve names used in mappings +} + +void NameAndTypeResolver::reset() +{ + m_scopes.clear(); + m_globalScope = Scope(); + m_currentScope = &m_globalScope; +} + +void NameAndTypeResolver::handleFunction(FunctionDefinition& _function) +{ + ScopeHelper scopeHelper(*this, _function.getName(), _function); + + // @todo resolve names used in mappings + for (ptr const& variable : _function.getParameters()) + registerName(variable->getName(), *variable); + if (_function.hasReturnParameters()) + for (ptr const& variable : _function.getReturnParameters()) + registerName(variable->getName(), *variable); + handleFunctionBody(_function.getBody()); +} + +void NameAndTypeResolver::handleFunctionBody(Block& _functionBody) +{ + registerVariablesInFunction(_functionBody); + resolveReferencesInFunction(_functionBody); +} + +void NameAndTypeResolver::registerVariablesInFunction(Block& _functionBody) +{ + class VariableDeclarationFinder : public ASTVisitor { + public: + VariableDeclarationFinder(NameAndTypeResolver& _resolver) : m_resolver(_resolver) {} + virtual bool visit(VariableDeclaration& _variable) override { + m_resolver.registerName(_variable.getName(), _variable); + return false; + } + private: + NameAndTypeResolver& m_resolver; + }; + + VariableDeclarationFinder declarationFinder(*this); + _functionBody.accept(declarationFinder); +} + +void NameAndTypeResolver::resolveReferencesInFunction(Block& _functionBody) +{ + class ReferencesResolver : public ASTVisitor { + public: + ReferencesResolver(NameAndTypeResolver& _resolver) : m_resolver(_resolver) {} + virtual bool visit(Identifier& _identifier) override { + ASTNode* node = m_resolver.getNameFromCurrentScope(_identifier.getName()); + if (node == nullptr) + throw std::exception(); // @todo + _identifier.setReferencedObject(*node); + return false; + } + private: + NameAndTypeResolver& m_resolver; + }; + + ReferencesResolver referencesResolver(*this); + _functionBody.accept(referencesResolver); +} + + +void NameAndTypeResolver::registerName(ASTString const& _name, ASTNode& _declaration) +{ + if (!m_currentScope->registerName(_name, _declaration)) + throw std::exception(); // @todo +} + +ASTNode* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& _name, bool _recursive) +{ + return m_currentScope->resolveName(_name, _recursive); +} + +void NameAndTypeResolver::enterNewSubScope(ASTNode& _node) +{ + decltype(m_scopes)::iterator iter; + bool newlyAdded; + std::tie(iter, newlyAdded) = m_scopes.emplace(&_node, Scope(m_currentScope)); + BOOST_ASSERT(newlyAdded); + m_currentScope = &iter->second; +} + +void NameAndTypeResolver::closeCurrentScope() +{ + m_currentScope = m_currentScope->getOuterScope(); +} + +} } diff --git a/libsolidity/NameAndTypeResolver.h b/libsolidity/NameAndTypeResolver.h new file mode 100644 index 000000000..ca714ac26 --- /dev/null +++ b/libsolidity/NameAndTypeResolver.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include +#include + +namespace dev { +namespace solidity { + +class NameAndTypeResolver +{ +public: + NameAndTypeResolver(); + + void resolveNamesAndTypes(ContractDefinition& _contract); +private: + class ScopeHelper; //< RIIA helper to open and close scopes + + void reset(); + + void handleContract(ContractDefinition& _contract); + void handleFunction(FunctionDefinition& _function); + void handleFunctionBody(Block& _functionBody); + void registerVariablesInFunction(Block& _functionBody); + void resolveReferencesInFunction(Block& _functionBody); + + void registerName(ASTString const& _name, ASTNode& _declaration); + ASTNode* getNameFromCurrentScope(ASTString const& _name, bool _recursive = true); + + void enterNewSubScope(ASTNode& _node); + void closeCurrentScope(); + + Scope m_globalScope; // not part of the map + std::map m_scopes; + + Scope* m_currentScope; +}; + +} } diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 3ccd09300..eb171cbca 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -28,7 +28,7 @@ namespace dev { namespace solidity { -ptr Parser::parse(std::shared_ptr const& _scanner) +ptr Parser::parse(std::shared_ptr const& _scanner) { m_scanner = _scanner; @@ -132,8 +132,9 @@ ptr Parser::parseFunctionDefinition(bool _isPublic) } ptr returnParameters; if (m_scanner->getCurrentToken() == Token::RETURNS) { + const bool permitEmptyParameterList = false; m_scanner->next(); - returnParameters = parseParameterList(); + returnParameters = parseParameterList(permitEmptyParameterList); } ptr block = parseBlock(); nodeFactory.setEndPositionFromNode(block); @@ -212,13 +213,13 @@ ptr Parser::parseMapping() return nodeFactory.createNode(keyType, valueType); } -ptr Parser::parseParameterList() +ptr Parser::parseParameterList(bool _permitEmpty) { ASTNodeFactory nodeFactory(*this); vecptr parameters; expectToken(Token::LPAREN); - if (m_scanner->getCurrentToken() != Token::RPAREN) { + if (!_permitEmpty || m_scanner->getCurrentToken() != Token::RPAREN) { parameters.push_back(parseVariableDeclaration()); while (m_scanner->getCurrentToken() != Token::RPAREN) { expectToken(Token::COMMA); diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index f8940d1a6..88369052e 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -32,7 +32,7 @@ class Scanner; class Parser { public: - ptr parse(std::shared_ptr const& _scanner); + ptr parse(std::shared_ptr const& _scanner); private: class ASTNodeFactory; @@ -50,7 +50,7 @@ private: ptr parseVariableDeclaration(); ptr parseTypeName(); ptr parseMapping(); - ptr parseParameterList(); + ptr parseParameterList(bool _permitEmpty = true); ptr parseBlock(); ptr parseStatement(); ptr parseIfStatement(); diff --git a/libsolidity/Scope.h b/libsolidity/Scope.h new file mode 100644 index 000000000..c0aede949 --- /dev/null +++ b/libsolidity/Scope.h @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include + +namespace dev { +namespace solidity { + +class Scope +{ +public: + explicit Scope(Scope* _outerScope = nullptr) : m_outerScope(_outerScope) {} + /// Registers the name _name in the scope unless it is already declared. Returns true iff + /// it was not yet declared. + bool registerName(ASTString const& _name, ASTNode& _declaration) + { + if (m_declaredNames.find(_name) != m_declaredNames.end()) + return false; + m_declaredNames[_name] = &_declaration; + return true; + } + ASTNode* resolveName(ASTString const& _name, bool _recursive = false) const + { + auto result = m_declaredNames.find(_name); + if (result != m_declaredNames.end()) + return result->second; + if (_recursive && m_outerScope != nullptr) + return m_outerScope->resolveName(_name, true); + return nullptr; + } + Scope* getOuterScope() const { return m_outerScope; } + +private: + Scope* m_outerScope; + std::map m_declaredNames; +}; + +} } diff --git a/solc/main.cpp b/solc/main.cpp index 6fca11a67..a92f466ec 100644 --- a/solc/main.cpp +++ b/solc/main.cpp @@ -8,11 +8,12 @@ #include #include #include +#include namespace dev { namespace solidity { -ptr parseAST(std::string const& _source) +ptr parseAST(std::string const& _source) { ptr scanner = std::make_shared(CharStream(_source)); Parser parser; @@ -70,9 +71,12 @@ int main(int argc, char** argv) std::cout << "Parsing..." << std::endl; // @todo catch exception - dev::solidity::ptr ast = dev::solidity::parseAST(src); + dev::solidity::ptr ast = dev::solidity::parseAST(src); std::cout << "Syntax tree for the contract:" << std::endl; dev::solidity::ASTPrinter printer(ast, src); printer.print(std::cout); + std::cout << "Resolving identifiers..." << std::endl; + dev::solidity::NameAndTypeResolver resolver; + resolver.resolveNamesAndTypes(*ast.get()); return 0; } diff --git a/test/solidityNameAndTypeResolution.cpp b/test/solidityNameAndTypeResolution.cpp new file mode 100644 index 000000000..568025e5a --- /dev/null +++ b/test/solidityNameAndTypeResolution.cpp @@ -0,0 +1,113 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum 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. + + cpp-ethereum 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 cpp-ethereum. If not, see . +*/ +/** + * @author Christian + * @date 2014 + * Unit tests for the name and type resolution of the solidity parser. + */ + +#include + +#include +#include +#include +#include +#include + +namespace dev { +namespace solidity { +namespace test { + +namespace { + void parseTextAndResolveNames(const std::string& _source) + { + Parser parser; + ptr contract = parser.parse( + std::make_shared(CharStream(_source))); + NameAndTypeResolver resolver; + resolver.resolveNamesAndTypes(*contract); + } +} + +BOOST_AUTO_TEST_SUITE(SolidityNameAndTypeResolution) + +BOOST_AUTO_TEST_CASE(smoke_test) +{ + char const* text = "contract test {\n" + " uint256 stateVariable1;\n" + " function fun(uint256 arg1) { var x = 2; uint256 y = 3; x = 1; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(double_stateVariable_declaration) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " uint128 variable;\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), std::exception); +} + +BOOST_AUTO_TEST_CASE(double_function_declaration) +{ + char const* text = "contract test {\n" + " function fun() { var x = 2; }\n" + " function fun() { var y = 9; }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), std::exception); +} + +BOOST_AUTO_TEST_CASE(double_variable_declaration) +{ + char const* text = "contract test {\n" + " function f() { uint256 x = 9; if (true) { uint256 x = 2;} x = 3; }\n" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), std::exception); +} + +BOOST_AUTO_TEST_CASE(name_shadowing) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f() { uint8 variable = 2; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(name_references) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f() { variable = 2; f(); test; }" + "}\n"; + BOOST_CHECK_NO_THROW(parseTextAndResolveNames(text)); +} + +BOOST_AUTO_TEST_CASE(undeclared_name) +{ + char const* text = "contract test {\n" + " uint256 variable;\n" + " function f() { notfound = 2; }" + "}\n"; + BOOST_CHECK_THROW(parseTextAndResolveNames(text), std::exception); +} + +BOOST_AUTO_TEST_SUITE_END() + +} } } // end namespaces +