diff --git a/libsolidity/CMakeLists.txt b/libsolidity/CMakeLists.txt index 24b7d2d85..246a141d4 100644 --- a/libsolidity/CMakeLists.txt +++ b/libsolidity/CMakeLists.txt @@ -44,6 +44,8 @@ set(sources analysis/SyntaxChecker.h analysis/TypeChecker.cpp analysis/TypeChecker.h + analysis/VariableCanBeImmutable.cpp + analysis/VariableCanBeImmutable.h analysis/ViewPureChecker.cpp analysis/ViewPureChecker.h ast/AST.cpp diff --git a/libsolidity/analysis/VariableCanBeImmutable.cpp b/libsolidity/analysis/VariableCanBeImmutable.cpp new file mode 100644 index 000000000..f8d7c905e --- /dev/null +++ b/libsolidity/analysis/VariableCanBeImmutable.cpp @@ -0,0 +1,125 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include +#include + +#include + +#include +#include +#include + +#include + +using namespace std; +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::frontend; + + +bool VariableCanBeImmutable::visit(ContractDefinition const& _contractDefinition) +{ + m_contract = &_contractDefinition; + auto const& edges = (*_contractDefinition.annotation().deployedCallGraph)->edges; + frontend::CallGraph::Node _node = frontend::CallGraph::SpecialNode::Entry; + // A contract with no additional functions + if (!edges.count(_node)) + return true; + auto functions = + edges.at(_node) | + ranges::views::transform( + [](auto const& _node) -> optional + { + auto callable = get(_node); + solAssert(callable, ""); + if (auto functionDefinition = dynamic_cast(callable)) + return functionDefinition; + return std::nullopt; + } + ) | + ranges::views::filter([](auto const& _optional) { return _optional.has_value(); }) | + ranges::views::transform([](auto const& _optional) { return *_optional; }) | + ranges::to> + ; + m_reachableFunctions = util::BreadthFirstSearch{functions}.run( + [&](FunctionDefinition const* _function, auto&& _addChild) + { + for (CallGraph::Node const& node: edges.at(_function)) + { + auto callable = get(node); + solAssert(callable, ""); + if (auto function = dynamic_cast(callable)) + _addChild(function); + } + } + ).visited; + + return true; +} + +void VariableCanBeImmutable::endVisit(ContractDefinition const& _contractDefinition) +{ + for (auto const& variable: _contractDefinition.stateVariables()) + if ( + !variable->immutable() && + !m_variablesWrittenTo.count(variable) && + !variable->isConstant() + ) + m_errorReporter.warning( + 0000_error, + variable->location(), + "Variable declaration can be converted into an immutable." + ); + + m_variablesWrittenTo.clear(); + m_contract = nullptr; +} + +bool VariableCanBeImmutable::visit(FunctionDefinition const& _functionDefinition) +{ + m_function = &_functionDefinition; + + return true; +} +void VariableCanBeImmutable::endVisit(FunctionDefinition const& ) +{ + m_function = nullptr; +} + +void VariableCanBeImmutable::endVisit(Identifier const& _identifier) +{ + Declaration const* declaration = _identifier.annotation().referencedDeclaration; + solAssert(declaration, ""); + + bool writes = _identifier.annotation().willBeWrittenTo; + if (VariableDeclaration const* varDecl = dynamic_cast(declaration)) + if ( + writes && + !varDecl->immutable() && + varDecl->isStateVariable() && + !varDecl->isConstant() + ) + { + solAssert(m_contract, ""); + if (m_function && m_reachableFunctions.count(m_function)) + m_variablesWrittenTo.insert(varDecl); + } +} diff --git a/libsolidity/analysis/VariableCanBeImmutable.h b/libsolidity/analysis/VariableCanBeImmutable.h new file mode 100644 index 000000000..6ecbb64f4 --- /dev/null +++ b/libsolidity/analysis/VariableCanBeImmutable.h @@ -0,0 +1,59 @@ +/* + 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 . +*/ +// SPDX-License-Identifier: GPL-3.0 + +#include +#include +#include +#include + +using namespace std; +using namespace solidity; +using namespace solidity::langutil; +using namespace solidity::frontend; + +namespace solidity::langutil +{ +class ErrorReporter; +struct SourceLocation; +} + + +/// Need to figure out the control flow graph. If a state variable is never +/// assigned outside of constructor context, then we can make it immutable. +struct VariableCanBeImmutable: public ASTConstVisitor +{ + VariableCanBeImmutable(ErrorReporter& _errorReporter): + m_errorReporter(_errorReporter) + {} + + bool visit(ContractDefinition const& _contractDefinition) override; + void endVisit(ContractDefinition const& _contractDefinition) override; + bool visit(FunctionDefinition const& _functionDefinition) override; + void endVisit(FunctionDefinition const& ) override; + void endVisit(Identifier const& _identifier) override; +private: + /// The current contract + ContractDefinition const* m_contract = nullptr; + /// The set of functions that can be called from external + std::set m_reachableFunctions; + /// Current function; + FunctionDefinition const* m_function; + /// Variables that are written to in deploy code. + std::set m_variablesWrittenTo; + ErrorReporter& m_errorReporter; +}; diff --git a/libsolidity/interface/CompilerStack.cpp b/libsolidity/interface/CompilerStack.cpp index dcc07e1f5..83d484626 100644 --- a/libsolidity/interface/CompilerStack.cpp +++ b/libsolidity/interface/CompilerStack.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -554,6 +555,16 @@ bool CompilerStack::analyze() noErrors = false; } + if (noErrors) + { + VariableCanBeImmutable v(m_errorReporter); + for (Source const* source: m_sourceOrder) + if (source->ast) + { + source->ast->accept(v); + } + } + if (noErrors) { ModelChecker modelChecker(m_errorReporter, m_smtlib2Responses, m_modelCheckerSettings, m_readFile, m_enabledSMTSolvers);