12 changed files with 437 additions and 66 deletions
@ -0,0 +1,142 @@ |
|||||
|
#include <libsolidity/NameAndTypeResolver.h> |
||||
|
|
||||
|
#include <libsolidity/AST.h> |
||||
|
#include <boost/assert.hpp> |
||||
|
|
||||
|
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<VariableDeclaration> const& variable : _contract.getStateVariables()) |
||||
|
registerName(variable->getName(), *variable); |
||||
|
// @todo structs
|
||||
|
|
||||
|
for (ptr<FunctionDefinition> 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<VariableDeclaration> const& variable : _function.getParameters()) |
||||
|
registerName(variable->getName(), *variable); |
||||
|
if (_function.hasReturnParameters()) |
||||
|
for (ptr<VariableDeclaration> 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(); |
||||
|
} |
||||
|
|
||||
|
} } |
@ -0,0 +1,40 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
|
||||
|
#include <libsolidity/Scope.h> |
||||
|
#include <libsolidity/ASTVisitor.h> |
||||
|
|
||||
|
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<ASTNode*, Scope> m_scopes; |
||||
|
|
||||
|
Scope* m_currentScope; |
||||
|
}; |
||||
|
|
||||
|
} } |
@ -0,0 +1,39 @@ |
|||||
|
#pragma once |
||||
|
|
||||
|
#include <map> |
||||
|
|
||||
|
#include <libsolidity/ASTForward.h> |
||||
|
|
||||
|
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<ASTString, ASTNode*> m_declaredNames; |
||||
|
}; |
||||
|
|
||||
|
} } |
@ -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 <http://www.gnu.org/licenses/>.
|
||||
|
*/ |
||||
|
/**
|
||||
|
* @author Christian <c@ethdev.com> |
||||
|
* @date 2014 |
||||
|
* Unit tests for the name and type resolution of the solidity parser. |
||||
|
*/ |
||||
|
|
||||
|
#include <string> |
||||
|
|
||||
|
#include <libdevcore/Log.h> |
||||
|
#include <libsolidity/Scanner.h> |
||||
|
#include <libsolidity/Parser.h> |
||||
|
#include <libsolidity/NameAndTypeResolver.h> |
||||
|
#include <boost/test/unit_test.hpp> |
||||
|
|
||||
|
namespace dev { |
||||
|
namespace solidity { |
||||
|
namespace test { |
||||
|
|
||||
|
namespace { |
||||
|
void parseTextAndResolveNames(const std::string& _source) |
||||
|
{ |
||||
|
Parser parser; |
||||
|
ptr<ContractDefinition> contract = parser.parse( |
||||
|
std::make_shared<Scanner>(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
|
||||
|
|
Loading…
Reference in new issue