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