diff --git a/AST.h b/AST.h index e47615368..b3fd840a0 100755 --- a/AST.h +++ b/AST.h @@ -173,6 +173,7 @@ public: virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; + std::vector> const& getBaseContracts() const { return m_baseContracts; } std::vector> const& getDefinedStructs() const { return m_definedStructs; } std::vector> const& getStateVariables() const { return m_stateVariables; } std::vector> const& getDefinedFunctions() const { return m_definedFunctions; } @@ -189,6 +190,11 @@ public: /// as intended for use by the ABI. std::map, FunctionDefinition const*> getInterfaceFunctions() const; + /// List of all (direct and indirect) base contracts in order from derived to base, including + /// the contract itself. Available after name resolution + std::vector const& getLinearizedBaseContracts() const { return m_linearizedBaseContracts; } + void setLinearizedBaseContracts(std::vector const& _bases) { m_linearizedBaseContracts = _bases; } + /// Returns the constructor or nullptr if no constructor was specified FunctionDefinition const* getConstructor() const; @@ -200,6 +206,8 @@ private: std::vector> m_stateVariables; std::vector> m_definedFunctions; ASTPointer m_documentation; + + std::vector m_linearizedBaseContracts; }; class StructDefinition: public Declaration diff --git a/DeclarationContainer.h b/DeclarationContainer.h index c0a0b42c7..e4b793259 100644 --- a/DeclarationContainer.h +++ b/DeclarationContainer.h @@ -42,11 +42,12 @@ public: explicit DeclarationContainer(Declaration const* _enclosingDeclaration = nullptr, DeclarationContainer const* _enclosingContainer = nullptr): m_enclosingDeclaration(_enclosingDeclaration), m_enclosingContainer(_enclosingContainer) {} - /// Registers the declaration in the scope unless its name is already declared. Returns true iff - /// it was not yet declared. + /// Registers the declaration in the scope unless its name is already declared. + /// @returns true iff it was not yet declared. bool registerDeclaration(Declaration const& _declaration, bool _update = false); Declaration const* resolveName(ASTString const& _name, bool _recursive = false) const; Declaration const* getEnclosingDeclaration() const { return m_enclosingDeclaration; } + std::map const& getDeclarations() const { return m_declarations; } private: Declaration const* m_enclosingDeclaration; diff --git a/NameAndTypeResolver.cpp b/NameAndTypeResolver.cpp index 3774537d1..2a73f21be 100644 --- a/NameAndTypeResolver.cpp +++ b/NameAndTypeResolver.cpp @@ -46,7 +46,17 @@ void NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) { + m_currentScope = &m_scopes[nullptr]; + + for (ASTPointer const& baseContract: _contract.getBaseContracts()) + ReferencesResolver resolver(*baseContract, *this, nullptr); + m_currentScope = &m_scopes[&_contract]; + + linearizeBaseContracts(_contract); + for (ContractDefinition const* base: _contract.getLinearizedBaseContracts()) + importInheritedScope(*base); + for (ASTPointer const& structDef: _contract.getDefinedStructs()) ReferencesResolver resolver(*structDef, *this, nullptr); for (ASTPointer const& variable: _contract.getStateVariables()) @@ -57,7 +67,6 @@ void NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) ReferencesResolver referencesResolver(*function, *this, function->getReturnParameterList().get()); } - m_currentScope = &m_scopes[nullptr]; } void NameAndTypeResolver::checkTypeRequirements(ContractDefinition& _contract) @@ -86,6 +95,94 @@ Declaration const* NameAndTypeResolver::getNameFromCurrentScope(ASTString const& return m_currentScope->resolveName(_name, _recursive); } +void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) +{ + auto iterator = m_scopes.find(&_base); + solAssert(iterator != end(m_scopes), ""); + for (auto const& nameAndDeclaration: iterator->second.getDeclarations()) + { + Declaration const* declaration = nameAndDeclaration.second; + if (declaration->getScope() == &_base) + m_currentScope->registerDeclaration(*declaration); + } +} + +void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) const +{ + // order in the lists is from derived to base + // list of lists to linearize, the last element is the list of direct bases + list> input(1, {&_contract}); + for (ASTPointer const& baseIdentifier: _contract.getBaseContracts()) + { + ContractDefinition const* base = dynamic_cast( + baseIdentifier->getReferencedDeclaration()); + if (!base) + BOOST_THROW_EXCEPTION(baseIdentifier->createTypeError("Contract expected.")); + // "push_back" has the effect that bases mentioned earlier can overwrite members of bases + // mentioned later + input.back().push_back(base); + vector const& basesBases = base->getLinearizedBaseContracts(); + if (basesBases.empty()) + BOOST_THROW_EXCEPTION(baseIdentifier->createTypeError("Definition of base has to precede definition of derived contract")); + input.push_front(list(basesBases.begin(), basesBases.end())); + } + vector result = cThreeMerge(input); + if (result.empty()) + BOOST_THROW_EXCEPTION(_contract.createTypeError("Linearization of inheritance graph impossible")); + _contract.setLinearizedBaseContracts(result); +} + +template +vector<_T const*> NameAndTypeResolver::cThreeMerge(list>& _toMerge) +{ + // returns true iff _candidate appears only as last element of the lists + auto appearsOnlyAtHead = [&](_T const* _candidate) -> bool + { + for (list<_T const*> const& bases: _toMerge) + { + solAssert(!bases.empty(), ""); + if (find(++bases.begin(), bases.end(), _candidate) != bases.end()) + return false; + } + return true; + }; + // returns the next candidate to append to the linearized list or nullptr on failure + auto nextCandidate = [&]() -> _T const* + { + for (list<_T const*> const& bases: _toMerge) + { + solAssert(!bases.empty(), ""); + if (appearsOnlyAtHead(bases.front())) + return bases.front(); + } + return nullptr; + }; + // removes the given contract from all lists + auto removeCandidate = [&](_T const* _candidate) + { + for (auto it = _toMerge.begin(); it != _toMerge.end();) + { + it->remove(_candidate); + if (it->empty()) + it = _toMerge.erase(it); + else + ++it; + } + }; + + _toMerge.remove_if([](list<_T const*> const& _bases) { return _bases.empty(); }); + vector<_T const*> result; + while (!_toMerge.empty()) + { + _T const* candidate = nextCandidate(); + if (!candidate) + return vector<_T const*>(); + result.push_back(candidate); + removeCandidate(candidate); + } + return result; +} + DeclarationRegistrationHelper::DeclarationRegistrationHelper(map& _scopes, ASTNode& _astRoot): m_scopes(_scopes), m_currentScope(nullptr) diff --git a/NameAndTypeResolver.h b/NameAndTypeResolver.h index 1032a87cf..4fb67da34 100644 --- a/NameAndTypeResolver.h +++ b/NameAndTypeResolver.h @@ -23,6 +23,7 @@ #pragma once #include +#include #include #include @@ -64,6 +65,17 @@ public: private: void reset(); + /// Imports all members declared directly in the given contract (i.e. does not import inherited + /// members) into the current scope if they are not present already. + void importInheritedScope(ContractDefinition const& _base); + + /// Computes "C3-Linearization" of base contracts and stores it inside the contract. + void linearizeBaseContracts(ContractDefinition& _contract) const; + /// Computes the C3-merge of the given list of lists of bases. + /// @returns the linearized vector or an empty vector if linearization is not possible. + template + static std::vector<_T const*> cThreeMerge(std::list>& _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.