diff options
author | chriseth <chris@ethereum.org> | 2017-08-29 01:48:34 +0800 |
---|---|---|
committer | chriseth <chris@ethereum.org> | 2017-09-06 19:50:49 +0800 |
commit | 5470da4d9adc8ef07aa1c2a758b7062be843cca4 (patch) | |
tree | 824e0012f236a9d959c0eb46e5a1fdb02ece43ec /libsolidity/analysis | |
parent | 16526ad554256bde3e185c99c1651b0f47725165 (diff) | |
download | dexon-solidity-5470da4d9adc8ef07aa1c2a758b7062be843cca4.tar.gz dexon-solidity-5470da4d9adc8ef07aa1c2a758b7062be843cca4.tar.zst dexon-solidity-5470da4d9adc8ef07aa1c2a758b7062be843cca4.zip |
View-pure checker.
Diffstat (limited to 'libsolidity/analysis')
-rw-r--r-- | libsolidity/analysis/StaticAnalyzer.h | 4 | ||||
-rw-r--r-- | libsolidity/analysis/ViewPureChecker.cpp | 227 | ||||
-rw-r--r-- | libsolidity/analysis/ViewPureChecker.h | 79 |
3 files changed, 308 insertions, 2 deletions
diff --git a/libsolidity/analysis/StaticAnalyzer.h b/libsolidity/analysis/StaticAnalyzer.h index a3080b42..24ed119f 100644 --- a/libsolidity/analysis/StaticAnalyzer.h +++ b/libsolidity/analysis/StaticAnalyzer.h @@ -37,8 +37,8 @@ namespace solidity /** * The module that performs static analysis on the AST. * In this context, static analysis is anything that can produce warnings which can help - * programmers write cleaner code. For every warning generated eher, it has to be possible to write - * equivalent code that does generate the warning. + * programmers write cleaner code. For every warning generated here, it has to be possible to write + * equivalent code that does not generate the warning. */ class StaticAnalyzer: private ASTConstVisitor { diff --git a/libsolidity/analysis/ViewPureChecker.cpp b/libsolidity/analysis/ViewPureChecker.cpp new file mode 100644 index 00000000..0e2cfacf --- /dev/null +++ b/libsolidity/analysis/ViewPureChecker.cpp @@ -0,0 +1,227 @@ +/* + This file is part of solidity. + + solidity 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. + + solidity 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 solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <libsolidity/analysis/ViewPureChecker.h> + +using namespace std; +using namespace dev; +using namespace dev::solidity; + +bool ViewPureChecker::check() +{ + vector<ContractDefinition const*> contracts; + + for (auto const& node: m_ast) + { + SourceUnit const* source = dynamic_cast<SourceUnit const*>(node.get()); + solAssert(source, ""); + for (auto const& topLevelNode: source->nodes()) + { + ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(topLevelNode.get()); + if (contract) + contracts.push_back(contract); + } + } + + // Check modifiers first to infer their state mutability. + for (auto const* contract: contracts) + for (ModifierDefinition const* mod: contract->functionModifiers()) + mod->accept(*this); + + for (auto const* contract: contracts) + contract->accept(*this); + + return !m_errors; +} + + + +bool ViewPureChecker::visit(FunctionDefinition const& _funDef) +{ + solAssert(!m_currentFunction, ""); + m_currentFunction = &_funDef; + m_currentBestMutability = StateMutability::Pure; + return true; +} + +void ViewPureChecker::endVisit(FunctionDefinition const& _funDef) +{ + solAssert(m_currentFunction == &_funDef, ""); + if ( + m_currentBestMutability < _funDef.stateMutability() && + _funDef.stateMutability() != StateMutability::Payable && + _funDef.isImplemented() && + !_funDef.isConstructor() && + !_funDef.isFallback() && + !_funDef.isConstructor() && + !_funDef.annotation().superFunction + ) + m_errorReporter.warning( + _funDef.location(), + "Function state mutability can be restricted to " + stateMutabilityToString(m_currentBestMutability) + ); + m_currentFunction = nullptr; +} + +bool ViewPureChecker::visit(ModifierDefinition const&) +{ + solAssert(m_currentFunction == nullptr, ""); + m_currentBestMutability = StateMutability::Pure; + return true; +} + +void ViewPureChecker::endVisit(ModifierDefinition const& _modifierDef) +{ + solAssert(m_currentFunction == nullptr, ""); + m_inferredMutability[&_modifierDef] = m_currentBestMutability; +} + +void ViewPureChecker::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + solAssert(declaration, ""); + + StateMutability mutability = StateMutability::Pure; + + bool writes = _identifier.annotation().lValueRequested; + if (VariableDeclaration const* varDecl = dynamic_cast<VariableDeclaration const*>(declaration)) + { + if (varDecl->isStateVariable()) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + } + else if (MagicVariableDeclaration const* magicVar = dynamic_cast<MagicVariableDeclaration const*>(declaration)) + { + switch (magicVar->type()->category()) + { + case Type::Category::Contract: + solAssert(_identifier.name() == "this" || _identifier.name() == "super", ""); + if (!dynamic_cast<ContractType const&>(*magicVar->type()).isSuper()) + // reads the address + mutability = StateMutability::View; + break; + case Type::Category::Integer: + solAssert(_identifier.name() == "now", ""); + mutability = StateMutability::View; + break; + default: + break; + } + } + + reportMutability(mutability, _identifier); +} + +void ViewPureChecker::endVisit(InlineAssembly const& _inlineAssembly) +{ + // @TOOD we can and should analyze it further. + reportMutability(StateMutability::NonPayable, _inlineAssembly); +} + +void ViewPureChecker::reportMutability(StateMutability _mutability, const ASTNode& _node) +{ + if (m_currentFunction && m_currentFunction->stateMutability() < _mutability) + { + m_errors = true; + if (_mutability == StateMutability::View) + m_errorReporter.typeError( + _node.location(), + "Function declared as pure, but this expression reads from the environment or state and thus " + "requires \"view\"." + ); + else if (_mutability == StateMutability::NonPayable) + m_errorReporter.typeError( + _node.location(), + "Function declared as " + + stateMutabilityToString(m_currentFunction->stateMutability()) + + ", but this expression modifies the state and thus " + "requires non-payable (the default) or payable." + ); + else + solAssert(false, ""); + } + if (_mutability >= m_currentBestMutability) + m_currentBestMutability = _mutability; +} + +void ViewPureChecker::endVisit(FunctionCall const& _functionCall) +{ + if (_functionCall.annotation().kind != FunctionCallKind::FunctionCall) + return; + + StateMutability mut = dynamic_cast<FunctionType const&>(*_functionCall.expression().annotation().type).stateMutability(); + // We only require "nonpayable" to call a payble function. + if (mut == StateMutability::Payable) + mut = StateMutability::NonPayable; + reportMutability(mut, _functionCall); +} + +void ViewPureChecker::endVisit(MemberAccess const& _memberAccess) +{ + StateMutability mutability = StateMutability::Pure; + bool writes = _memberAccess.annotation().lValueRequested; + + ASTString const& member = _memberAccess.memberName(); + switch (_memberAccess.expression().annotation().type->category()) + { + case Type::Category::Contract: + case Type::Category::Integer: + if (member == "balance" && !_memberAccess.annotation().referencedDeclaration) + mutability = StateMutability::View; + break; + case Type::Category::Magic: + // we can ignore the kind of magic and only look at the name of the member + if (member != "data" && member != "sig" && member != "blockhash") + mutability = StateMutability::View; + break; + case Type::Category::Struct: + { + if (_memberAccess.expression().annotation().type->dataStoredIn(DataLocation::Storage)) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + break; + } + case Type::Category::Array: + { + auto const& type = dynamic_cast<ArrayType const&>(*_memberAccess.expression().annotation().type); + if (member == "length" && type.isDynamicallySized() && type.dataStoredIn(DataLocation::Storage)) + mutability = writes ? StateMutability::NonPayable : StateMutability::View; + break; + } + default: + break; + } + reportMutability(mutability, _memberAccess); +} + +void ViewPureChecker::endVisit(IndexAccess const& _indexAccess) +{ + solAssert(_indexAccess.indexExpression(), ""); + + bool writes = _indexAccess.annotation().lValueRequested; + if (_indexAccess.baseExpression().annotation().type->dataStoredIn(DataLocation::Storage)) + reportMutability(writes ? StateMutability::NonPayable : StateMutability::View, _indexAccess); +} + +void ViewPureChecker::endVisit(ModifierInvocation const& _modifier) +{ + solAssert(_modifier.name(), ""); + ModifierDefinition const* mod = dynamic_cast<decltype(mod)>(_modifier.name()->annotation().referencedDeclaration); + solAssert(mod, ""); + solAssert(m_inferredMutability.count(mod), ""); + + reportMutability(m_inferredMutability.at(mod), _modifier); +} + diff --git a/libsolidity/analysis/ViewPureChecker.h b/libsolidity/analysis/ViewPureChecker.h new file mode 100644 index 00000000..6aedfa36 --- /dev/null +++ b/libsolidity/analysis/ViewPureChecker.h @@ -0,0 +1,79 @@ +/* + This file is part of solidity. + + solidity 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. + + solidity 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 solidity. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <libsolidity/ast/ASTEnums.h> +#include <libsolidity/ast/ASTForward.h> +#include <libsolidity/ast/ASTVisitor.h> + +#include <libsolidity/interface/ErrorReporter.h> + +#include <map> +#include <memory> + +namespace dev +{ +namespace solidity +{ + +class ASTNode; +class FunctionDefinition; +class ModifierDefinition; +class Identifier; +class MemberAccess; +class IndexAccess; +class ModifierInvocation; +class FunctionCall; +class InlineAssembly; + +class ViewPureChecker: private ASTConstVisitor +{ +public: + ViewPureChecker(std::vector<std::shared_ptr<ASTNode>> const& _ast, ErrorReporter& _errorReporter): + m_ast(_ast), m_errorReporter(_errorReporter) {} + + bool check(); + +private: + + virtual bool visit(FunctionDefinition const& _funDef) override; + virtual void endVisit(FunctionDefinition const& _funDef) override; + virtual bool visit(ModifierDefinition const& _modifierDef) override; + virtual void endVisit(ModifierDefinition const& _modifierDef) override; + virtual void endVisit(Identifier const& _identifier) override; + virtual void endVisit(MemberAccess const& _memberAccess) override; + virtual void endVisit(IndexAccess const& _indexAccess) override; + virtual void endVisit(ModifierInvocation const& _modifier) override; + virtual void endVisit(FunctionCall const& _functionCall) override; + virtual void endVisit(InlineAssembly const& _inlineAssembly) override; + + /// Called when an element of mutability @a _mutability is encountered. + /// Creates appropriate warnings and errors and sets @a m_currentBestMutability. + void reportMutability(StateMutability _mutability, ASTNode const& _node); + + std::vector<std::shared_ptr<ASTNode>> const& m_ast; + ErrorReporter& m_errorReporter; + + bool m_errors = false; + StateMutability m_currentBestMutability = StateMutability::Payable; + FunctionDefinition const* m_currentFunction = nullptr; + std::map<ModifierDefinition const*, StateMutability> m_inferredMutability; +}; + +} +} |