Source units are independent scopes.

This commit is contained in:
chriseth 2015-12-05 03:09:47 +01:00
parent e510e7e792
commit 7cb7818cea
9 changed files with 204 additions and 55 deletions

View File

@ -28,15 +28,19 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::solidity; using namespace dev::solidity;
Declaration const* DeclarationContainer::conflictingDeclaration(Declaration const& _declaration) const Declaration const* DeclarationContainer::conflictingDeclaration(
Declaration const& _declaration,
ASTString const* _name
) const
{ {
ASTString const& declarationName(_declaration.name()); if (!_name)
solAssert(!declarationName.empty(), ""); _name = &_declaration.name();
solAssert(!_name->empty(), "");
vector<Declaration const*> declarations; vector<Declaration const*> declarations;
if (m_declarations.count(declarationName)) if (m_declarations.count(*_name))
declarations += m_declarations.at(declarationName); declarations += m_declarations.at(*_name);
if (m_invisibleDeclarations.count(declarationName)) if (m_invisibleDeclarations.count(*_name))
declarations += m_invisibleDeclarations.at(declarationName); declarations += m_invisibleDeclarations.at(*_name);
if (dynamic_cast<FunctionDefinition const*>(&_declaration)) if (dynamic_cast<FunctionDefinition const*>(&_declaration))
{ {
@ -51,25 +55,31 @@ Declaration const* DeclarationContainer::conflictingDeclaration(Declaration cons
return nullptr; return nullptr;
} }
bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update) bool DeclarationContainer::registerDeclaration(
Declaration const& _declaration,
ASTString const* _name,
bool _invisible,
bool _update
)
{ {
ASTString const& declarationName(_declaration.name()); if (!_name)
if (declarationName.empty()) _name = &_declaration.name();
if (_name->empty())
return true; return true;
if (_update) if (_update)
{ {
solAssert(!dynamic_cast<FunctionDefinition const*>(&_declaration), "Attempt to update function definition."); solAssert(!dynamic_cast<FunctionDefinition const*>(&_declaration), "Attempt to update function definition.");
m_declarations.erase(declarationName); m_declarations.erase(*_name);
m_invisibleDeclarations.erase(declarationName); m_invisibleDeclarations.erase(*_name);
} }
else if (conflictingDeclaration(_declaration)) else if (conflictingDeclaration(_declaration))
return false; return false;
if (_invisible) if (_invisible)
m_invisibleDeclarations[declarationName].push_back(&_declaration); m_invisibleDeclarations[*_name].push_back(&_declaration);
else else
m_declarations[declarationName].push_back(&_declaration); m_declarations[*_name].push_back(&_declaration);
return true; return true;
} }

View File

@ -41,23 +41,24 @@ class DeclarationContainer
{ {
public: public:
explicit DeclarationContainer( explicit DeclarationContainer(
Declaration const* _enclosingDeclaration = nullptr, ASTNode const* _enclosingNode = nullptr,
DeclarationContainer const* _enclosingContainer = nullptr DeclarationContainer const* _enclosingContainer = nullptr
): ):
m_enclosingDeclaration(_enclosingDeclaration), m_enclosingContainer(_enclosingContainer) {} m_enclosingNode(_enclosingNode), m_enclosingContainer(_enclosingContainer) {}
/// Registers the declaration in the scope unless its name is already declared or the name is empty. /// Registers the declaration in the scope unless its name is already declared or the name is empty.
/// @param _name the name to register, if nullptr the intrinsic name of @a _declaration is used.
/// @param _invisible if true, registers the declaration, reports name clashes but does not return it in @a resolveName /// @param _invisible if true, registers the declaration, reports name clashes but does not return it in @a resolveName
/// @param _update if true, replaces a potential declaration that is already present /// @param _update if true, replaces a potential declaration that is already present
/// @returns false if the name was already declared. /// @returns false if the name was already declared.
bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false); bool registerDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr, bool _invisible = false, bool _update = false);
std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const; std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const;
Declaration const* enclosingDeclaration() const { return m_enclosingDeclaration; } ASTNode const* enclosingNode() const { return m_enclosingNode; }
std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; } std::map<ASTString, std::vector<Declaration const*>> const& declarations() const { return m_declarations; }
/// @returns whether declaration is valid, and if not also returns previous declaration. /// @returns whether declaration is valid, and if not also returns previous declaration.
Declaration const* conflictingDeclaration(Declaration const& _declaration) const; Declaration const* conflictingDeclaration(Declaration const& _declaration, ASTString const* _name = nullptr) const;
private: private:
Declaration const* m_enclosingDeclaration; ASTNode const* m_enclosingNode;
DeclarationContainer const* m_enclosingContainer; DeclarationContainer const* m_enclosingContainer;
std::map<ASTString, std::vector<Declaration const*>> m_declarations; std::map<ASTString, std::vector<Declaration const*>> m_declarations;
std::map<ASTString, std::vector<Declaration const*>> m_invisibleDeclarations; std::map<ASTString, std::vector<Declaration const*>> m_invisibleDeclarations;

View File

@ -38,6 +38,7 @@ NameAndTypeResolver::NameAndTypeResolver(
) : ) :
m_errors(_errors) m_errors(_errors)
{ {
if (!m_scopes[nullptr])
m_scopes[nullptr].reset(new DeclarationContainer()); m_scopes[nullptr].reset(new DeclarationContainer());
for (Declaration const* declaration: _globals) for (Declaration const* declaration: _globals)
m_scopes[nullptr]->registerDeclaration(*declaration); m_scopes[nullptr]->registerDeclaration(*declaration);
@ -45,6 +46,10 @@ NameAndTypeResolver::NameAndTypeResolver(
bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit)
{ {
solAssert(!m_scopes[&_sourceUnit], "");
m_scopes[&_sourceUnit].reset(new DeclarationContainer(nullptr, m_scopes[nullptr].get()));
m_currentScope = m_scopes[&_sourceUnit].get();
// The helper registers all declarations in m_scopes as a side-effect of its construction. // The helper registers all declarations in m_scopes as a side-effect of its construction.
try try
{ {
@ -59,11 +64,37 @@ bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit)
return true; return true;
} }
bool NameAndTypeResolver::performImports(SourceUnit& _sourceUnit, map<string, SourceUnit const*> const& _sourceUnits)
{
DeclarationContainer& target = *m_scopes.at(&_sourceUnit);
bool error = false;
for (auto const& node: _sourceUnit.nodes())
if (auto imp = dynamic_cast<ImportDirective const*>(node.get()))
{
if (!_sourceUnits.count(imp->identifier()))
{
reportDeclarationError(node->location(), "Import \"" + imp->identifier() + "\" not found.");
error = true;
}
else
{
auto scope = m_scopes.find(_sourceUnits.at(imp->identifier()));
solAssert(scope != end(m_scopes), "");
for (auto const& nameAndDeclaration: scope->second->declarations())
for (auto const& declaration: nameAndDeclaration.second)
target.registerDeclaration(*declaration, &nameAndDeclaration.first);
}
}
return !error;
}
bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract)
{ {
try try
{ {
m_currentScope = m_scopes[nullptr].get(); m_currentScope = m_scopes[_contract.scope()].get();
solAssert(!!m_currentScope, "");
ReferencesResolver resolver(m_errors, *this, nullptr); ReferencesResolver resolver(m_errors, *this, nullptr);
bool success = true; bool success = true;
@ -134,7 +165,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
{ {
try try
{ {
m_scopes[nullptr]->registerDeclaration(_declaration, false, true); m_scopes[nullptr]->registerDeclaration(_declaration, nullptr, false, true);
solAssert(_declaration.scope() == nullptr, "Updated declaration outside global scope."); solAssert(_declaration.scope() == nullptr, "Updated declaration outside global scope.");
} }
catch (FatalError const&) catch (FatalError const&)
@ -146,7 +177,7 @@ bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration)
return true; return true;
} }
vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, ASTNode const* _scope) const
{ {
auto iterator = m_scopes.find(_scope); auto iterator = m_scopes.find(_scope);
if (iterator == end(m_scopes)) if (iterator == end(m_scopes))
@ -348,9 +379,10 @@ DeclarationRegistrationHelper::DeclarationRegistrationHelper(
ErrorList& _errors ErrorList& _errors
): ):
m_scopes(_scopes), m_scopes(_scopes),
m_currentScope(nullptr), m_currentScope(&_astRoot),
m_errors(_errors) m_errors(_errors)
{ {
solAssert(!!m_scopes.at(m_currentScope), "");
_astRoot.accept(*this); _astRoot.accept(*this);
} }
@ -462,12 +494,12 @@ void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declara
void DeclarationRegistrationHelper::closeCurrentScope() void DeclarationRegistrationHelper::closeCurrentScope()
{ {
solAssert(m_currentScope, "Closed non-existing scope."); solAssert(m_currentScope, "Closed non-existing scope.");
m_currentScope = m_scopes[m_currentScope]->enclosingDeclaration(); m_currentScope = m_scopes[m_currentScope]->enclosingNode();
} }
void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope)
{ {
if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, !_declaration.isVisibleInContract())) if (!m_scopes[m_currentScope]->registerDeclaration(_declaration, nullptr, !_declaration.isVisibleInContract()))
{ {
SourceLocation firstDeclarationLocation; SourceLocation firstDeclarationLocation;
SourceLocation secondDeclarationLocation; SourceLocation secondDeclarationLocation;
@ -502,14 +534,17 @@ string DeclarationRegistrationHelper::currentCanonicalName() const
{ {
string ret; string ret;
for ( for (
Declaration const* scope = m_currentScope; ASTNode const* scope = m_currentScope;
scope != nullptr; scope != nullptr;
scope = m_scopes[scope]->enclosingDeclaration() scope = m_scopes[scope]->enclosingNode()
) )
{
if (auto decl = dynamic_cast<Declaration const*>(scope))
{ {
if (!ret.empty()) if (!ret.empty())
ret = "." + ret; ret = "." + ret;
ret = scope->name() + ret; ret = decl->name() + ret;
}
} }
return ret; return ret;
} }

View File

@ -46,6 +46,8 @@ public:
/// Registers all declarations found in the source unit. /// Registers all declarations found in the source unit.
/// @returns false in case of error. /// @returns false in case of error.
bool registerDeclarations(SourceUnit& _sourceUnit); bool registerDeclarations(SourceUnit& _sourceUnit);
/// Applies the effect of import directives.
bool performImports(SourceUnit& _sourceUnit, std::map<std::string, SourceUnit const*> const& _sourceUnits);
/// Resolves all names and types referenced from the given contract. /// Resolves all names and types referenced from the given contract.
/// @returns false in case of error. /// @returns false in case of error.
bool resolveNamesAndTypes(ContractDefinition& _contract); bool resolveNamesAndTypes(ContractDefinition& _contract);
@ -55,9 +57,9 @@ public:
bool updateDeclaration(Declaration const& _declaration); bool updateDeclaration(Declaration const& _declaration);
/// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted, /// Resolves the given @a _name inside the scope @a _scope. If @a _scope is omitted,
/// the global scope is used (i.e. the one containing only the contract). /// the global scope is used (i.e. the one containing only the pre-defined global variables).
/// @returns a pointer to the declaration on success or nullptr on failure. /// @returns a pointer to the declaration on success or nullptr on failure.
std::vector<Declaration const*> resolveName(ASTString const& _name, Declaration const* _scope = nullptr) const; std::vector<Declaration const*> resolveName(ASTString const& _name, ASTNode const* _scope = nullptr) const;
/// Resolves a name in the "current" scope. Should only be called during the initial /// Resolves a name in the "current" scope. Should only be called during the initial
/// resolving phase. /// resolving phase.
@ -88,11 +90,6 @@ private:
template <class _T> template <class _T>
static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge); static std::vector<_T const*> cThreeMerge(std::list<std::list<_T const*>>& _toMerge);
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
std::map<ASTNode const*, std::unique_ptr<DeclarationContainer>> m_scopes;
// creates the Declaration error and adds it in the errors list // creates the Declaration error and adds it in the errors list
void reportDeclarationError( void reportDeclarationError(
SourceLocation _sourceLoction, SourceLocation _sourceLoction,
@ -110,6 +107,12 @@ private:
// creates the Declaration error and adds it in the errors list and throws FatalError // creates the Declaration error and adds it in the errors list and throws FatalError
void reportFatalTypeError(Error const& _e); void reportFatalTypeError(Error const& _e);
/// Maps nodes declaring a scope to scopes, i.e. ContractDefinition and FunctionDeclaration,
/// where nullptr denotes the global scope. Note that structs are not scope since they do
/// not contain code.
std::map<ASTNode const*, std::unique_ptr<DeclarationContainer>> m_scopes;
DeclarationContainer* m_currentScope = nullptr; DeclarationContainer* m_currentScope = nullptr;
ErrorList& m_errors; ErrorList& m_errors;
}; };
@ -164,7 +167,7 @@ private:
void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description); void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description);
std::map<ASTNode const*, std::unique_ptr<DeclarationContainer>>& m_scopes; std::map<ASTNode const*, std::unique_ptr<DeclarationContainer>>& m_scopes;
Declaration const* m_currentScope = nullptr; ASTNode const* m_currentScope = nullptr;
VariableScope* m_currentFunction = nullptr; VariableScope* m_currentFunction = nullptr;
ErrorList& m_errors; ErrorList& m_errors;
}; };

View File

@ -139,7 +139,9 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
bool isPointer = true; bool isPointer = true;
if (_variable.isExternalCallableParameter()) if (_variable.isExternalCallableParameter())
{ {
auto const& contract = dynamic_cast<ContractDefinition const&>(*_variable.scope()->scope()); auto const& contract = dynamic_cast<ContractDefinition const&>(
*dynamic_cast<Declaration const&>(*_variable.scope()).scope()
);
if (contract.isLibrary()) if (contract.isLibrary())
{ {
if (varLoc == Location::Memory) if (varLoc == Location::Memory)
@ -162,9 +164,11 @@ void ReferencesResolver::endVisit(VariableDeclaration const& _variable)
else else
typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage; typeLoc = varLoc == Location::Memory ? DataLocation::Memory : DataLocation::Storage;
} }
else if (_variable.isCallableParameter() && _variable.scope()->isPublic()) else if (_variable.isCallableParameter() && dynamic_cast<Declaration const&>(*_variable.scope()).isPublic())
{ {
auto const& contract = dynamic_cast<ContractDefinition const&>(*_variable.scope()->scope()); auto const& contract = dynamic_cast<ContractDefinition const&>(
*dynamic_cast<Declaration const&>(*_variable.scope()).scope()
);
// force locations of public or external function (return) parameters to memory // force locations of public or external function (return) parameters to memory
if (varLoc == Location::Storage && !contract.isLibrary()) if (varLoc == Location::Storage && !contract.isLibrary())
fatalTypeError(_variable.location(), fatalTypeError(_variable.location(),

View File

@ -172,8 +172,8 @@ public:
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope. /// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step. /// Available only after name and type resolution step.
Declaration const* scope() const { return m_scope; } ASTNode const* scope() const { return m_scope; }
void setScope(Declaration const* _scope) { m_scope = _scope; } void setScope(ASTNode const* _scope) { m_scope = _scope; }
virtual bool isLValue() const { return false; } virtual bool isLValue() const { return false; }
virtual bool isPartOfExternalInterface() const { return false; } virtual bool isPartOfExternalInterface() const { return false; }
@ -190,7 +190,7 @@ protected:
private: private:
ASTPointer<ASTString> m_name; ASTPointer<ASTString> m_name;
Visibility m_visibility; Visibility m_visibility;
Declaration const* m_scope; ASTNode const* m_scope;
}; };
/** /**

View File

@ -103,12 +103,14 @@ bool CompilerStack::parse()
m_errors.clear(); m_errors.clear();
m_parseSuccessful = false; m_parseSuccessful = false;
map<string, SourceUnit const*> sourceUnitsByName;
for (auto& sourcePair: m_sources) for (auto& sourcePair: m_sources)
{ {
sourcePair.second.scanner->reset(); sourcePair.second.scanner->reset();
sourcePair.second.ast = Parser(m_errors).parse(sourcePair.second.scanner); sourcePair.second.ast = Parser(m_errors).parse(sourcePair.second.scanner);
if (!sourcePair.second.ast) if (!sourcePair.second.ast)
solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error."); solAssert(!Error::containsOnlyWarnings(m_errors), "Parser returned null but did not report error.");
sourceUnitsByName[sourcePair.first] = sourcePair.second.ast.get();
} }
if (!Error::containsOnlyWarnings(m_errors)) if (!Error::containsOnlyWarnings(m_errors))
// errors while parsing. sould stop before type checking // errors while parsing. sould stop before type checking
@ -128,6 +130,10 @@ bool CompilerStack::parse()
if (!resolver.registerDeclarations(*source->ast)) if (!resolver.registerDeclarations(*source->ast))
return false; return false;
for (Source const* source: m_sourceOrder)
if (!resolver.performImports(*source->ast, sourceUnitsByName))
return false;
for (Source const* source: m_sourceOrder) for (Source const* source: m_sourceOrder)
for (ASTPointer<ASTNode> const& node: source->ast->nodes()) for (ASTPointer<ASTNode> const& node: source->ast->nodes())
if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get())) if (ContractDefinition* contract = dynamic_cast<ContractDefinition*>(node.get()))

View File

@ -0,0 +1,83 @@
/*
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 2015
* Tests for high level features like import.
*/
#include <string>
#include <boost/test/unit_test.hpp>
#include <libsolidity/interface/Exceptions.h>
#include <libsolidity/interface/CompilerStack.h>
using namespace std;
namespace dev
{
namespace solidity
{
namespace test
{
BOOST_AUTO_TEST_SUITE(SolidityImports)
BOOST_AUTO_TEST_CASE(smoke_test)
{
CompilerStack c;
c.addSource("a", "contract C {}");
BOOST_CHECK(c.compile());
}
BOOST_AUTO_TEST_CASE(regular_import)
{
CompilerStack c;
c.addSource("a", "contract C {}");
c.addSource("b", "import \"a\"; contract D is C {}");
BOOST_CHECK(c.compile());
}
BOOST_AUTO_TEST_CASE(import_does_not_clutter_importee)
{
CompilerStack c;
c.addSource("a", "contract C { D d; }");
c.addSource("b", "import \"a\"; contract D is C {}");
BOOST_CHECK(!c.compile());
}
BOOST_AUTO_TEST_CASE(import_is_transitive)
{
CompilerStack c;
c.addSource("a", "contract C { }");
c.addSource("b", "import \"a\";");
c.addSource("c", "import \"b\"; contract D is C {}");
BOOST_CHECK(c.compile());
}
BOOST_AUTO_TEST_CASE(circular_import)
{
CompilerStack c;
c.addSource("a", "import \"b\"; contract C { D d; }");
c.addSource("b", "import \"a\"; contract D { C c; }");
BOOST_CHECK(c.compile());
}
BOOST_AUTO_TEST_SUITE_END()
}
}
} // end namespaces

View File

@ -71,18 +71,21 @@ private:
}; };
Declaration const& resolveDeclaration( Declaration const& resolveDeclaration(
vector<string> const& _namespacedName, NameAndTypeResolver const& _resolver) SourceUnit const& _sourceUnit,
vector<string> const& _namespacedName,
NameAndTypeResolver const& _resolver
)
{ {
Declaration const* declaration = nullptr; ASTNode const* scope = &_sourceUnit;
// bracers are required, cause msvc couldnt handle this macro in for statement // bracers are required, cause msvc couldnt handle this macro in for statement
for (string const& namePart: _namespacedName) for (string const& namePart: _namespacedName)
{ {
auto declarations = _resolver.resolveName(namePart, declaration); auto declarations = _resolver.resolveName(namePart, scope);
BOOST_REQUIRE(!declarations.empty()); BOOST_REQUIRE(!declarations.empty());
BOOST_REQUIRE(declaration = *declarations.begin()); BOOST_REQUIRE(scope = *declarations.begin());
} }
BOOST_REQUIRE(declaration); BOOST_REQUIRE(scope);
return *declaration; return dynamic_cast<Declaration const&>(*scope);
} }
bytes compileFirstExpression( bytes compileFirstExpression(
@ -140,13 +143,17 @@ bytes compileFirstExpression(
unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack unsigned parametersSize = _localVariables.size(); // assume they are all one slot on the stack
context.adjustStackOffset(parametersSize); context.adjustStackOffset(parametersSize);
for (vector<string> const& variable: _localVariables) for (vector<string> const& variable: _localVariables)
context.addVariable(dynamic_cast<VariableDeclaration const&>(resolveDeclaration(variable, resolver)), context.addVariable(
parametersSize--); dynamic_cast<VariableDeclaration const&>(resolveDeclaration(*sourceUnit, variable, resolver)),
parametersSize--
);
ExpressionCompiler(context).compile(*extractor.expression()); ExpressionCompiler(context).compile(*extractor.expression());
for (vector<string> const& function: _functions) for (vector<string> const& function: _functions)
context << context.functionEntryLabel(dynamic_cast<FunctionDefinition const&>(resolveDeclaration(function, resolver))); context << context.functionEntryLabel(dynamic_cast<FunctionDefinition const&>(
resolveDeclaration(*sourceUnit, function, resolver)
));
bytes instructions = context.assembledObject().bytecode; bytes instructions = context.assembledObject().bytecode;
// debug // debug
// cout << eth::disassemble(instructions) << endl; // cout << eth::disassemble(instructions) << endl;