diff options
author | chriseth <c@ethdev.com> | 2015-10-22 19:58:50 +0800 |
---|---|---|
committer | chriseth <c@ethdev.com> | 2015-10-22 19:58:50 +0800 |
commit | cb8f6633b237dab7d54904e00fed111c176a812c (patch) | |
tree | b2df13e7c4c16c01b6cdc7cd5c15932031185d95 /libsolidity/analysis | |
parent | d41f8b7ce702c3b25c48d27e2e895ccdcd04e4e0 (diff) | |
parent | e3dffb611fe1736e3ffa170e6d8dc4dee17366bd (diff) | |
download | dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar.gz dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar.bz2 dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar.lz dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar.xz dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.tar.zst dexon-solidity-cb8f6633b237dab7d54904e00fed111c176a812c.zip |
Merge pull request #163 from chriseth/fileReorg
File reorganisation.
Diffstat (limited to 'libsolidity/analysis')
-rw-r--r-- | libsolidity/analysis/ConstantEvaluator.cpp | 59 | ||||
-rw-r--r-- | libsolidity/analysis/ConstantEvaluator.h | 50 | ||||
-rw-r--r-- | libsolidity/analysis/DeclarationContainer.cpp | 85 | ||||
-rw-r--r-- | libsolidity/analysis/DeclarationContainer.h | 67 | ||||
-rw-r--r-- | libsolidity/analysis/GlobalContext.cpp | 96 | ||||
-rw-r--r-- | libsolidity/analysis/GlobalContext.h | 64 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.cpp | 555 | ||||
-rw-r--r-- | libsolidity/analysis/NameAndTypeResolver.h | 169 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.cpp | 234 | ||||
-rw-r--r-- | libsolidity/analysis/ReferencesResolver.h | 69 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.cpp | 1328 | ||||
-rw-r--r-- | libsolidity/analysis/TypeChecker.h | 122 |
12 files changed, 2898 insertions, 0 deletions
diff --git a/libsolidity/analysis/ConstantEvaluator.cpp b/libsolidity/analysis/ConstantEvaluator.cpp new file mode 100644 index 00000000..6beb655e --- /dev/null +++ b/libsolidity/analysis/ConstantEvaluator.cpp @@ -0,0 +1,59 @@ +/* + 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 + * Evaluator for types of constant expressions. + */ + +#include <libsolidity/analysis/ConstantEvaluator.h> +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +void ConstantEvaluator::endVisit(UnaryOperation const& _operation) +{ + TypePointer const& subType = _operation.subExpression().annotation().type; + if (!dynamic_cast<IntegerConstantType const*>(subType.get())) + BOOST_THROW_EXCEPTION(_operation.subExpression().createTypeError("Invalid constant expression.")); + TypePointer t = subType->unaryOperatorResult(_operation.getOperator()); + _operation.annotation().type = t; +} + +void ConstantEvaluator::endVisit(BinaryOperation const& _operation) +{ + TypePointer const& leftType = _operation.leftExpression().annotation().type; + TypePointer const& rightType = _operation.rightExpression().annotation().type; + if (!dynamic_cast<IntegerConstantType const*>(leftType.get())) + BOOST_THROW_EXCEPTION(_operation.leftExpression().createTypeError("Invalid constant expression.")); + if (!dynamic_cast<IntegerConstantType const*>(rightType.get())) + BOOST_THROW_EXCEPTION(_operation.rightExpression().createTypeError("Invalid constant expression.")); + TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + if (Token::isCompareOp(_operation.getOperator())) + commonType = make_shared<BoolType>(); + _operation.annotation().type = commonType; +} + +void ConstantEvaluator::endVisit(Literal const& _literal) +{ + _literal.annotation().type = Type::forLiteral(_literal); + if (!_literal.annotation().type) + BOOST_THROW_EXCEPTION(_literal.createTypeError("Invalid literal value.")); +} diff --git a/libsolidity/analysis/ConstantEvaluator.h b/libsolidity/analysis/ConstantEvaluator.h new file mode 100644 index 00000000..f311efbf --- /dev/null +++ b/libsolidity/analysis/ConstantEvaluator.h @@ -0,0 +1,50 @@ +/* + 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 + * Evaluator for types of constant expressions. + */ + +#pragma once + +#include <libsolidity/ast/ASTVisitor.h> + +namespace dev +{ +namespace solidity +{ + +class TypeChecker; + +/** + * Small drop-in replacement for TypeChecker to evaluate simple expressions of integer constants. + */ +class ConstantEvaluator: private ASTConstVisitor +{ +public: + ConstantEvaluator(Expression const& _expr) { _expr.accept(*this); } + +private: + virtual void endVisit(BinaryOperation const& _operation); + virtual void endVisit(UnaryOperation const& _operation); + virtual void endVisit(Literal const& _literal); + +}; + +} +} diff --git a/libsolidity/analysis/DeclarationContainer.cpp b/libsolidity/analysis/DeclarationContainer.cpp new file mode 100644 index 00000000..7339ad5d --- /dev/null +++ b/libsolidity/analysis/DeclarationContainer.cpp @@ -0,0 +1,85 @@ +/* + 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 + * Scope - object that holds declaration of names. + */ + +#include <libsolidity/analysis/DeclarationContainer.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/ast/Types.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +Declaration const* DeclarationContainer::conflictingDeclaration(Declaration const& _declaration) const +{ + ASTString const& declarationName(_declaration.name()); + solAssert(!declarationName.empty(), ""); + vector<Declaration const*> declarations; + if (m_declarations.count(declarationName)) + declarations += m_declarations.at(declarationName); + if (m_invisibleDeclarations.count(declarationName)) + declarations += m_invisibleDeclarations.at(declarationName); + + if (dynamic_cast<FunctionDefinition const*>(&_declaration)) + { + // check that all other declarations with the same name are functions + for (Declaration const* declaration: declarations) + if (!dynamic_cast<FunctionDefinition const*>(declaration)) + return declaration; + } + else if (!declarations.empty()) + return declarations.front(); + + return nullptr; +} + +bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update) +{ + ASTString const& declarationName(_declaration.name()); + if (declarationName.empty()) + return true; + + if (_update) + { + solAssert(!dynamic_cast<FunctionDefinition const*>(&_declaration), "Attempt to update function definition."); + m_declarations.erase(declarationName); + m_invisibleDeclarations.erase(declarationName); + } + else if (conflictingDeclaration(_declaration)) + return false; + + if (_invisible) + m_invisibleDeclarations[declarationName].push_back(&_declaration); + else + m_declarations[declarationName].push_back(&_declaration); + return true; +} + +std::vector<Declaration const*> DeclarationContainer::resolveName(ASTString const& _name, bool _recursive) const +{ + solAssert(!_name.empty(), "Attempt to resolve empty name."); + auto result = m_declarations.find(_name); + if (result != m_declarations.end()) + return result->second; + if (_recursive && m_enclosingContainer) + return m_enclosingContainer->resolveName(_name, true); + return vector<Declaration const*>({}); +} diff --git a/libsolidity/analysis/DeclarationContainer.h b/libsolidity/analysis/DeclarationContainer.h new file mode 100644 index 00000000..064724d1 --- /dev/null +++ b/libsolidity/analysis/DeclarationContainer.h @@ -0,0 +1,67 @@ +/* + 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 + * Scope - object that holds declaration of names. + */ + +#pragma once + +#include <map> +#include <set> +#include <boost/noncopyable.hpp> + +#include <libsolidity/ast/ASTForward.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Container that stores mappings between names and declarations. It also contains a link to the + * enclosing scope. + */ +class DeclarationContainer +{ +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 or the name is empty. + /// @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 + /// @returns false if the name was already declared. + bool registerDeclaration(Declaration const& _declaration, bool _invisible = false, bool _update = false); + std::vector<Declaration const*> resolveName(ASTString const& _name, bool _recursive = false) const; + Declaration const* enclosingDeclaration() const { return m_enclosingDeclaration; } + 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. + Declaration const* conflictingDeclaration(Declaration const& _declaration) const; + +private: + Declaration const* m_enclosingDeclaration; + DeclarationContainer const* m_enclosingContainer; + std::map<ASTString, std::vector<Declaration const*>> m_declarations; + std::map<ASTString, std::vector<Declaration const*>> m_invisibleDeclarations; +}; + +} +} diff --git a/libsolidity/analysis/GlobalContext.cpp b/libsolidity/analysis/GlobalContext.cpp new file mode 100644 index 00000000..20f8272f --- /dev/null +++ b/libsolidity/analysis/GlobalContext.cpp @@ -0,0 +1,96 @@ +/* + 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> + * @author Gav Wood <g@ethdev.com> + * @date 2014 + * Container of the (implicit and explicit) global objects. + */ + +#include <memory> +#include <libsolidity/analysis/GlobalContext.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/ast/Types.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +GlobalContext::GlobalContext(): +m_magicVariables(vector<shared_ptr<MagicVariableDeclaration const>>{make_shared<MagicVariableDeclaration>("block", make_shared<MagicType>(MagicType::Kind::Block)), + make_shared<MagicVariableDeclaration>("msg", make_shared<MagicType>(MagicType::Kind::Message)), + make_shared<MagicVariableDeclaration>("tx", make_shared<MagicType>(MagicType::Kind::Transaction)), + make_shared<MagicVariableDeclaration>("now", make_shared<IntegerType>(256)), + make_shared<MagicVariableDeclaration>("suicide", + make_shared<FunctionType>(strings{"address"}, strings{}, FunctionType::Location::Suicide)), + make_shared<MagicVariableDeclaration>("sha3", + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), + make_shared<MagicVariableDeclaration>("log0", + make_shared<FunctionType>(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), + make_shared<MagicVariableDeclaration>("log1", + make_shared<FunctionType>(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), + make_shared<MagicVariableDeclaration>("log2", + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), + make_shared<MagicVariableDeclaration>("log3", + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), + make_shared<MagicVariableDeclaration>("log4", + make_shared<FunctionType>(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), + make_shared<MagicVariableDeclaration>("sha256", + make_shared<FunctionType>(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), + make_shared<MagicVariableDeclaration>("ecrecover", + make_shared<FunctionType>(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), + make_shared<MagicVariableDeclaration>("ripemd160", + make_shared<FunctionType>(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))}) +{ +} + +void GlobalContext::setCurrentContract(ContractDefinition const& _contract) +{ + m_currentContract = &_contract; +} + +vector<Declaration const*> GlobalContext::declarations() const +{ + vector<Declaration const*> declarations; + declarations.reserve(m_magicVariables.size()); + for (ASTPointer<Declaration const> const& variable: m_magicVariables) + declarations.push_back(variable.get()); + return declarations; +} + +MagicVariableDeclaration const* GlobalContext::currentThis() const +{ + if (!m_thisPointer[m_currentContract]) + m_thisPointer[m_currentContract] = make_shared<MagicVariableDeclaration>( + "this", make_shared<ContractType>(*m_currentContract)); + return m_thisPointer[m_currentContract].get(); + +} + +MagicVariableDeclaration const* GlobalContext::currentSuper() const +{ + if (!m_superPointer[m_currentContract]) + m_superPointer[m_currentContract] = make_shared<MagicVariableDeclaration>( + "super", make_shared<ContractType>(*m_currentContract, true)); + return m_superPointer[m_currentContract].get(); +} + +} +} diff --git a/libsolidity/analysis/GlobalContext.h b/libsolidity/analysis/GlobalContext.h new file mode 100644 index 00000000..482391d3 --- /dev/null +++ b/libsolidity/analysis/GlobalContext.h @@ -0,0 +1,64 @@ +/* + 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 + * Container of the (implicit and explicit) global objects. + */ + +#pragma once + +#include <string> +#include <vector> +#include <map> +#include <memory> +#include <boost/noncopyable.hpp> +#include <libsolidity/ast/ASTForward.h> + +namespace dev +{ +namespace solidity +{ + +class Type; // forward + +/** + * Container for all global objects which look like AST nodes, but are not part of the AST + * that is currently being compiled. + * @note must not be destroyed or moved during compilation as its objects can be referenced from + * other objects. + */ +class GlobalContext: private boost::noncopyable +{ +public: + GlobalContext(); + void setCurrentContract(ContractDefinition const& _contract); + MagicVariableDeclaration const* currentThis() const; + MagicVariableDeclaration const* currentSuper() const; + + /// @returns a vector of all implicit global declarations excluding "this". + std::vector<Declaration const*> declarations() const; + +private: + std::vector<std::shared_ptr<MagicVariableDeclaration const>> m_magicVariables; + ContractDefinition const* m_currentContract = nullptr; + std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_thisPointer; + std::map<ContractDefinition const*, std::shared_ptr<MagicVariableDeclaration const>> mutable m_superPointer; +}; + +} +} diff --git a/libsolidity/analysis/NameAndTypeResolver.cpp b/libsolidity/analysis/NameAndTypeResolver.cpp new file mode 100644 index 00000000..ffd01137 --- /dev/null +++ b/libsolidity/analysis/NameAndTypeResolver.cpp @@ -0,0 +1,555 @@ +/* + 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 + * Parser part that determines the declarations corresponding to names and the types of expressions. + */ + +#include <libsolidity/analysis/NameAndTypeResolver.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/analysis/TypeChecker.h> +#include <libsolidity/interface/Exceptions.h> + +using namespace std; + +namespace dev +{ +namespace solidity +{ + +NameAndTypeResolver::NameAndTypeResolver( + vector<Declaration const*> const& _globals, + ErrorList& _errors +) : + m_errors(_errors) +{ + for (Declaration const* declaration: _globals) + m_scopes[nullptr].registerDeclaration(*declaration); +} + +bool NameAndTypeResolver::registerDeclarations(SourceUnit& _sourceUnit) +{ + // The helper registers all declarations in m_scopes as a side-effect of its construction. + try + { + DeclarationRegistrationHelper registrar(m_scopes, _sourceUnit, m_errors); + } + catch (FatalError const& _e) + { + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + return false; + } + return true; +} + +bool NameAndTypeResolver::resolveNamesAndTypes(ContractDefinition& _contract) +{ + try + { + m_currentScope = &m_scopes[nullptr]; + + for (ASTPointer<InheritanceSpecifier> const& baseContract: _contract.baseContracts()) + ReferencesResolver resolver(*baseContract, *this, &_contract, nullptr); + + m_currentScope = &m_scopes[&_contract]; + + linearizeBaseContracts(_contract); + std::vector<ContractDefinition const*> properBases( + ++_contract.annotation().linearizedBaseContracts.begin(), + _contract.annotation().linearizedBaseContracts.end() + ); + + for (ContractDefinition const* base: properBases) + importInheritedScope(*base); + + for (ASTPointer<StructDefinition> const& structDef: _contract.definedStructs()) + ReferencesResolver resolver(*structDef, *this, &_contract, nullptr); + for (ASTPointer<EnumDefinition> const& enumDef: _contract.definedEnums()) + ReferencesResolver resolver(*enumDef, *this, &_contract, nullptr); + for (ASTPointer<VariableDeclaration> const& variable: _contract.stateVariables()) + ReferencesResolver resolver(*variable, *this, &_contract, nullptr); + for (ASTPointer<EventDefinition> const& event: _contract.events()) + ReferencesResolver resolver(*event, *this, &_contract, nullptr); + + // these can contain code, only resolve parameters for now + for (ASTPointer<ModifierDefinition> const& modifier: _contract.functionModifiers()) + { + m_currentScope = &m_scopes[modifier.get()]; + ReferencesResolver resolver(*modifier, *this, &_contract, nullptr); + } + for (ASTPointer<FunctionDefinition> const& function: _contract.definedFunctions()) + { + m_currentScope = &m_scopes[function.get()]; + ReferencesResolver referencesResolver( + *function, + *this, + &_contract, + function->returnParameterList().get() + ); + } + + m_currentScope = &m_scopes[&_contract]; + + // now resolve references inside the code + for (ASTPointer<ModifierDefinition> const& modifier: _contract.functionModifiers()) + { + m_currentScope = &m_scopes[modifier.get()]; + ReferencesResolver resolver(*modifier, *this, &_contract, nullptr, true); + } + for (ASTPointer<FunctionDefinition> const& function: _contract.definedFunctions()) + { + m_currentScope = &m_scopes[function.get()]; + ReferencesResolver referencesResolver( + *function, + *this, + &_contract, + function->returnParameterList().get(), + true + ); + } + } + catch (FatalError const& _e) + { + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + return false; + } + return true; +} + +bool NameAndTypeResolver::updateDeclaration(Declaration const& _declaration) +{ + try + { + m_scopes[nullptr].registerDeclaration(_declaration, false, true); + solAssert(_declaration.scope() == nullptr, "Updated declaration outside global scope."); + } + catch (FatalError const& _error) + { + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + return false; + } + return true; +} + +vector<Declaration const*> NameAndTypeResolver::resolveName(ASTString const& _name, Declaration const* _scope) const +{ + auto iterator = m_scopes.find(_scope); + if (iterator == end(m_scopes)) + return vector<Declaration const*>({}); + return iterator->second.resolveName(_name, false); +} + +vector<Declaration const*> NameAndTypeResolver::nameFromCurrentScope(ASTString const& _name, bool _recursive) const +{ + return m_currentScope->resolveName(_name, _recursive); +} + +Declaration const* NameAndTypeResolver::pathFromCurrentScope(vector<ASTString> const& _path, bool _recursive) const +{ + solAssert(!_path.empty(), ""); + vector<Declaration const*> candidates = m_currentScope->resolveName(_path.front(), _recursive); + for (size_t i = 1; i < _path.size() && candidates.size() == 1; i++) + { + if (!m_scopes.count(candidates.front())) + return nullptr; + candidates = m_scopes.at(candidates.front()).resolveName(_path[i], false); + } + if (candidates.size() == 1) + return candidates.front(); + else + return nullptr; +} + +vector<Declaration const*> NameAndTypeResolver::cleanedDeclarations( + Identifier const& _identifier, + vector<Declaration const*> const& _declarations +) +{ + solAssert(_declarations.size() > 1, ""); + vector<Declaration const*> uniqueFunctions; + + for (auto it = _declarations.begin(); it != _declarations.end(); ++it) + { + solAssert(*it, ""); + // the declaration is functionDefinition while declarations > 1 + FunctionDefinition const& functionDefinition = dynamic_cast<FunctionDefinition const&>(**it); + FunctionType functionType(functionDefinition); + for (auto parameter: functionType.parameterTypes() + functionType.returnParameterTypes()) + if (!parameter) + reportFatalDeclarationError(_identifier.location(), "Function type can not be used in this context"); + + if (uniqueFunctions.end() == find_if( + uniqueFunctions.begin(), + uniqueFunctions.end(), + [&](Declaration const* d) + { + FunctionType newFunctionType(dynamic_cast<FunctionDefinition const&>(*d)); + return functionType.hasEqualArgumentTypes(newFunctionType); + } + )) + uniqueFunctions.push_back(*it); + } + return uniqueFunctions; +} + +void NameAndTypeResolver::importInheritedScope(ContractDefinition const& _base) +{ + auto iterator = m_scopes.find(&_base); + solAssert(iterator != end(m_scopes), ""); + for (auto const& nameAndDeclaration: iterator->second.declarations()) + for (auto const& declaration: nameAndDeclaration.second) + // Import if it was declared in the base, is not the constructor and is visible in derived classes + if (declaration->scope() == &_base && declaration->isVisibleInDerivedContracts()) + m_currentScope->registerDeclaration(*declaration); +} + +void NameAndTypeResolver::linearizeBaseContracts(ContractDefinition& _contract) +{ + // order in the lists is from derived to base + // list of lists to linearize, the last element is the list of direct bases + list<list<ContractDefinition const*>> input(1, {}); + for (ASTPointer<InheritanceSpecifier> const& baseSpecifier: _contract.baseContracts()) + { + Identifier const& baseName = baseSpecifier->name(); + auto base = dynamic_cast<ContractDefinition const*>(baseName.annotation().referencedDeclaration); + if (!base) + reportFatalTypeError(baseName.createTypeError("Contract expected.")); + // "push_front" has the effect that bases mentioned later can overwrite members of bases + // mentioned earlier + input.back().push_front(base); + vector<ContractDefinition const*> const& basesBases = base->annotation().linearizedBaseContracts; + if (basesBases.empty()) + reportFatalTypeError(baseName.createTypeError("Definition of base has to precede definition of derived contract")); + input.push_front(list<ContractDefinition const*>(basesBases.begin(), basesBases.end())); + } + input.back().push_front(&_contract); + vector<ContractDefinition const*> result = cThreeMerge(input); + if (result.empty()) + reportFatalTypeError(_contract.createTypeError("Linearization of inheritance graph impossible")); + _contract.annotation().linearizedBaseContracts = result; + _contract.annotation().contractDependencies.insert(result.begin() + 1, result.end()); +} + +template <class _T> +vector<_T const*> NameAndTypeResolver::cThreeMerge(list<list<_T const*>>& _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; +} + +void NameAndTypeResolver::reportDeclarationError( + SourceLocation _sourceLoction, + string const& _description, + SourceLocation _secondarySourceLocation, + string const& _secondaryDescription +) +{ + auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error? + *err << + errinfo_sourceLocation(_sourceLoction) << + errinfo_comment(_description) << + errinfo_secondarySourceLocation( + SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation) + ); + + m_errors.push_back(err); +} + +void NameAndTypeResolver::reportDeclarationError(SourceLocation _sourceLocation, string const& _description) +{ + auto err = make_shared<Error>(Error::Type::DeclarationError); // todo remove Error? + *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description); + + m_errors.push_back(err); +} + +void NameAndTypeResolver::reportFatalDeclarationError( + SourceLocation _sourceLocation, + string const& _description +) +{ + reportDeclarationError(_sourceLocation, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} + +void NameAndTypeResolver::reportTypeError(Error const& _e) +{ + m_errors.push_back(make_shared<Error>(_e)); +} + +void NameAndTypeResolver::reportFatalTypeError(Error const& _e) +{ + reportTypeError(_e); + BOOST_THROW_EXCEPTION(FatalError()); +} + +DeclarationRegistrationHelper::DeclarationRegistrationHelper( + map<ASTNode const*, DeclarationContainer>& _scopes, + ASTNode& _astRoot, + ErrorList& _errors +): + m_scopes(_scopes), + m_currentScope(nullptr), + m_errors(_errors) +{ + _astRoot.accept(*this); +} + +bool DeclarationRegistrationHelper::visit(ContractDefinition& _contract) +{ + registerDeclaration(_contract, true); + _contract.annotation().canonicalName = currentCanonicalName(); + return true; +} + +void DeclarationRegistrationHelper::endVisit(ContractDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(StructDefinition& _struct) +{ + registerDeclaration(_struct, true); + _struct.annotation().canonicalName = currentCanonicalName(); + return true; +} + +void DeclarationRegistrationHelper::endVisit(StructDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(EnumDefinition& _enum) +{ + registerDeclaration(_enum, true); + _enum.annotation().canonicalName = currentCanonicalName(); + return true; +} + +void DeclarationRegistrationHelper::endVisit(EnumDefinition&) +{ + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(EnumValue& _value) +{ + registerDeclaration(_value, false); + return true; +} + +bool DeclarationRegistrationHelper::visit(FunctionDefinition& _function) +{ + registerDeclaration(_function, true); + m_currentFunction = &_function; + return true; +} + +void DeclarationRegistrationHelper::endVisit(FunctionDefinition&) +{ + m_currentFunction = nullptr; + closeCurrentScope(); +} + +bool DeclarationRegistrationHelper::visit(ModifierDefinition& _modifier) +{ + registerDeclaration(_modifier, true); + m_currentFunction = &_modifier; + return true; +} + +void DeclarationRegistrationHelper::endVisit(ModifierDefinition&) +{ + m_currentFunction = nullptr; + closeCurrentScope(); +} + +void DeclarationRegistrationHelper::endVisit(VariableDeclarationStatement& _variableDeclarationStatement) +{ + // Register the local variables with the function + // This does not fit here perfectly, but it saves us another AST visit. + solAssert(m_currentFunction, "Variable declaration without function."); + for (ASTPointer<VariableDeclaration> const& var: _variableDeclarationStatement.declarations()) + if (var) + m_currentFunction->addLocalVariable(*var); +} + +bool DeclarationRegistrationHelper::visit(VariableDeclaration& _declaration) +{ + registerDeclaration(_declaration, false); + return true; +} + +bool DeclarationRegistrationHelper::visit(EventDefinition& _event) +{ + registerDeclaration(_event, true); + return true; +} + +void DeclarationRegistrationHelper::endVisit(EventDefinition&) +{ + closeCurrentScope(); +} + +void DeclarationRegistrationHelper::enterNewSubScope(Declaration const& _declaration) +{ + map<ASTNode const*, DeclarationContainer>::iterator iter; + bool newlyAdded; + tie(iter, newlyAdded) = m_scopes.emplace(&_declaration, DeclarationContainer(m_currentScope, &m_scopes[m_currentScope])); + solAssert(newlyAdded, "Unable to add new scope."); + m_currentScope = &_declaration; +} + +void DeclarationRegistrationHelper::closeCurrentScope() +{ + solAssert(m_currentScope, "Closed non-existing scope."); + m_currentScope = m_scopes[m_currentScope].enclosingDeclaration(); +} + +void DeclarationRegistrationHelper::registerDeclaration(Declaration& _declaration, bool _opensScope) +{ + if (!m_scopes[m_currentScope].registerDeclaration(_declaration, !_declaration.isVisibleInContract())) + { + SourceLocation firstDeclarationLocation; + SourceLocation secondDeclarationLocation; + Declaration const* conflictingDeclaration = m_scopes[m_currentScope].conflictingDeclaration(_declaration); + solAssert(conflictingDeclaration, ""); + + if (_declaration.location().start < conflictingDeclaration->location().start) + { + firstDeclarationLocation = _declaration.location(); + secondDeclarationLocation = conflictingDeclaration->location(); + } + else + { + firstDeclarationLocation = conflictingDeclaration->location(); + secondDeclarationLocation = _declaration.location(); + } + + declarationError( + secondDeclarationLocation, + "Identifier already declared.", + firstDeclarationLocation, + "The previous declaration is here:" + ); + } + + _declaration.setScope(m_currentScope); + if (_opensScope) + enterNewSubScope(_declaration); +} + +string DeclarationRegistrationHelper::currentCanonicalName() const +{ + string ret; + for ( + Declaration const* scope = m_currentScope; + scope != nullptr; + scope = m_scopes[scope].enclosingDeclaration() + ) + { + if (!ret.empty()) + ret = "." + ret; + ret = scope->name() + ret; + } + return ret; +} + +void DeclarationRegistrationHelper::declarationError( + SourceLocation _sourceLocation, + string const& _description, + SourceLocation _secondarySourceLocation, + string const& _secondaryDescription +) +{ + auto err = make_shared<Error>(Error::Type::DeclarationError); + *err << + errinfo_sourceLocation(_sourceLocation) << + errinfo_comment(_description) << + errinfo_secondarySourceLocation( + SecondarySourceLocation().append(_secondaryDescription, _secondarySourceLocation) + ); + + m_errors.push_back(err); +} + +void DeclarationRegistrationHelper::declarationError(SourceLocation _sourceLocation, string const& _description) +{ + auto err = make_shared<Error>(Error::Type::DeclarationError); + *err << errinfo_sourceLocation(_sourceLocation) << errinfo_comment(_description); + + m_errors.push_back(err); +} + +void DeclarationRegistrationHelper::fatalDeclarationError( + SourceLocation _sourceLocation, + string const& _description +) +{ + declarationError(_sourceLocation, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} + +} +} diff --git a/libsolidity/analysis/NameAndTypeResolver.h b/libsolidity/analysis/NameAndTypeResolver.h new file mode 100644 index 00000000..0d9b2477 --- /dev/null +++ b/libsolidity/analysis/NameAndTypeResolver.h @@ -0,0 +1,169 @@ +/* + 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 + * Parser part that determines the declarations corresponding to names and the types of expressions. + */ + +#pragma once + +#include <map> +#include <list> +#include <boost/noncopyable.hpp> +#include <libsolidity/analysis/DeclarationContainer.h> +#include <libsolidity/analysis/ReferencesResolver.h> +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/ast/ASTAnnotations.h> + +namespace dev +{ +namespace solidity +{ + +/** + * Resolves name references, typenames and sets the (explicitly given) types for all variable + * declarations. + */ +class NameAndTypeResolver: private boost::noncopyable +{ +public: + NameAndTypeResolver(std::vector<Declaration const*> const& _globals, ErrorList& _errors); + /// Registers all declarations found in the source unit. + /// @returns false in case of error. + bool registerDeclarations(SourceUnit& _sourceUnit); + /// Resolves all names and types referenced from the given contract. + /// @returns false in case of error. + bool resolveNamesAndTypes(ContractDefinition& _contract); + /// Updates the given global declaration (used for "this"). Not to be used with declarations + /// that create their own scope. + /// @returns false in case of error. + bool updateDeclaration(Declaration const& _declaration); + + /// 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). + /// @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; + + /// Resolves a name in the "current" scope. Should only be called during the initial + /// resolving phase. + std::vector<Declaration const*> nameFromCurrentScope(ASTString const& _name, bool _recursive = true) const; + + /// Resolves a path starting from the "current" scope. Should only be called during the initial + /// resolving phase. + /// @note Returns a null pointer if any component in the path was not unique or not found. + Declaration const* pathFromCurrentScope(std::vector<ASTString> const& _path, bool _recursive = true) const; + + /// returns the vector of declarations without repetitions + std::vector<Declaration const*> cleanedDeclarations( + Identifier const& _identifier, + std::vector<Declaration const*> const& _declarations + ); + +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. Reports errors if any + void linearizeBaseContracts(ContractDefinition& _contract); + /// 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 <class _T> + 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*, DeclarationContainer> m_scopes; + + // creates the Declaration error and adds it in the errors list + void reportDeclarationError( + SourceLocation _sourceLoction, + std::string const& _description, + SourceLocation _secondarySourceLocation, + std::string const& _secondaryDescription + ); + // creates the Declaration error and adds it in the errors list + void reportDeclarationError(SourceLocation _sourceLocation, std::string const& _description); + // creates the Declaration error and adds it in the errors list and throws FatalError + void reportFatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description); + + // creates the Declaration error and adds it in the errors list + void reportTypeError(Error const& _e); + // creates the Declaration error and adds it in the errors list and throws FatalError + void reportFatalTypeError(Error const& _e); + + DeclarationContainer* m_currentScope = nullptr; + ErrorList& m_errors; +}; + +/** + * Traverses the given AST upon construction and fills _scopes with all declarations inside the + * AST. + */ +class DeclarationRegistrationHelper: private ASTVisitor +{ +public: + DeclarationRegistrationHelper(std::map<ASTNode const*, DeclarationContainer>& _scopes, ASTNode& _astRoot, ErrorList& _errors); + +private: + bool visit(ContractDefinition& _contract) override; + void endVisit(ContractDefinition& _contract) override; + bool visit(StructDefinition& _struct) override; + void endVisit(StructDefinition& _struct) override; + bool visit(EnumDefinition& _enum) override; + void endVisit(EnumDefinition& _enum) override; + bool visit(EnumValue& _value) override; + bool visit(FunctionDefinition& _function) override; + void endVisit(FunctionDefinition& _function) override; + bool visit(ModifierDefinition& _modifier) override; + void endVisit(ModifierDefinition& _modifier) override; + void endVisit(VariableDeclarationStatement& _variableDeclarationStatement) override; + bool visit(VariableDeclaration& _declaration) override; + bool visit(EventDefinition& _event) override; + void endVisit(EventDefinition& _event) override; + + void enterNewSubScope(Declaration const& _declaration); + void closeCurrentScope(); + void registerDeclaration(Declaration& _declaration, bool _opensScope); + + /// @returns the canonical name of the current scope. + std::string currentCanonicalName() const; + // creates the Declaration error and adds it in the errors list + void declarationError( + SourceLocation _sourceLocation, + std::string const& _description, + SourceLocation _secondarySourceLocation, + std::string const& _secondaryDescription + ); + + // creates the Declaration error and adds it in the errors list + void declarationError(SourceLocation _sourceLocation, std::string const& _description); + // creates the Declaration error and adds it in the errors list and throws FatalError + void fatalDeclarationError(SourceLocation _sourceLocation, std::string const& _description); + + std::map<ASTNode const*, DeclarationContainer>& m_scopes; + Declaration const* m_currentScope; + VariableScope* m_currentFunction; + ErrorList& m_errors; +}; + +} +} diff --git a/libsolidity/analysis/ReferencesResolver.cpp b/libsolidity/analysis/ReferencesResolver.cpp new file mode 100644 index 00000000..fb7cdb3e --- /dev/null +++ b/libsolidity/analysis/ReferencesResolver.cpp @@ -0,0 +1,234 @@ +/* + 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 + * Component that resolves type names to types and annotates the AST accordingly. + */ + +#include <libsolidity/analysis/ReferencesResolver.h> +#include <libsolidity/ast/AST.h> +#include <libsolidity/analysis/NameAndTypeResolver.h> +#include <libsolidity/interface/Exceptions.h> +#include <libsolidity/analysis/ConstantEvaluator.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +ReferencesResolver::ReferencesResolver( + ASTNode& _root, + NameAndTypeResolver& _resolver, + ContractDefinition const* _currentContract, + ParameterList const* _returnParameters, + bool _resolveInsideCode +): + m_resolver(_resolver), + m_currentContract(_currentContract), + m_returnParameters(_returnParameters), + m_resolveInsideCode(_resolveInsideCode) +{ + _root.accept(*this); +} + +bool ReferencesResolver::visit(Return const& _return) +{ + _return.annotation().functionReturnParameters = m_returnParameters; + return true; +} + +bool ReferencesResolver::visit(UserDefinedTypeName const& _typeName) +{ + Declaration const* declaration = m_resolver.pathFromCurrentScope(_typeName.namePath()); + if (!declaration) + BOOST_THROW_EXCEPTION( + Error(Error::Type::DeclarationError) << + errinfo_sourceLocation(_typeName.location()) << + errinfo_comment("Identifier not found or not unique.") + ); + _typeName.annotation().referencedDeclaration = declaration; + return true; +} + +bool ReferencesResolver::visit(Identifier const& _identifier) +{ + auto declarations = m_resolver.nameFromCurrentScope(_identifier.name()); + if (declarations.empty()) + BOOST_THROW_EXCEPTION( + Error(Error::Type::DeclarationError) << + errinfo_sourceLocation(_identifier.location()) << + errinfo_comment("Undeclared identifier.") + ); + else if (declarations.size() == 1) + { + _identifier.annotation().referencedDeclaration = declarations.front(); + _identifier.annotation().contractScope = m_currentContract; + } + else + _identifier.annotation().overloadedDeclarations = + m_resolver.cleanedDeclarations(_identifier, declarations); + return false; +} + +void ReferencesResolver::endVisit(VariableDeclaration const& _variable) +{ + if (_variable.annotation().type) + return; + + TypePointer type; + if (_variable.typeName()) + { + type = typeFor(*_variable.typeName()); + using Location = VariableDeclaration::Location; + Location loc = _variable.referenceLocation(); + // References are forced to calldata for external function parameters (not return) + // and memory for parameters (also return) of publicly visible functions. + // They default to memory for function parameters and storage for local variables. + // As an exception, "storage" is allowed for library functions. + if (auto ref = dynamic_cast<ReferenceType const*>(type.get())) + { + if (_variable.isExternalCallableParameter()) + { + auto const& contract = dynamic_cast<ContractDefinition const&>(*_variable.scope()->scope()); + if (contract.isLibrary()) + { + if (loc == Location::Memory) + BOOST_THROW_EXCEPTION(_variable.createTypeError( + "Location has to be calldata or storage for external " + "library functions (remove the \"memory\" keyword)." + )); + } + else + { + // force location of external function parameters (not return) to calldata + if (loc != Location::Default) + BOOST_THROW_EXCEPTION(_variable.createTypeError( + "Location has to be calldata for external functions " + "(remove the \"memory\" or \"storage\" keyword)." + )); + } + if (loc == Location::Default) + type = ref->copyForLocation(DataLocation::CallData, true); + } + else if (_variable.isCallableParameter() && _variable.scope()->isPublic()) + { + auto const& contract = dynamic_cast<ContractDefinition const&>(*_variable.scope()->scope()); + // force locations of public or external function (return) parameters to memory + if (loc == Location::Storage && !contract.isLibrary()) + BOOST_THROW_EXCEPTION(_variable.createTypeError( + "Location has to be memory for publicly visible functions " + "(remove the \"storage\" keyword)." + )); + if (loc == Location::Default || !contract.isLibrary()) + type = ref->copyForLocation(DataLocation::Memory, true); + } + else + { + if (_variable.isConstant()) + { + if (loc != Location::Default && loc != Location::Memory) + BOOST_THROW_EXCEPTION(_variable.createTypeError( + "Storage location has to be \"memory\" (or unspecified) for constants." + )); + loc = Location::Memory; + } + if (loc == Location::Default) + loc = _variable.isCallableParameter() ? Location::Memory : Location::Storage; + bool isPointer = !_variable.isStateVariable(); + type = ref->copyForLocation( + loc == Location::Memory ? + DataLocation::Memory : + DataLocation::Storage, + isPointer + ); + } + } + else if (loc != Location::Default && !ref) + BOOST_THROW_EXCEPTION(_variable.createTypeError( + "Storage location can only be given for array or struct types." + )); + + if (!type) + BOOST_THROW_EXCEPTION(_variable.typeName()->createTypeError("Invalid type name.")); + + } + else if (!_variable.canHaveAutoType()) + BOOST_THROW_EXCEPTION(_variable.createTypeError("Explicit type needed.")); + // otherwise we have a "var"-declaration whose type is resolved by the first assignment + + _variable.annotation().type = type; +} + +TypePointer ReferencesResolver::typeFor(TypeName const& _typeName) +{ + if (_typeName.annotation().type) + return _typeName.annotation().type; + + TypePointer type; + if (auto elemTypeName = dynamic_cast<ElementaryTypeName const*>(&_typeName)) + type = Type::fromElementaryTypeName(elemTypeName->typeName()); + else if (auto typeName = dynamic_cast<UserDefinedTypeName const*>(&_typeName)) + { + Declaration const* declaration = typeName->annotation().referencedDeclaration; + solAssert(!!declaration, ""); + + if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration)) + type = make_shared<StructType>(*structDef); + else if (EnumDefinition const* enumDef = dynamic_cast<EnumDefinition const*>(declaration)) + type = make_shared<EnumType>(*enumDef); + else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration)) + type = make_shared<ContractType>(*contract); + else + BOOST_THROW_EXCEPTION(typeName->createTypeError( + "Name has to refer to a struct, enum or contract." + )); + } + else if (auto mapping = dynamic_cast<Mapping const*>(&_typeName)) + { + TypePointer keyType = typeFor(mapping->keyType()); + TypePointer valueType = typeFor(mapping->valueType()); + // Convert key type to memory. + keyType = ReferenceType::copyForLocationIfReference(DataLocation::Memory, keyType); + // Convert value type to storage reference. + valueType = ReferenceType::copyForLocationIfReference(DataLocation::Storage, valueType); + type = make_shared<MappingType>(keyType, valueType); + } + else if (auto arrayType = dynamic_cast<ArrayTypeName const*>(&_typeName)) + { + TypePointer baseType = typeFor(arrayType->baseType()); + if (baseType->storageBytes() == 0) + BOOST_THROW_EXCEPTION(arrayType->baseType().createTypeError( + "Illegal base type of storage size zero for array." + )); + if (Expression const* length = arrayType->length()) + { + if (!length->annotation().type) + ConstantEvaluator e(*length); + + auto const* lengthType = dynamic_cast<IntegerConstantType const*>(length->annotation().type.get()); + if (!lengthType) + BOOST_THROW_EXCEPTION(length->createTypeError("Invalid array length.")); + type = make_shared<ArrayType>(DataLocation::Storage, baseType, lengthType->literalValue(nullptr)); + } + else + type = make_shared<ArrayType>(DataLocation::Storage, baseType); + } + + return _typeName.annotation().type = move(type); +} + diff --git a/libsolidity/analysis/ReferencesResolver.h b/libsolidity/analysis/ReferencesResolver.h new file mode 100644 index 00000000..4276adaa --- /dev/null +++ b/libsolidity/analysis/ReferencesResolver.h @@ -0,0 +1,69 @@ +/* + 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 + * Component that resolves type names to types and annotates the AST accordingly. + */ + +#pragma once + +#include <map> +#include <list> +#include <boost/noncopyable.hpp> +#include <libsolidity/ast/ASTVisitor.h> +#include <libsolidity/ast/ASTAnnotations.h> + +namespace dev +{ +namespace solidity +{ + +class NameAndTypeResolver; + +/** + * Resolves references to declarations (of variables and types) and also establishes the link + * between a return statement and the return parameter list. + */ +class ReferencesResolver: private ASTConstVisitor +{ +public: + ReferencesResolver( + ASTNode& _root, + NameAndTypeResolver& _resolver, + ContractDefinition const* _currentContract, + ParameterList const* _returnParameters, + bool _resolveInsideCode = false + ); + +private: + virtual bool visit(Block const&) override { return m_resolveInsideCode; } + virtual bool visit(Identifier const& _identifier) override; + virtual bool visit(UserDefinedTypeName const& _typeName) override; + virtual bool visit(Return const& _return) override; + virtual void endVisit(VariableDeclaration const& _variable) override; + + TypePointer typeFor(TypeName const& _typeName); + + NameAndTypeResolver& m_resolver; + ContractDefinition const* m_currentContract; + ParameterList const* m_returnParameters; + bool m_resolveInsideCode; +}; + +} +} diff --git a/libsolidity/analysis/TypeChecker.cpp b/libsolidity/analysis/TypeChecker.cpp new file mode 100644 index 00000000..6b12c57f --- /dev/null +++ b/libsolidity/analysis/TypeChecker.cpp @@ -0,0 +1,1328 @@ +/* + 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 + * Type analyzer and checker. + */ + +#include <libsolidity/analysis/TypeChecker.h> +#include <memory> +#include <boost/range/adaptor/reversed.hpp> +#include <libsolidity/ast/AST.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + + +bool TypeChecker::checkTypeRequirements(const ContractDefinition& _contract) +{ + try + { + visit(_contract); + } + catch (FatalError const&) + { + // We got a fatal error which required to stop further type checking, but we can + // continue normally from here. + if (m_errors.empty()) + throw; // Something is weird here, rather throw again. + } + return Error::containsOnlyWarnings(m_errors); +} + +TypePointer const& TypeChecker::type(Expression const& _expression) const +{ + solAssert(!!_expression.annotation().type, "Type requested but not present."); + return _expression.annotation().type; +} + +TypePointer const& TypeChecker::type(VariableDeclaration const& _variable) const +{ + solAssert(!!_variable.annotation().type, "Type requested but not present."); + return _variable.annotation().type; +} + +bool TypeChecker::visit(ContractDefinition const& _contract) +{ + // We force our own visiting order here. + ASTNode::listAccept(_contract.definedStructs(), *this); + ASTNode::listAccept(_contract.baseContracts(), *this); + + checkContractDuplicateFunctions(_contract); + checkContractIllegalOverrides(_contract); + checkContractAbstractFunctions(_contract); + checkContractAbstractConstructors(_contract); + + FunctionDefinition const* function = _contract.constructor(); + if (function && !function->returnParameters().empty()) + typeError(*function->returnParameterList(), "Non-empty \"returns\" directive for constructor."); + + FunctionDefinition const* fallbackFunction = nullptr; + for (ASTPointer<FunctionDefinition> const& function: _contract.definedFunctions()) + { + if (function->name().empty()) + { + if (fallbackFunction) + { + auto err = make_shared<Error>(Error::Type::DeclarationError); + *err << errinfo_comment("Only one fallback function is allowed."); + m_errors.push_back(err); + } + else + { + fallbackFunction = function.get(); + if (!fallbackFunction->parameters().empty()) + typeError(fallbackFunction->parameterList(), "Fallback function cannot take parameters."); + } + } + if (!function->isImplemented()) + _contract.annotation().isFullyImplemented = false; + } + + ASTNode::listAccept(_contract.stateVariables(), *this); + ASTNode::listAccept(_contract.events(), *this); + ASTNode::listAccept(_contract.functionModifiers(), *this); + ASTNode::listAccept(_contract.definedFunctions(), *this); + + checkContractExternalTypeClashes(_contract); + // check for hash collisions in function signatures + set<FixedHash<4>> hashes; + for (auto const& it: _contract.interfaceFunctionList()) + { + FixedHash<4> const& hash = it.first; + if (hashes.count(hash)) + typeError( + _contract, + string("Function signature hash collision for ") + it.second->externalSignature() + ); + hashes.insert(hash); + } + + if (_contract.isLibrary()) + checkLibraryRequirements(_contract); + + return false; +} + +void TypeChecker::checkContractDuplicateFunctions(ContractDefinition const& _contract) +{ + /// Checks that two functions with the same name defined in this contract have different + /// argument types and that there is at most one constructor. + map<string, vector<FunctionDefinition const*>> functions; + for (ASTPointer<FunctionDefinition> const& function: _contract.definedFunctions()) + functions[function->name()].push_back(function.get()); + + // Constructor + if (functions[_contract.name()].size() > 1) + { + SecondarySourceLocation ssl; + auto it = ++functions[_contract.name()].begin(); + for (; it != functions[_contract.name()].end(); ++it) + ssl.append("Another declaration is here:", (*it)->location()); + + auto err = make_shared<Error>(Error(Error::Type::DeclarationError)); + *err << + errinfo_sourceLocation(functions[_contract.name()].front()->location()) << + errinfo_comment("More than one constructor defined.") << + errinfo_secondarySourceLocation(ssl); + m_errors.push_back(err); + } + for (auto const& it: functions) + { + vector<FunctionDefinition const*> const& overloads = it.second; + for (size_t i = 0; i < overloads.size(); ++i) + for (size_t j = i + 1; j < overloads.size(); ++j) + if (FunctionType(*overloads[i]).hasEqualArgumentTypes(FunctionType(*overloads[j]))) + { + auto err = make_shared<Error>(Error(Error::Type::DeclarationError)); + *err << + errinfo_sourceLocation(overloads[j]->location()) << + errinfo_comment("Function with same name and arguments defined twice.") << + errinfo_secondarySourceLocation(SecondarySourceLocation().append( + "Other declaration is here:", overloads[i]->location())); + m_errors.push_back(err); + } + } +} + +void TypeChecker::checkContractAbstractFunctions(ContractDefinition const& _contract) +{ + // Mapping from name to function definition (exactly one per argument type equality class) and + // flag to indicate whether it is fully implemented. + using FunTypeAndFlag = std::pair<FunctionTypePointer, bool>; + map<string, vector<FunTypeAndFlag>> functions; + + // Search from base to derived + for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.annotation().linearizedBaseContracts)) + for (ASTPointer<FunctionDefinition> const& function: contract->definedFunctions()) + { + auto& overloads = functions[function->name()]; + FunctionTypePointer funType = make_shared<FunctionType>(*function); + auto it = find_if(overloads.begin(), overloads.end(), [&](FunTypeAndFlag const& _funAndFlag) + { + return funType->hasEqualArgumentTypes(*_funAndFlag.first); + }); + if (it == overloads.end()) + overloads.push_back(make_pair(funType, function->isImplemented())); + else if (it->second) + { + if (!function->isImplemented()) + typeError(*function, "Redeclaring an already implemented function as abstract"); + } + else if (function->isImplemented()) + it->second = true; + } + + // Set to not fully implemented if at least one flag is false. + for (auto const& it: functions) + for (auto const& funAndFlag: it.second) + if (!funAndFlag.second) + { + _contract.annotation().isFullyImplemented = false; + return; + } +} + +void TypeChecker::checkContractAbstractConstructors(ContractDefinition const& _contract) +{ + set<ContractDefinition const*> argumentsNeeded; + // check that we get arguments for all base constructors that need it. + // If not mark the contract as abstract (not fully implemented) + + vector<ContractDefinition const*> const& bases = _contract.annotation().linearizedBaseContracts; + for (ContractDefinition const* contract: bases) + if (FunctionDefinition const* constructor = contract->constructor()) + if (contract != &_contract && !constructor->parameters().empty()) + argumentsNeeded.insert(contract); + + for (ContractDefinition const* contract: bases) + { + if (FunctionDefinition const* constructor = contract->constructor()) + for (auto const& modifier: constructor->modifiers()) + { + auto baseContract = dynamic_cast<ContractDefinition const*>( + &dereference(*modifier->name()) + ); + if (baseContract) + argumentsNeeded.erase(baseContract); + } + + + for (ASTPointer<InheritanceSpecifier> const& base: contract->baseContracts()) + { + auto baseContract = dynamic_cast<ContractDefinition const*>(&dereference(base->name())); + solAssert(baseContract, ""); + if (!base->arguments().empty()) + argumentsNeeded.erase(baseContract); + } + } + if (!argumentsNeeded.empty()) + _contract.annotation().isFullyImplemented = false; +} + +void TypeChecker::checkContractIllegalOverrides(ContractDefinition const& _contract) +{ + // TODO unify this at a later point. for this we need to put the constness and the access specifier + // into the types + map<string, vector<FunctionDefinition const*>> functions; + map<string, ModifierDefinition const*> modifiers; + + // We search from derived to base, so the stored item causes the error. + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + { + for (ASTPointer<FunctionDefinition> const& function: contract->definedFunctions()) + { + if (function->isConstructor()) + continue; // constructors can neither be overridden nor override anything + string const& name = function->name(); + if (modifiers.count(name)) + typeError(*modifiers[name], "Override changes function to modifier."); + FunctionType functionType(*function); + // function should not change the return type + for (FunctionDefinition const* overriding: functions[name]) + { + FunctionType overridingType(*overriding); + if (!overridingType.hasEqualArgumentTypes(functionType)) + continue; + if ( + overriding->visibility() != function->visibility() || + overriding->isDeclaredConst() != function->isDeclaredConst() || + overridingType != functionType + ) + typeError(*overriding, "Override changes extended function signature."); + } + functions[name].push_back(function.get()); + } + for (ASTPointer<ModifierDefinition> const& modifier: contract->functionModifiers()) + { + string const& name = modifier->name(); + ModifierDefinition const*& override = modifiers[name]; + if (!override) + override = modifier.get(); + else if (ModifierType(*override) != ModifierType(*modifier)) + typeError(*override, "Override changes modifier signature."); + if (!functions[name].empty()) + typeError(*override, "Override changes modifier to function."); + } + } +} + +void TypeChecker::checkContractExternalTypeClashes(ContractDefinition const& _contract) +{ + map<string, vector<pair<Declaration const*, FunctionTypePointer>>> externalDeclarations; + for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts) + { + for (ASTPointer<FunctionDefinition> const& f: contract->definedFunctions()) + if (f->isPartOfExternalInterface()) + { + auto functionType = make_shared<FunctionType>(*f); + externalDeclarations[functionType->externalSignature()].push_back( + make_pair(f.get(), functionType) + ); + } + for (ASTPointer<VariableDeclaration> const& v: contract->stateVariables()) + if (v->isPartOfExternalInterface()) + { + auto functionType = make_shared<FunctionType>(*v); + externalDeclarations[functionType->externalSignature()].push_back( + make_pair(v.get(), functionType) + ); + } + } + for (auto const& it: externalDeclarations) + for (size_t i = 0; i < it.second.size(); ++i) + for (size_t j = i + 1; j < it.second.size(); ++j) + if (!it.second[i].second->hasEqualArgumentTypes(*it.second[j].second)) + typeError( + *it.second[j].first, + "Function overload clash during conversion to external types for arguments." + ); +} + +void TypeChecker::checkLibraryRequirements(ContractDefinition const& _contract) +{ + solAssert(_contract.isLibrary(), ""); + if (!_contract.baseContracts().empty()) + typeError(_contract, "Library is not allowed to inherit."); + + for (auto const& var: _contract.stateVariables()) + if (!var->isConstant()) + typeError(*var, "Library cannot have non-constant state variables"); +} + +void TypeChecker::endVisit(InheritanceSpecifier const& _inheritance) +{ + auto base = dynamic_cast<ContractDefinition const*>(&dereference(_inheritance.name())); + solAssert(base, "Base contract not available."); + + if (base->isLibrary()) + typeError(_inheritance, "Libraries cannot be inherited from."); + + auto const& arguments = _inheritance.arguments(); + TypePointers parameterTypes = ContractType(*base).constructorType()->parameterTypes(); + if (!arguments.empty() && parameterTypes.size() != arguments.size()) + typeError( + _inheritance, + "Wrong argument count for constructor call: " + + toString(arguments.size()) + + " arguments given but expected " + + toString(parameterTypes.size()) + + "." + ); + + for (size_t i = 0; i < arguments.size(); ++i) + if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) + typeError( + *arguments[i], + "Invalid type for argument in constructor call. " + "Invalid implicit conversion from " + + type(*arguments[i])->toString() + + " to " + + parameterTypes[i]->toString() + + " requested." + ); +} + +bool TypeChecker::visit(StructDefinition const& _struct) +{ + for (ASTPointer<VariableDeclaration> const& member: _struct.members()) + if (!type(*member)->canBeStored()) + typeError(*member, "Type cannot be used in struct."); + + // Check recursion, fatal error if detected. + using StructPointer = StructDefinition const*; + using StructPointersSet = set<StructPointer>; + function<void(StructPointer,StructPointersSet const&)> check = [&](StructPointer _struct, StructPointersSet const& _parents) + { + if (_parents.count(_struct)) + fatalTypeError(*_struct, "Recursive struct definition."); + StructPointersSet parents = _parents; + parents.insert(_struct); + for (ASTPointer<VariableDeclaration> const& member: _struct->members()) + if (type(*member)->category() == Type::Category::Struct) + { + auto const& typeName = dynamic_cast<UserDefinedTypeName const&>(*member->typeName()); + check(&dynamic_cast<StructDefinition const&>(*typeName.annotation().referencedDeclaration), parents); + } + }; + check(&_struct, StructPointersSet{}); + + ASTNode::listAccept(_struct.members(), *this); + + return false; +} + +bool TypeChecker::visit(FunctionDefinition const& _function) +{ + bool isLibraryFunction = dynamic_cast<ContractDefinition const&>(*_function.scope()).isLibrary(); + for (ASTPointer<VariableDeclaration> const& var: _function.parameters() + _function.returnParameters()) + { + if (!type(*var)->canLiveOutsideStorage()) + typeError(*var, "Type is required to live outside storage."); + if (_function.visibility() >= FunctionDefinition::Visibility::Public && !(type(*var)->interfaceType(isLibraryFunction))) + fatalTypeError(*var, "Internal type is not allowed for public or external functions."); + } + for (ASTPointer<ModifierInvocation> const& modifier: _function.modifiers()) + visitManually( + *modifier, + _function.isConstructor() ? + dynamic_cast<ContractDefinition const&>(*_function.scope()).annotation().linearizedBaseContracts : + vector<ContractDefinition const*>() + ); + if (_function.isImplemented()) + _function.body().accept(*this); + return false; +} + +bool TypeChecker::visit(VariableDeclaration const& _variable) +{ + // Variables can be declared without type (with "var"), in which case the first assignment + // sets the type. + // Note that assignments before the first declaration are legal because of the special scoping + // rules inherited from JavaScript. + + // type is filled either by ReferencesResolver directly from the type name or by + // TypeChecker at the VariableDeclarationStatement level. + TypePointer varType = _variable.annotation().type; + solAssert(!!varType, "Failed to infer variable type."); + if (_variable.isConstant()) + { + if (!dynamic_cast<ContractDefinition const*>(_variable.scope())) + typeError(_variable, "Illegal use of \"constant\" specifier."); + if (!_variable.value()) + typeError(_variable, "Uninitialized \"constant\" variable."); + if (!varType->isValueType()) + { + bool constImplemented = false; + if (auto arrayType = dynamic_cast<ArrayType const*>(varType.get())) + constImplemented = arrayType->isByteArray(); + if (!constImplemented) + typeError( + _variable, + "Illegal use of \"constant\" specifier. \"constant\" " + "is not yet implemented for this type." + ); + } + } + if (_variable.value()) + expectType(*_variable.value(), *varType); + if (!_variable.isStateVariable()) + { + if (varType->dataStoredIn(DataLocation::Memory) || varType->dataStoredIn(DataLocation::CallData)) + if (!varType->canLiveOutsideStorage()) + typeError(_variable, "Type " + varType->toString() + " is only valid in storage."); + } + else if ( + _variable.visibility() >= VariableDeclaration::Visibility::Public && + !FunctionType(_variable).interfaceFunctionType() + ) + typeError(_variable, "Internal type is not allowed for public state variables."); + return false; +} + +void TypeChecker::visitManually( + ModifierInvocation const& _modifier, + vector<ContractDefinition const*> const& _bases +) +{ + std::vector<ASTPointer<Expression>> const& arguments = _modifier.arguments(); + for (ASTPointer<Expression> const& argument: arguments) + argument->accept(*this); + _modifier.name()->accept(*this); + + auto const* declaration = &dereference(*_modifier.name()); + vector<ASTPointer<VariableDeclaration>> emptyParameterList; + vector<ASTPointer<VariableDeclaration>> const* parameters = nullptr; + if (auto modifierDecl = dynamic_cast<ModifierDefinition const*>(declaration)) + parameters = &modifierDecl->parameters(); + else + // check parameters for Base constructors + for (ContractDefinition const* base: _bases) + if (declaration == base) + { + if (auto referencedConstructor = base->constructor()) + parameters = &referencedConstructor->parameters(); + else + parameters = &emptyParameterList; + break; + } + if (!parameters) + typeError(_modifier, "Referenced declaration is neither modifier nor base class."); + if (parameters->size() != arguments.size()) + typeError( + _modifier, + "Wrong argument count for modifier invocation: " + + toString(arguments.size()) + + " arguments given but expected " + + toString(parameters->size()) + + "." + ); + for (size_t i = 0; i < _modifier.arguments().size(); ++i) + if (!type(*arguments[i])->isImplicitlyConvertibleTo(*type(*(*parameters)[i]))) + typeError( + *arguments[i], + "Invalid type for argument in modifier invocation. " + "Invalid implicit conversion from " + + type(*arguments[i])->toString() + + " to " + + type(*(*parameters)[i])->toString() + + " requested." + ); +} + +bool TypeChecker::visit(EventDefinition const& _eventDef) +{ + unsigned numIndexed = 0; + for (ASTPointer<VariableDeclaration> const& var: _eventDef.parameters()) + { + if (var->isIndexed()) + numIndexed++; + if (_eventDef.isAnonymous() && numIndexed > 4) + typeError(_eventDef, "More than 4 indexed arguments for anonymous event."); + else if (!_eventDef.isAnonymous() && numIndexed > 3) + typeError(_eventDef, "More than 3 indexed arguments for event."); + if (!type(*var)->canLiveOutsideStorage()) + typeError(*var, "Type is required to live outside storage."); + if (!type(*var)->interfaceType(false)) + typeError(*var, "Internal type is not allowed as event parameter type."); + } + return false; +} + + +bool TypeChecker::visit(IfStatement const& _ifStatement) +{ + expectType(_ifStatement.condition(), BoolType()); + _ifStatement.trueStatement().accept(*this); + if (_ifStatement.falseStatement()) + _ifStatement.falseStatement()->accept(*this); + return false; +} + +bool TypeChecker::visit(WhileStatement const& _whileStatement) +{ + expectType(_whileStatement.condition(), BoolType()); + _whileStatement.body().accept(*this); + return false; +} + +bool TypeChecker::visit(ForStatement const& _forStatement) +{ + if (_forStatement.initializationExpression()) + _forStatement.initializationExpression()->accept(*this); + if (_forStatement.condition()) + expectType(*_forStatement.condition(), BoolType()); + if (_forStatement.loopExpression()) + _forStatement.loopExpression()->accept(*this); + _forStatement.body().accept(*this); + return false; +} + +void TypeChecker::endVisit(Return const& _return) +{ + if (!_return.expression()) + return; + ParameterList const* params = _return.annotation().functionReturnParameters; + if (!params) + { + typeError(_return, "Return arguments not allowed."); + return; + } + TypePointers returnTypes; + for (auto const& var: params->parameters()) + returnTypes.push_back(type(*var)); + if (auto tupleType = dynamic_cast<TupleType const*>(type(*_return.expression()).get())) + { + if (tupleType->components().size() != params->parameters().size()) + typeError(_return, "Different number of arguments in return statement than in returns declaration."); + else if (!tupleType->isImplicitlyConvertibleTo(TupleType(returnTypes))) + typeError( + *_return.expression(), + "Return argument type " + + type(*_return.expression())->toString() + + " is not implicitly convertible to expected type " + + TupleType(returnTypes).toString(false) + + "." + ); + } + else if (params->parameters().size() != 1) + typeError(_return, "Different number of arguments in return statement than in returns declaration."); + else + { + TypePointer const& expected = type(*params->parameters().front()); + if (!type(*_return.expression())->isImplicitlyConvertibleTo(*expected)) + typeError( + *_return.expression(), + "Return argument type " + + type(*_return.expression())->toString() + + " is not implicitly convertible to expected type (type of first return variable) " + + expected->toString() + + "." + ); + } +} + +bool TypeChecker::visit(VariableDeclarationStatement const& _statement) +{ + if (!_statement.initialValue()) + { + // No initial value is only permitted for single variables with specified type. + if (_statement.declarations().size() != 1 || !_statement.declarations().front()) + fatalTypeError(_statement, "Assignment necessary for type detection."); + VariableDeclaration const& varDecl = *_statement.declarations().front(); + if (!varDecl.annotation().type) + fatalTypeError(_statement, "Assignment necessary for type detection."); + if (auto ref = dynamic_cast<ReferenceType const*>(type(varDecl).get())) + { + if (ref->dataStoredIn(DataLocation::Storage)) + { + auto err = make_shared<Error>(Error::Type::Warning); + *err << + errinfo_sourceLocation(varDecl.location()) << + errinfo_comment("Uninitialized storage pointer. Did you mean '<type> memory " + varDecl.name() + "'?"); + m_errors.push_back(err); + } + } + varDecl.accept(*this); + return false; + } + + // Here we have an initial value and might have to derive some types before we can visit + // the variable declaration(s). + + _statement.initialValue()->accept(*this); + TypePointers valueTypes; + if (auto tupleType = dynamic_cast<TupleType const*>(type(*_statement.initialValue()).get())) + valueTypes = tupleType->components(); + else + valueTypes = TypePointers{type(*_statement.initialValue())}; + + // Determine which component is assigned to which variable. + // If numbers do not match, fill up if variables begin or end empty (not both). + vector<VariableDeclaration const*>& assignments = _statement.annotation().assignments; + assignments.resize(valueTypes.size(), nullptr); + vector<ASTPointer<VariableDeclaration>> const& variables = _statement.declarations(); + if (variables.empty()) + { + if (!valueTypes.empty()) + fatalTypeError( + _statement, + "Too many components (" + + toString(valueTypes.size()) + + ") in value for variable assignment (0) needed" + ); + } + else if (valueTypes.size() != variables.size() && !variables.front() && !variables.back()) + fatalTypeError( + _statement, + "Wildcard both at beginning and end of variable declaration list is only allowed " + "if the number of components is equal." + ); + size_t minNumValues = variables.size(); + if (!variables.empty() && (!variables.back() || !variables.front())) + --minNumValues; + if (valueTypes.size() < minNumValues) + fatalTypeError( + _statement, + "Not enough components (" + + toString(valueTypes.size()) + + ") in value to assign all variables (" + + toString(minNumValues) + ")." + ); + if (valueTypes.size() > variables.size() && variables.front() && variables.back()) + fatalTypeError( + _statement, + "Too many components (" + + toString(valueTypes.size()) + + ") in value for variable assignment (" + + toString(minNumValues) + + " needed)." + ); + bool fillRight = !variables.empty() && (!variables.back() || variables.front()); + for (size_t i = 0; i < min(variables.size(), valueTypes.size()); ++i) + if (fillRight) + assignments[i] = variables[i].get(); + else + assignments[assignments.size() - i - 1] = variables[variables.size() - i - 1].get(); + + for (size_t i = 0; i < assignments.size(); ++i) + { + if (!assignments[i]) + continue; + VariableDeclaration const& var = *assignments[i]; + solAssert(!var.value(), "Value has to be tied to statement."); + TypePointer const& valueComponentType = valueTypes[i]; + solAssert(!!valueComponentType, ""); + if (!var.annotation().type) + { + // Infer type from value. + solAssert(!var.typeName(), ""); + if ( + valueComponentType->category() == Type::Category::IntegerConstant && + !dynamic_pointer_cast<IntegerConstantType const>(valueComponentType)->integerType() + ) + fatalTypeError(*_statement.initialValue(), "Invalid integer constant " + valueComponentType->toString() + "."); + var.annotation().type = valueComponentType->mobileType(); + var.accept(*this); + } + else + { + var.accept(*this); + if (!valueComponentType->isImplicitlyConvertibleTo(*var.annotation().type)) + typeError( + _statement, + "Type " + + valueComponentType->toString() + + " is not implicitly convertible to expected type " + + var.annotation().type->toString() + + "." + ); + } + } + return false; +} + +void TypeChecker::endVisit(ExpressionStatement const& _statement) +{ + if (type(_statement.expression())->category() == Type::Category::IntegerConstant) + if (!dynamic_pointer_cast<IntegerConstantType const>(type(_statement.expression()))->integerType()) + typeError(_statement.expression(), "Invalid integer constant."); +} + +bool TypeChecker::visit(Assignment const& _assignment) +{ + requireLValue(_assignment.leftHandSide()); + TypePointer t = type(_assignment.leftHandSide()); + _assignment.annotation().type = t; + if (TupleType const* tupleType = dynamic_cast<TupleType const*>(t.get())) + { + // Sequenced assignments of tuples is not valid, make the result a "void" type. + _assignment.annotation().type = make_shared<TupleType>(); + expectType(_assignment.rightHandSide(), *tupleType); + } + else if (t->category() == Type::Category::Mapping) + { + typeError(_assignment, "Mappings cannot be assigned to."); + _assignment.rightHandSide().accept(*this); + } + else if (_assignment.assignmentOperator() == Token::Assign) + expectType(_assignment.rightHandSide(), *t); + else + { + // compound assignment + _assignment.rightHandSide().accept(*this); + TypePointer resultType = t->binaryOperatorResult( + Token::AssignmentToBinaryOp(_assignment.assignmentOperator()), + type(_assignment.rightHandSide()) + ); + if (!resultType || *resultType != *t) + typeError( + _assignment, + "Operator " + + string(Token::toString(_assignment.assignmentOperator())) + + " not compatible with types " + + t->toString() + + " and " + + type(_assignment.rightHandSide())->toString() + ); + } + return false; +} + +bool TypeChecker::visit(TupleExpression const& _tuple) +{ + vector<ASTPointer<Expression>> const& components = _tuple.components(); + TypePointers types; + if (_tuple.annotation().lValueRequested) + { + for (auto const& component: components) + if (component) + { + requireLValue(*component); + types.push_back(type(*component)); + } + else + types.push_back(TypePointer()); + _tuple.annotation().type = make_shared<TupleType>(types); + // If some of the components are not LValues, the error is reported above. + _tuple.annotation().isLValue = true; + } + else + { + for (size_t i = 0; i < components.size(); ++i) + { + // Outside of an lvalue-context, the only situation where a component can be empty is (x,). + if (!components[i] && !(i == 1 && components.size() == 2)) + fatalTypeError(_tuple, "Tuple component cannot be empty."); + else if (components[i]) + { + components[i]->accept(*this); + types.push_back(type(*components[i])); + } + else + types.push_back(TypePointer()); + } + if (components.size() == 1) + _tuple.annotation().type = type(*components[0]); + else + { + if (components.size() == 2 && !components[1]) + types.pop_back(); + _tuple.annotation().type = make_shared<TupleType>(types); + } + } + return false; +} + +bool TypeChecker::visit(UnaryOperation const& _operation) +{ + // Inc, Dec, Add, Sub, Not, BitNot, Delete + Token::Value op = _operation.getOperator(); + if (op == Token::Value::Inc || op == Token::Value::Dec || op == Token::Value::Delete) + requireLValue(_operation.subExpression()); + else + _operation.subExpression().accept(*this); + TypePointer const& subExprType = type(_operation.subExpression()); + TypePointer t = type(_operation.subExpression())->unaryOperatorResult(op); + if (!t) + { + typeError( + _operation, + "Unary operator " + + string(Token::toString(op)) + + " cannot be applied to type " + + subExprType->toString() + ); + t = subExprType; + } + _operation.annotation().type = t; + return false; +} + +void TypeChecker::endVisit(BinaryOperation const& _operation) +{ + TypePointer const& leftType = type(_operation.leftExpression()); + TypePointer const& rightType = type(_operation.rightExpression()); + TypePointer commonType = leftType->binaryOperatorResult(_operation.getOperator(), rightType); + if (!commonType) + { + typeError( + _operation, + "Operator " + + string(Token::toString(_operation.getOperator())) + + " not compatible with types " + + leftType->toString() + + " and " + + rightType->toString() + ); + commonType = leftType; + } + _operation.annotation().commonType = commonType; + _operation.annotation().type = + Token::isCompareOp(_operation.getOperator()) ? + make_shared<BoolType>() : + commonType; +} + +bool TypeChecker::visit(FunctionCall const& _functionCall) +{ + bool isPositionalCall = _functionCall.names().empty(); + vector<ASTPointer<Expression const>> arguments = _functionCall.arguments(); + vector<ASTPointer<ASTString>> const& argumentNames = _functionCall.names(); + + // We need to check arguments' type first as they will be needed for overload resolution. + shared_ptr<TypePointers> argumentTypes; + if (isPositionalCall) + argumentTypes = make_shared<TypePointers>(); + for (ASTPointer<Expression const> const& argument: arguments) + { + argument->accept(*this); + // only store them for positional calls + if (isPositionalCall) + argumentTypes->push_back(type(*argument)); + } + if (isPositionalCall) + _functionCall.expression().annotation().argumentTypes = move(argumentTypes); + + _functionCall.expression().accept(*this); + TypePointer expressionType = type(_functionCall.expression()); + + if (auto const* typeType = dynamic_cast<TypeType const*>(expressionType.get())) + { + _functionCall.annotation().isStructConstructorCall = (typeType->actualType()->category() == Type::Category::Struct); + _functionCall.annotation().isTypeConversion = !_functionCall.annotation().isStructConstructorCall; + } + else + _functionCall.annotation().isStructConstructorCall = _functionCall.annotation().isTypeConversion = false; + + if (_functionCall.annotation().isTypeConversion) + { + TypeType const& t = dynamic_cast<TypeType const&>(*expressionType); + TypePointer resultType = t.actualType(); + if (arguments.size() != 1) + typeError(_functionCall, "Exactly one argument expected for explicit type conversion."); + else if (!isPositionalCall) + typeError(_functionCall, "Type conversion cannot allow named arguments."); + else + { + TypePointer const& argType = type(*arguments.front()); + if (auto argRefType = dynamic_cast<ReferenceType const*>(argType.get())) + // do not change the data location when converting + // (data location cannot yet be specified for type conversions) + resultType = ReferenceType::copyForLocationIfReference(argRefType->location(), resultType); + if (!argType->isExplicitlyConvertibleTo(*resultType)) + typeError(_functionCall, "Explicit type conversion not allowed."); + } + _functionCall.annotation().type = resultType; + + return false; + } + + // Actual function call or struct constructor call. + + FunctionTypePointer functionType; + + /// For error message: Struct members that were removed during conversion to memory. + set<string> membersRemovedForStructConstructor; + if (_functionCall.annotation().isStructConstructorCall) + { + TypeType const& t = dynamic_cast<TypeType const&>(*expressionType); + auto const& structType = dynamic_cast<StructType const&>(*t.actualType()); + functionType = structType.constructorType(); + membersRemovedForStructConstructor = structType.membersMissingInMemory(); + } + else + functionType = dynamic_pointer_cast<FunctionType const>(expressionType); + + if (!functionType) + { + typeError(_functionCall, "Type is not callable"); + _functionCall.annotation().type = make_shared<TupleType>(); + return false; + } + else if (functionType->returnParameterTypes().size() == 1) + _functionCall.annotation().type = functionType->returnParameterTypes().front(); + else + _functionCall.annotation().type = make_shared<TupleType>(functionType->returnParameterTypes()); + + TypePointers const& parameterTypes = functionType->parameterTypes(); + if (!functionType->takesArbitraryParameters() && parameterTypes.size() != arguments.size()) + { + string msg = + "Wrong argument count for function call: " + + toString(arguments.size()) + + " arguments given but expected " + + toString(parameterTypes.size()) + + "."; + // Extend error message in case we try to construct a struct with mapping member. + if (_functionCall.annotation().isStructConstructorCall && !membersRemovedForStructConstructor.empty()) + { + msg += " Members that have to be skipped in memory:"; + for (auto const& member: membersRemovedForStructConstructor) + msg += " " + member; + } + typeError(_functionCall, msg); + } + else if (isPositionalCall) + { + // call by positional arguments + for (size_t i = 0; i < arguments.size(); ++i) + { + auto const& argType = type(*arguments[i]); + if (functionType->takesArbitraryParameters()) + { + if (auto t = dynamic_cast<IntegerConstantType const*>(argType.get())) + if (!t->integerType()) + typeError(*arguments[i], "Integer constant too large."); + } + else if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[i])) + typeError( + *arguments[i], + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*arguments[i])->toString() + + " to " + + parameterTypes[i]->toString() + + " requested." + ); + } + } + else + { + // call by named arguments + auto const& parameterNames = functionType->parameterNames(); + if (functionType->takesArbitraryParameters()) + typeError( + _functionCall, + "Named arguments cannnot be used for functions that take arbitrary parameters." + ); + else if (parameterNames.size() > argumentNames.size()) + typeError(_functionCall, "Some argument names are missing."); + else if (parameterNames.size() < argumentNames.size()) + typeError(_functionCall, "Too many arguments."); + else + { + // check duplicate names + bool duplication = false; + for (size_t i = 0; i < argumentNames.size(); i++) + for (size_t j = i + 1; j < argumentNames.size(); j++) + if (*argumentNames[i] == *argumentNames[j]) + { + duplication = true; + typeError(*arguments[i], "Duplicate named argument."); + } + + // check actual types + if (!duplication) + for (size_t i = 0; i < argumentNames.size(); i++) + { + bool found = false; + for (size_t j = 0; j < parameterNames.size(); j++) + if (parameterNames[j] == *argumentNames[i]) + { + found = true; + // check type convertible + if (!type(*arguments[i])->isImplicitlyConvertibleTo(*parameterTypes[j])) + typeError( + *arguments[i], + "Invalid type for argument in function call. " + "Invalid implicit conversion from " + + type(*arguments[i])->toString() + + " to " + + parameterTypes[i]->toString() + + " requested." + ); + break; + } + + if (!found) + typeError( + _functionCall, + "Named argument does not match function declaration." + ); + } + } + } + + return false; +} + +void TypeChecker::endVisit(NewExpression const& _newExpression) +{ + auto contract = dynamic_cast<ContractDefinition const*>(&dereference(_newExpression.contractName())); + + if (!contract) + fatalTypeError(_newExpression, "Identifier is not a contract."); + if (!contract->annotation().isFullyImplemented) + typeError(_newExpression, "Trying to create an instance of an abstract contract."); + + auto scopeContract = _newExpression.contractName().annotation().contractScope; + scopeContract->annotation().contractDependencies.insert(contract); + solAssert( + !contract->annotation().linearizedBaseContracts.empty(), + "Linearized base contracts not yet available." + ); + if (contractDependenciesAreCyclic(*scopeContract)) + typeError( + _newExpression, + "Circular reference for contract creation (cannot create instance of derived or same contract)." + ); + + auto contractType = make_shared<ContractType>(*contract); + TypePointers const& parameterTypes = contractType->constructorType()->parameterTypes(); + _newExpression.annotation().type = make_shared<FunctionType>( + parameterTypes, + TypePointers{contractType}, + strings(), + strings(), + FunctionType::Location::Creation + ); +} + +bool TypeChecker::visit(MemberAccess const& _memberAccess) +{ + _memberAccess.expression().accept(*this); + TypePointer exprType = type(_memberAccess.expression()); + ASTString const& memberName = _memberAccess.memberName(); + + // Retrieve the types of the arguments if this is used to call a function. + auto const& argumentTypes = _memberAccess.annotation().argumentTypes; + MemberList::MemberMap possibleMembers = exprType->members().membersByName(memberName); + if (possibleMembers.size() > 1 && argumentTypes) + { + // do overload resolution + for (auto it = possibleMembers.begin(); it != possibleMembers.end();) + if ( + it->type->category() == Type::Category::Function && + !dynamic_cast<FunctionType const&>(*it->type).canTakeArguments(*argumentTypes) + ) + it = possibleMembers.erase(it); + else + ++it; + } + if (possibleMembers.size() == 0) + { + auto storageType = ReferenceType::copyForLocationIfReference( + DataLocation::Storage, + exprType + ); + if (!storageType->members().membersByName(memberName).empty()) + fatalTypeError( + _memberAccess, + "Member \"" + memberName + "\" is not available in " + + exprType->toString() + + " outside of storage." + ); + fatalTypeError( + _memberAccess, + "Member \"" + memberName + "\" not found or not visible " + "after argument-dependent lookup in " + exprType->toString() + ); + } + else if (possibleMembers.size() > 1) + fatalTypeError( + _memberAccess, + "Member \"" + memberName + "\" not unique " + "after argument-dependent lookup in " + exprType->toString() + ); + + auto& annotation = _memberAccess.annotation(); + annotation.referencedDeclaration = possibleMembers.front().declaration; + annotation.type = possibleMembers.front().type; + if (exprType->category() == Type::Category::Struct) + annotation.isLValue = true; + else if (exprType->category() == Type::Category::Array) + { + auto const& arrayType(dynamic_cast<ArrayType const&>(*exprType)); + annotation.isLValue = ( + memberName == "length" && + arrayType.location() == DataLocation::Storage && + arrayType.isDynamicallySized() + ); + } + + return false; +} + +bool TypeChecker::visit(IndexAccess const& _access) +{ + _access.baseExpression().accept(*this); + TypePointer baseType = type(_access.baseExpression()); + TypePointer resultType; + bool isLValue = false; + Expression const* index = _access.indexExpression(); + switch (baseType->category()) + { + case Type::Category::Array: + { + ArrayType const& actualType = dynamic_cast<ArrayType const&>(*baseType); + if (!index) + typeError(_access, "Index expression cannot be omitted."); + else if (actualType.isString()) + { + typeError(_access, "Index access for string is not possible."); + index->accept(*this); + } + else + { + expectType(*index, IntegerType(256)); + if (auto integerType = dynamic_cast<IntegerConstantType const*>(type(*index).get())) + if (!actualType.isDynamicallySized() && actualType.length() <= integerType->literalValue(nullptr)) + typeError(_access, "Out of bounds array access."); + } + resultType = actualType.baseType(); + isLValue = actualType.location() != DataLocation::CallData; + break; + } + case Type::Category::Mapping: + { + MappingType const& actualType = dynamic_cast<MappingType const&>(*baseType); + if (!index) + typeError(_access, "Index expression cannot be omitted."); + else + expectType(*index, *actualType.keyType()); + resultType = actualType.valueType(); + isLValue = true; + break; + } + case Type::Category::TypeType: + { + TypeType const& typeType = dynamic_cast<TypeType const&>(*baseType); + if (!index) + resultType = make_shared<TypeType>(make_shared<ArrayType>(DataLocation::Memory, typeType.actualType())); + else + { + index->accept(*this); + if (auto length = dynamic_cast<IntegerConstantType const*>(type(*index).get())) + resultType = make_shared<TypeType>(make_shared<ArrayType>( + DataLocation::Memory, + typeType.actualType(), + length->literalValue(nullptr) + )); + else + typeError(*index, "Integer constant expected."); + } + break; + } + default: + fatalTypeError( + _access.baseExpression(), + "Indexed expression has to be a type, mapping or array (is " + baseType->toString() + ")" + ); + } + _access.annotation().type = move(resultType); + _access.annotation().isLValue = isLValue; + + return false; +} + +bool TypeChecker::visit(Identifier const& _identifier) +{ + IdentifierAnnotation& annotation = _identifier.annotation(); + if (!annotation.referencedDeclaration) + { + if (!annotation.argumentTypes) + fatalTypeError(_identifier, "Unable to determine overloaded type."); + if (annotation.overloadedDeclarations.empty()) + fatalTypeError(_identifier, "No candidates for overload resolution found."); + else if (annotation.overloadedDeclarations.size() == 1) + annotation.referencedDeclaration = *annotation.overloadedDeclarations.begin(); + else + { + vector<Declaration const*> candidates; + + for (Declaration const* declaration: annotation.overloadedDeclarations) + { + TypePointer function = declaration->type(_identifier.annotation().contractScope); + solAssert(!!function, "Requested type not present."); + auto const* functionType = dynamic_cast<FunctionType const*>(function.get()); + if (functionType && functionType->canTakeArguments(*annotation.argumentTypes)) + candidates.push_back(declaration); + } + if (candidates.empty()) + fatalTypeError(_identifier, "No matching declaration found after argument-dependent lookup."); + else if (candidates.size() == 1) + annotation.referencedDeclaration = candidates.front(); + else + fatalTypeError(_identifier, "No unique declaration found after argument-dependent lookup."); + } + } + solAssert( + !!annotation.referencedDeclaration, + "Referenced declaration is null after overload resolution." + ); + annotation.isLValue = annotation.referencedDeclaration->isLValue(); + annotation.type = annotation.referencedDeclaration->type(_identifier.annotation().contractScope); + if (!annotation.type) + fatalTypeError(_identifier, "Declaration referenced before type could be determined."); + return false; +} + +void TypeChecker::endVisit(ElementaryTypeNameExpression const& _expr) +{ + _expr.annotation().type = make_shared<TypeType>(Type::fromElementaryTypeName(_expr.typeToken())); +} + +void TypeChecker::endVisit(Literal const& _literal) +{ + _literal.annotation().type = Type::forLiteral(_literal); + if (!_literal.annotation().type) + fatalTypeError(_literal, "Invalid literal value."); +} + +bool TypeChecker::contractDependenciesAreCyclic( + ContractDefinition const& _contract, + std::set<ContractDefinition const*> const& _seenContracts +) const +{ + // Naive depth-first search that remembers nodes already seen. + if (_seenContracts.count(&_contract)) + return true; + set<ContractDefinition const*> seen(_seenContracts); + seen.insert(&_contract); + for (auto const* c: _contract.annotation().contractDependencies) + if (contractDependenciesAreCyclic(*c, seen)) + return true; + return false; +} + +Declaration const& TypeChecker::dereference(Identifier const& _identifier) +{ + solAssert(!!_identifier.annotation().referencedDeclaration, "Declaration not stored."); + return *_identifier.annotation().referencedDeclaration; +} + +void TypeChecker::expectType(Expression const& _expression, Type const& _expectedType) +{ + _expression.accept(*this); + + if (!type(_expression)->isImplicitlyConvertibleTo(_expectedType)) + typeError( + _expression, + "Type " + + type(_expression)->toString() + + " is not implicitly convertible to expected type " + + _expectedType.toString() + + "." + ); +} + +void TypeChecker::requireLValue(Expression const& _expression) +{ + _expression.annotation().lValueRequested = true; + _expression.accept(*this); + if (!_expression.annotation().isLValue) + typeError(_expression, "Expression has to be an lvalue."); +} + +void TypeChecker::typeError(ASTNode const& _node, string const& _description) +{ + auto err = make_shared<Error>(Error::Type::TypeError); + *err << + errinfo_sourceLocation(_node.location()) << + errinfo_comment(_description); + + m_errors.push_back(err); +} + +void TypeChecker::fatalTypeError(ASTNode const& _node, string const& _description) +{ + typeError(_node, _description); + BOOST_THROW_EXCEPTION(FatalError()); +} diff --git a/libsolidity/analysis/TypeChecker.h b/libsolidity/analysis/TypeChecker.h new file mode 100644 index 00000000..9a568349 --- /dev/null +++ b/libsolidity/analysis/TypeChecker.h @@ -0,0 +1,122 @@ +/* + 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 + * Type analyzer and checker. + */ + +#pragma once + +#include <libsolidity/analysis/TypeChecker.h> +#include <libsolidity/ast/Types.h> +#include <libsolidity/ast/ASTAnnotations.h> +#include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ASTVisitor.h> + +namespace dev +{ +namespace solidity +{ + + +/** + * The module that performs type analysis on the AST, checks the applicability of operations on + * those types and stores errors for invalid operations. + * Provides a way to retrieve the type of an AST node. + */ +class TypeChecker: private ASTConstVisitor +{ +public: + /// @param _errors the reference to the list of errors and warnings to add them found during type checking. + TypeChecker(ErrorList& _errors): m_errors(_errors) {} + + /// Performs type checking on the given contract and all of its sub-nodes. + /// @returns true iff all checks passed. Note even if all checks passed, errors() can still contain warnings + bool checkTypeRequirements(ContractDefinition const& _contract); + + /// @returns the type of an expression and asserts that it is present. + TypePointer const& type(Expression const& _expression) const; + /// @returns the type of the given variable and throws if the type is not present + /// (this can happen for variables with non-explicit types before their types are resolved) + TypePointer const& type(VariableDeclaration const& _variable) const; + +private: + /// Adds a new error to the list of errors. + void typeError(ASTNode const& _node, std::string const& _description); + + /// Adds a new error to the list of errors and throws to abort type checking. + void fatalTypeError(ASTNode const& _node, std::string const& _description); + + virtual bool visit(ContractDefinition const& _contract) override; + /// Checks that two functions defined in this contract with the same name have different + /// arguments and that there is at most one constructor. + void checkContractDuplicateFunctions(ContractDefinition const& _contract); + void checkContractIllegalOverrides(ContractDefinition const& _contract); + void checkContractAbstractFunctions(ContractDefinition const& _contract); + void checkContractAbstractConstructors(ContractDefinition const& _contract); + /// Checks that different functions with external visibility end up having different + /// external argument types (i.e. different signature). + void checkContractExternalTypeClashes(ContractDefinition const& _contract); + /// Checks that all requirements for a library are fulfilled if this is a library. + void checkLibraryRequirements(ContractDefinition const& _contract); + + virtual void endVisit(InheritanceSpecifier const& _inheritance) override; + virtual bool visit(StructDefinition const& _struct) override; + virtual bool visit(FunctionDefinition const& _function) override; + virtual bool visit(VariableDeclaration const& _variable) override; + /// We need to do this manually because we want to pass the bases of the current contract in + /// case this is a base constructor call. + void visitManually(ModifierInvocation const& _modifier, std::vector<ContractDefinition const*> const& _bases); + virtual bool visit(EventDefinition const& _eventDef) override; + virtual bool visit(IfStatement const& _ifStatement) override; + virtual bool visit(WhileStatement const& _whileStatement) override; + virtual bool visit(ForStatement const& _forStatement) override; + virtual void endVisit(Return const& _return) override; + virtual bool visit(VariableDeclarationStatement const& _variable) override; + virtual void endVisit(ExpressionStatement const& _statement) override; + virtual bool visit(Assignment const& _assignment) override; + virtual bool visit(TupleExpression const& _tuple) override; + virtual void endVisit(BinaryOperation const& _operation) override; + virtual bool visit(UnaryOperation const& _operation) override; + virtual bool visit(FunctionCall const& _functionCall) override; + virtual void endVisit(NewExpression const& _newExpression) override; + virtual bool visit(MemberAccess const& _memberAccess) override; + virtual bool visit(IndexAccess const& _indexAccess) override; + virtual bool visit(Identifier const& _identifier) override; + virtual void endVisit(ElementaryTypeNameExpression const& _expr) override; + virtual void endVisit(Literal const& _literal) override; + + bool contractDependenciesAreCyclic( + ContractDefinition const& _contract, + std::set<ContractDefinition const*> const& _seenContracts = std::set<ContractDefinition const*>() + ) const; + + /// @returns the referenced declaration and throws on error. + Declaration const& dereference(Identifier const& _identifier); + + /// Runs type checks on @a _expression to infer its type and then checks that it is implicitly + /// convertible to @a _expectedType. + void expectType(Expression const& _expression, Type const& _expectedType); + /// Runs type checks on @a _expression to infer its type and then checks that it is an LValue. + void requireLValue(Expression const& _expression); + + ErrorList& m_errors; +}; + +} +} |