mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
ControlFlowAnalyser: Also consider called functions in a flow
This commit is contained in:
parent
3dfa68a574
commit
56ebea8b2f
@ -16,6 +16,7 @@ Compiler Features:
|
||||
|
||||
Bugfixes:
|
||||
* AST: Do not output value of Yul literal if it is not a valid UTF-8 string.
|
||||
* Control Flow Graph: Take internal calls to functions that always revert into account for reporting unused or unassigned variables.
|
||||
* SMTChecker: Fix internal error on struct constructor with fixed bytes member initialized with string literal.
|
||||
* Standard JSON: Properly allow the ``inliner`` setting under ``settings.optimizer.details``.
|
||||
* Type Checker: Fix internal compiler error related to having mapping types in constructor parameter for abstract contracts.
|
||||
@ -25,6 +26,7 @@ AST Changes:
|
||||
* Add member `hexValue` for Yul string and hex literals.
|
||||
|
||||
|
||||
|
||||
### 0.8.4 (2021-04-21)
|
||||
|
||||
Important Bugfixes:
|
||||
|
@ -10,6 +10,8 @@ set(sources
|
||||
analysis/ControlFlowBuilder.h
|
||||
analysis/ControlFlowGraph.cpp
|
||||
analysis/ControlFlowGraph.h
|
||||
analysis/ControlFlowRevertPruner.cpp
|
||||
analysis/ControlFlowRevertPruner.h
|
||||
analysis/DeclarationContainer.cpp
|
||||
analysis/DeclarationContainer.h
|
||||
analysis/DeclarationTypeChecker.cpp
|
||||
|
@ -22,28 +22,45 @@
|
||||
#include <libsolutil/Algorithms.h>
|
||||
#include <boost/range/algorithm/sort.hpp>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using namespace std;
|
||||
using namespace std::placeholders;
|
||||
using namespace solidity::langutil;
|
||||
using namespace solidity::frontend;
|
||||
|
||||
bool ControlFlowAnalyzer::analyze(ASTNode const& _astRoot)
|
||||
|
||||
bool ControlFlowAnalyzer::run()
|
||||
{
|
||||
_astRoot.accept(*this);
|
||||
for (auto& [pair, flow]: m_cfg.allFunctionFlows())
|
||||
analyze(*pair.function, pair.contract, *flow);
|
||||
|
||||
return Error::containsOnlyWarnings(m_errorReporter.errors());
|
||||
}
|
||||
|
||||
bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
|
||||
void ControlFlowAnalyzer::analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow)
|
||||
{
|
||||
if (_function.isImplemented())
|
||||
{
|
||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit, _function.body().statements().empty());
|
||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
||||
}
|
||||
return false;
|
||||
if (!_function.isImplemented())
|
||||
return;
|
||||
|
||||
optional<string> mostDerivedContractName;
|
||||
|
||||
// The name of the most derived contract only required if it differs from
|
||||
// the functions contract
|
||||
if (_contract && _contract != _function.annotation().contract)
|
||||
mostDerivedContractName = _contract->name();
|
||||
|
||||
checkUninitializedAccess(
|
||||
_flow.entry,
|
||||
_flow.exit,
|
||||
_function.body().statements().empty(),
|
||||
mostDerivedContractName
|
||||
);
|
||||
checkUnreachable(_flow.entry, _flow.exit, _flow.revert, _flow.transactionReturn);
|
||||
}
|
||||
|
||||
void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody) const
|
||||
|
||||
void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody, optional<string> _contractName)
|
||||
{
|
||||
struct NodeInfo
|
||||
{
|
||||
@ -156,16 +173,27 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
||||
" without prior assignment, which would lead to undefined behaviour."
|
||||
);
|
||||
else if (!_emptyBody && varDecl.name().empty())
|
||||
{
|
||||
if (!m_unassignedReturnVarsAlreadyWarnedFor.emplace(&varDecl).second)
|
||||
continue;
|
||||
|
||||
m_errorReporter.warning(
|
||||
6321_error,
|
||||
varDecl.location(),
|
||||
"Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable."
|
||||
"Unnamed return variable can remain unassigned" +
|
||||
(
|
||||
_contractName.has_value() ?
|
||||
" when the function is called when \"" + _contractName.value() + "\" is the most derived contract." :
|
||||
"."
|
||||
) +
|
||||
" Add an explicit return with value to all non-reverting code paths or name the variable."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
|
||||
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn)
|
||||
{
|
||||
// collect all nodes reachable from the entry point
|
||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
||||
@ -193,6 +221,8 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
|
||||
// Extend the location, as long as the next location overlaps (unreachable is sorted).
|
||||
for (; it != unreachable.end() && it->start <= location.end; ++it)
|
||||
location.end = std::max(location.end, it->end);
|
||||
m_errorReporter.warning(5740_error, location, "Unreachable code.");
|
||||
|
||||
if (m_unreachableLocationsAlreadyWarnedFor.emplace(location).second)
|
||||
m_errorReporter.warning(5740_error, location, "Unreachable code.");
|
||||
}
|
||||
}
|
||||
|
@ -19,30 +19,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
#include <liblangutil/ErrorReporter.h>
|
||||
#include <set>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
class ControlFlowAnalyzer: private ASTConstVisitor
|
||||
class ControlFlowAnalyzer
|
||||
{
|
||||
public:
|
||||
explicit ControlFlowAnalyzer(CFG const& _cfg, langutil::ErrorReporter& _errorReporter):
|
||||
m_cfg(_cfg), m_errorReporter(_errorReporter) {}
|
||||
|
||||
bool analyze(ASTNode const& _astRoot);
|
||||
|
||||
bool visit(FunctionDefinition const& _function) override;
|
||||
bool run();
|
||||
|
||||
private:
|
||||
void analyze(FunctionDefinition const& _function, ContractDefinition const* _contract, FunctionFlow const& _flow);
|
||||
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
|
||||
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody) const;
|
||||
/// @param _entry entry node
|
||||
/// @param _exit exit node
|
||||
/// @param _emptyBody whether the body of the function is empty (true) or not (false)
|
||||
/// @param _contractName name of the most derived contract, should be empty
|
||||
/// if the function is also defined in it
|
||||
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit, bool _emptyBody, std::optional<std::string> _contractName = {});
|
||||
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
|
||||
/// that can not be reached from @param _entry.
|
||||
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
|
||||
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn);
|
||||
|
||||
CFG const& m_cfg;
|
||||
langutil::ErrorReporter& m_errorReporter;
|
||||
|
||||
std::set<langutil::SourceLocation> m_unreachableLocationsAlreadyWarnedFor;
|
||||
std::set<VariableDeclaration const*> m_unassignedReturnVarsAlreadyWarnedFor;
|
||||
};
|
||||
|
||||
}
|
||||
|
@ -287,6 +287,11 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
|
||||
m_currentNode = nextNode;
|
||||
return false;
|
||||
}
|
||||
case FunctionType::Kind::Internal:
|
||||
{
|
||||
m_currentNode->functionCalls.emplace_back(&_functionCall);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -34,15 +34,25 @@ bool CFG::constructFlow(ASTNode const& _astRoot)
|
||||
|
||||
bool CFG::visit(FunctionDefinition const& _function)
|
||||
{
|
||||
if (_function.isImplemented())
|
||||
m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
|
||||
if (_function.isImplemented() && _function.isFree())
|
||||
m_functionControlFlow[{nullptr, &_function}] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
|
||||
return false;
|
||||
}
|
||||
|
||||
FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const
|
||||
bool CFG::visit(ContractDefinition const& _contract)
|
||||
{
|
||||
solAssert(m_functionControlFlow.count(&_function), "");
|
||||
return *m_functionControlFlow.find(&_function)->second;
|
||||
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
|
||||
for (FunctionDefinition const* function: contract->definedFunctions())
|
||||
if (function->isImplemented())
|
||||
m_functionControlFlow[{&_contract, function}] =
|
||||
ControlFlowBuilder::createFunctionFlow(m_nodeContainer, *function);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function, ContractDefinition const* _contract) const
|
||||
{
|
||||
return *m_functionControlFlow.at({_contract, &_function});
|
||||
}
|
||||
|
||||
CFGNode* CFG::NodeContainer::newNode()
|
||||
|
@ -98,6 +98,8 @@ struct CFGNode
|
||||
std::vector<CFGNode*> entries;
|
||||
/// Exit nodes. All CFG nodes to which control flow may continue after this node.
|
||||
std::vector<CFGNode*> exits;
|
||||
/// Function calls done by this node
|
||||
std::vector<FunctionCall const*> functionCalls;
|
||||
|
||||
/// Variable occurrences in the node.
|
||||
std::vector<VariableOccurrence> variableOccurrences;
|
||||
@ -118,7 +120,7 @@ struct FunctionFlow
|
||||
/// (e.g. all return statements of the function).
|
||||
CFGNode* exit = nullptr;
|
||||
/// Revert node. Control flow of the function in case of revert.
|
||||
/// This node is empty does not have any exits, but may have multiple entries
|
||||
/// This node is empty and does not have any exits, but may have multiple entries
|
||||
/// (e.g. all assert, require, revert and throw statements).
|
||||
CFGNode* revert = nullptr;
|
||||
/// Transaction return node. Destination node for inline assembly "return" calls.
|
||||
@ -130,13 +132,39 @@ struct FunctionFlow
|
||||
class CFG: private ASTConstVisitor
|
||||
{
|
||||
public:
|
||||
struct FunctionContractTuple
|
||||
{
|
||||
ContractDefinition const* contract = nullptr;
|
||||
FunctionDefinition const* function = nullptr;
|
||||
|
||||
// Use AST ids for comparison to keep a deterministic order in the
|
||||
// containers using this struct
|
||||
bool operator<(FunctionContractTuple const& _other) const
|
||||
{
|
||||
if (contract && _other.contract)
|
||||
if (contract->id() != _other.contract->id())
|
||||
return contract->id() < _other.contract->id();
|
||||
|
||||
return function->id() < _other.function->id();
|
||||
}
|
||||
};
|
||||
explicit CFG(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
|
||||
|
||||
bool constructFlow(ASTNode const& _astRoot);
|
||||
|
||||
bool visit(FunctionDefinition const& _function) override;
|
||||
bool visit(ContractDefinition const& _contract) override;
|
||||
|
||||
FunctionFlow const& functionFlow(FunctionDefinition const& _function) const;
|
||||
/// Get the function flow for the given function, using `_contract` as the
|
||||
/// most derived contract
|
||||
/// @param _function function to find the function flow for
|
||||
/// @param _contract most derived contract or nullptr for free functions
|
||||
FunctionFlow const& functionFlow(FunctionDefinition const& _function, ContractDefinition const* _contract = nullptr) const;
|
||||
|
||||
std::map<FunctionContractTuple, std::unique_ptr<FunctionFlow>> const& allFunctionFlows() const
|
||||
{
|
||||
return m_functionControlFlow;
|
||||
}
|
||||
|
||||
class NodeContainer
|
||||
{
|
||||
@ -153,7 +181,7 @@ private:
|
||||
/// are owned by the CFG class and stored in this container.
|
||||
NodeContainer m_nodeContainer;
|
||||
|
||||
std::map<FunctionDefinition const*, std::unique_ptr<FunctionFlow>> m_functionControlFlow;
|
||||
std::map<FunctionContractTuple, std::unique_ptr<FunctionFlow>> m_functionControlFlow;
|
||||
};
|
||||
|
||||
}
|
||||
|
227
libsolidity/analysis/ControlFlowRevertPruner.cpp
Normal file
227
libsolidity/analysis/ControlFlowRevertPruner.cpp
Normal file
@ -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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#include <libsolidity/analysis/ControlFlowRevertPruner.h>
|
||||
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
/// Find the right scope for the called function: When calling a base function, we keep the most derived, but we use the called contract in case it is a library function or nullptr for a free function
|
||||
ContractDefinition const* findScopeContract(FunctionDefinition const& _function, ContractDefinition const* _callingContract)
|
||||
{
|
||||
if (auto const* functionContract = _function.annotation().contract)
|
||||
{
|
||||
if (_callingContract && _callingContract->derivesFrom(*functionContract))
|
||||
return _callingContract;
|
||||
else
|
||||
return functionContract;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowRevertPruner::run()
|
||||
{
|
||||
// build a lookup table for function calls / callers
|
||||
for (auto& [pair, flow]: m_cfg.allFunctionFlows())
|
||||
collectCalls(*pair.function, pair.contract);
|
||||
|
||||
findRevertStates();
|
||||
modifyFunctionFlows();
|
||||
}
|
||||
|
||||
FunctionDefinition const* ControlFlowRevertPruner::resolveCall(FunctionCall const& _functionCall, ContractDefinition const* _contract)
|
||||
{
|
||||
auto result = m_resolveCache.find({&_functionCall, _contract});
|
||||
if (result != m_resolveCache.end())
|
||||
return result->second;
|
||||
|
||||
auto const& functionType = dynamic_cast<FunctionType const&>(
|
||||
*_functionCall.expression().annotation().type
|
||||
);
|
||||
|
||||
if (!functionType.hasDeclaration())
|
||||
return nullptr;
|
||||
|
||||
auto const& unresolvedFunctionDefinition =
|
||||
dynamic_cast<FunctionDefinition const&>(functionType.declaration());
|
||||
|
||||
FunctionDefinition const* returnFunctionDef = &unresolvedFunctionDefinition;
|
||||
|
||||
if (auto const* memberAccess = dynamic_cast<MemberAccess const*>(&_functionCall.expression()))
|
||||
{
|
||||
if (*memberAccess->annotation().requiredLookup == VirtualLookup::Super)
|
||||
{
|
||||
if (auto const typeType = dynamic_cast<TypeType const*>(memberAccess->expression().annotation().type))
|
||||
if (auto const contractType = dynamic_cast<ContractType const*>(typeType->actualType()))
|
||||
{
|
||||
solAssert(contractType->isSuper(), "");
|
||||
ContractDefinition const* superContract = contractType->contractDefinition().superContract(*_contract);
|
||||
|
||||
returnFunctionDef = &unresolvedFunctionDefinition.resolveVirtual(
|
||||
*_contract,
|
||||
superContract
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
solAssert(*memberAccess->annotation().requiredLookup == VirtualLookup::Static, "");
|
||||
returnFunctionDef = &unresolvedFunctionDefinition;
|
||||
}
|
||||
}
|
||||
else if (auto const* identifier = dynamic_cast<Identifier const*>(&_functionCall.expression()))
|
||||
{
|
||||
solAssert(*identifier->annotation().requiredLookup == VirtualLookup::Virtual, "");
|
||||
returnFunctionDef = &unresolvedFunctionDefinition.resolveVirtual(*_contract);
|
||||
}
|
||||
|
||||
if (returnFunctionDef && !returnFunctionDef->isImplemented())
|
||||
returnFunctionDef = nullptr;
|
||||
|
||||
return m_resolveCache[{&_functionCall, _contract}] = returnFunctionDef;
|
||||
}
|
||||
|
||||
void ControlFlowRevertPruner::findRevertStates()
|
||||
{
|
||||
std::set<CFG::FunctionContractTuple> pendingFunctions = keys(m_functions);
|
||||
|
||||
while (!pendingFunctions.empty())
|
||||
{
|
||||
CFG::FunctionContractTuple item = *pendingFunctions.begin();
|
||||
pendingFunctions.erase(pendingFunctions.begin());
|
||||
|
||||
if (m_functions[item] != RevertState::Unknown)
|
||||
continue;
|
||||
|
||||
bool foundExit = false;
|
||||
bool foundUnknown = false;
|
||||
|
||||
FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.function, item.contract);
|
||||
|
||||
solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run(
|
||||
[&](CFGNode* _node, auto&& _addChild) {
|
||||
if (_node == functionFlow.exit)
|
||||
foundExit = true;
|
||||
|
||||
for (auto const* functionCall: _node->functionCalls)
|
||||
{
|
||||
auto const* resolvedFunction = resolveCall(*functionCall, item.contract);
|
||||
|
||||
if (resolvedFunction == nullptr)
|
||||
continue;
|
||||
|
||||
switch (m_functions.at({findScopeContract(*resolvedFunction, item.contract), resolvedFunction}))
|
||||
{
|
||||
case RevertState::Unknown:
|
||||
foundUnknown = true;
|
||||
return;
|
||||
case RevertState::AllPathsRevert:
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (CFGNode* exit: _node->exits)
|
||||
_addChild(exit);
|
||||
});
|
||||
|
||||
auto& revertState = m_functions[item];
|
||||
|
||||
if (foundExit)
|
||||
revertState = RevertState::HasNonRevertingPath;
|
||||
else if (!foundUnknown)
|
||||
revertState = RevertState::AllPathsRevert;
|
||||
|
||||
// Mark all functions depending on this one as modified again
|
||||
if (revertState != RevertState::Unknown)
|
||||
for (auto& nextItem: m_calledBy[item.function])
|
||||
// Ignore different most derived contracts in dependent callees
|
||||
if (
|
||||
item.contract == nullptr ||
|
||||
nextItem.contract == nullptr ||
|
||||
nextItem.contract == item.contract
|
||||
)
|
||||
pendingFunctions.insert(nextItem);
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowRevertPruner::modifyFunctionFlows()
|
||||
{
|
||||
for (auto& item: m_functions)
|
||||
{
|
||||
FunctionFlow const& functionFlow = m_cfg.functionFlow(*item.first.function, item.first.contract);
|
||||
solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run(
|
||||
[&](CFGNode* _node, auto&& _addChild) {
|
||||
for (auto const* functionCall: _node->functionCalls)
|
||||
{
|
||||
auto const* resolvedFunction = resolveCall(*functionCall, item.first.contract);
|
||||
|
||||
if (resolvedFunction == nullptr)
|
||||
continue;
|
||||
|
||||
switch (m_functions.at({findScopeContract(*resolvedFunction, item.first.contract), resolvedFunction}))
|
||||
{
|
||||
case RevertState::Unknown:
|
||||
[[fallthrough]];
|
||||
case RevertState::AllPathsRevert:
|
||||
// If the revert states of the functions do not
|
||||
// change anymore, we treat all "unknown" states as
|
||||
// "reverting", since they can only be caused by
|
||||
// recursion.
|
||||
_node->exits = {functionFlow.revert};
|
||||
return;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (CFGNode* exit: _node->exits)
|
||||
_addChild(exit);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ControlFlowRevertPruner::collectCalls(FunctionDefinition const& _function, ContractDefinition const* _mostDerivedContract)
|
||||
{
|
||||
FunctionFlow const& functionFlow = m_cfg.functionFlow(_function, _mostDerivedContract);
|
||||
|
||||
CFG::FunctionContractTuple pair{_mostDerivedContract, &_function};
|
||||
|
||||
solAssert(m_functions.count(pair) == 0, "");
|
||||
m_functions[pair] = RevertState::Unknown;
|
||||
|
||||
solidity::util::BreadthFirstSearch<CFGNode*>{{functionFlow.entry}}.run(
|
||||
[&](CFGNode* _node, auto&& _addChild) {
|
||||
for (auto const* functionCall: _node->functionCalls)
|
||||
m_calledBy[resolveCall(*functionCall, _mostDerivedContract)].insert(pair);
|
||||
|
||||
for (CFGNode* exit: _node->exits)
|
||||
_addChild(exit);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
87
libsolidity/analysis/ControlFlowRevertPruner.h
Normal file
87
libsolidity/analysis/ControlFlowRevertPruner.h
Normal file
@ -0,0 +1,87 @@
|
||||
/*
|
||||
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/>.
|
||||
*/
|
||||
// SPDX-License-Identifier: GPL-3.0
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <libsolidity/ast/AST.h>
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
|
||||
#include <libsolutil/Algorithms.h>
|
||||
|
||||
namespace solidity::frontend
|
||||
{
|
||||
|
||||
/**
|
||||
* Analyses all function flows and recursively removes all exit edges from CFG
|
||||
* nodes that make function calls that will always revert.
|
||||
*/
|
||||
class ControlFlowRevertPruner
|
||||
{
|
||||
public:
|
||||
ControlFlowRevertPruner(CFG& _cfg): m_cfg(_cfg) {}
|
||||
|
||||
void run();
|
||||
private:
|
||||
/// Possible revert states of a function call
|
||||
enum class RevertState
|
||||
{
|
||||
AllPathsRevert,
|
||||
HasNonRevertingPath,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
/// Simple attempt at resolving a function call
|
||||
/// Does not aim to be able to resolve all calls, only used for variable
|
||||
/// assignment tracking and revert behavior.
|
||||
/// @param _functionCall the function call to analyse
|
||||
/// @param _mostDerivedContract most derived contract
|
||||
/// @returns function definition to which the call resolved or nullptr if no
|
||||
/// definition was found.
|
||||
FunctionDefinition const* resolveCall(FunctionCall const& _functionCall, ContractDefinition const* _mostDerivedContract);
|
||||
|
||||
/// Identify revert states of all function flows
|
||||
void findRevertStates();
|
||||
|
||||
/// Modify function flows so that edges with reverting function calls are removed
|
||||
void modifyFunctionFlows();
|
||||
|
||||
|
||||
/// Collect all function calls made by `_function` for later analysis
|
||||
/// @param _function function to analyse
|
||||
/// @param _mostDerivedContract most derived contract used in the calls
|
||||
void collectCalls(FunctionDefinition const& _function, ContractDefinition const* _mostDerivedContract);
|
||||
|
||||
/// Control Flow Graph object.
|
||||
CFG& m_cfg;
|
||||
|
||||
/// function/contract pairs mapped to their according revert state
|
||||
std::map<CFG::FunctionContractTuple, RevertState> m_functions;
|
||||
|
||||
/// Called function mapped to the set of function/contract pairs calling them
|
||||
std::map<
|
||||
FunctionDefinition const*,
|
||||
std::set<CFG::FunctionContractTuple>
|
||||
> m_calledBy;
|
||||
|
||||
std::map<
|
||||
std::tuple<FunctionCall const*, ContractDefinition const*>,
|
||||
FunctionDefinition const*
|
||||
> m_resolveCache;
|
||||
|
||||
};
|
||||
}
|
@ -28,6 +28,7 @@
|
||||
|
||||
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
|
||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||
#include <libsolidity/analysis/ControlFlowRevertPruner.h>
|
||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||
@ -516,10 +517,12 @@ bool CompilerStack::analyze()
|
||||
|
||||
if (noErrors)
|
||||
{
|
||||
ControlFlowRevertPruner pruner(cfg);
|
||||
pruner.run();
|
||||
|
||||
ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter);
|
||||
for (Source const* source: m_sourceOrder)
|
||||
if (source->ast && !controlFlowAnalyzer.analyze(*source->ast))
|
||||
noErrors = false;
|
||||
if (!controlFlowAnalyzer.run())
|
||||
noErrors = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -9,5 +9,3 @@ contract C {
|
||||
// ====
|
||||
// SMTEngine: all
|
||||
// ----
|
||||
// Warning 6321: (48-52): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (54-58): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
|
@ -23,18 +23,5 @@ a;
|
||||
// ====
|
||||
// SMTEngine: all
|
||||
// ----
|
||||
// Warning 6321: (130-134): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (138-142): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (146-150): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (154-158): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (162-166): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (170-174): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (178-182): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (186-190): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (194-198): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (202-206): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (208-211): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (213-217): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (219-226): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6133: (39-57): Statement has no effect.
|
||||
// Warning 6133: (63-74): Statement has no effect.
|
||||
|
@ -17,3 +17,5 @@ contract C
|
||||
// ====
|
||||
// SMTEngine: all
|
||||
// ----
|
||||
// Warning 5740: (137-157): Unreachable code.
|
||||
// Warning 5740: (199-237): Unreachable code.
|
||||
|
@ -0,0 +1,12 @@
|
||||
contract C
|
||||
{
|
||||
function iWillRevert() pure public { revert(); }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,11 @@
|
||||
function iWillRevert() pure { revert(); }
|
||||
|
||||
contract C {
|
||||
function test(bool _param) pure external returns(uint256) {
|
||||
if (_param)
|
||||
return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,17 @@
|
||||
library L
|
||||
{
|
||||
function iWillRevert() public pure { revert(); }
|
||||
}
|
||||
|
||||
contract C
|
||||
{
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
L.iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
||||
// Warning 6321: (128-135): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,13 @@
|
||||
contract C
|
||||
{
|
||||
function iWillRevertLevel1() pure public { revert(); }
|
||||
function iWillRevert() pure public { iWillRevertLevel1(); }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
contract C
|
||||
{
|
||||
function iWillRevertLevel2() pure public { revert(); }
|
||||
function iWillRevertLevel1() pure public { iWillRevertLevel2(); }
|
||||
function iWillRevert() pure public { iWillRevertLevel1(); }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,22 @@
|
||||
contract C
|
||||
{
|
||||
function iWillRevertLevel2(bool _recurse) pure public
|
||||
{
|
||||
if (_recurse)
|
||||
iWillRevertLevel1();
|
||||
else
|
||||
revert();
|
||||
}
|
||||
|
||||
function iWillRevertLevel1() pure public { iWillRevertLevel2(true); }
|
||||
function iWillRevert() pure public { iWillRevertLevel1(); }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
@ -0,0 +1,23 @@
|
||||
abstract contract B
|
||||
{
|
||||
function iWillRevert() pure public virtual { revert(); }
|
||||
}
|
||||
|
||||
abstract contract X
|
||||
{
|
||||
function iWillRevert() pure public virtual { revert(); }
|
||||
}
|
||||
|
||||
contract C is B, X
|
||||
{
|
||||
function iWillRevert() pure public override(B, X) { }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
B.iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
@ -0,0 +1,18 @@
|
||||
abstract contract B
|
||||
{
|
||||
function iWillRevert() pure public virtual { revert(); }
|
||||
}
|
||||
|
||||
contract C is B
|
||||
{
|
||||
function iWillRevert() pure public override { }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
super.iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
@ -0,0 +1,27 @@
|
||||
abstract contract B
|
||||
{
|
||||
function iWillRevert() pure public virtual { revert(); }
|
||||
|
||||
function test2(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
contract C is B
|
||||
{
|
||||
function iWillRevert() pure public override { }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
||||
// Warning 6321: (146-153): Unnamed return variable can remain unassigned when the function is called when "C" is the most derived contract. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (381-388): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,18 @@
|
||||
abstract contract B
|
||||
{
|
||||
function iWillRevert() pure public virtual { }
|
||||
}
|
||||
|
||||
contract C is B
|
||||
{
|
||||
function iWillRevert() pure public override { revert(); }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
// ----
|
@ -0,0 +1,19 @@
|
||||
abstract contract B
|
||||
{
|
||||
function iWillRevert() pure public virtual { }
|
||||
|
||||
function test(bool _param) pure external returns(uint256)
|
||||
{
|
||||
if (_param) return 1;
|
||||
|
||||
iWillRevert();
|
||||
}
|
||||
}
|
||||
|
||||
contract C is B
|
||||
{
|
||||
function iWillRevert() pure public override { revert(); }
|
||||
}
|
||||
|
||||
// ----
|
||||
// Warning 6321: (135-142): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,11 @@
|
||||
contract A {
|
||||
function f() public virtual returns (uint) { g(); }
|
||||
function g() internal virtual { revert(); }
|
||||
}
|
||||
contract B is A {
|
||||
function f() public override returns (uint) { A.f(); }
|
||||
function g() internal override {}
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (52-56): Unnamed return variable can remain unassigned when the function is called when "B" is the most derived contract. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (173-177): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,16 @@
|
||||
contract A {
|
||||
function f() public virtual returns (uint) { g(); }
|
||||
function g() internal virtual { revert(); }
|
||||
}
|
||||
contract B is A {
|
||||
function f() public virtual override returns (uint) { A.f(); }
|
||||
function g() internal virtual override { A.g(); }
|
||||
}
|
||||
contract C is B {
|
||||
function f() public virtual override returns (uint) { A.f(); }
|
||||
function g() internal virtual override { }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (52-56): Unnamed return variable can remain unassigned when the function is called when "C" is the most derived contract. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (181-185): Unnamed return variable can remain unassigned when the function is called when "C" is the most derived contract. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (318-322): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,12 @@
|
||||
==== Source: s1.sol ====
|
||||
function normal() pure returns (uint) { return 1337; }
|
||||
function reverting() pure returns (uint) { revert(); }
|
||||
==== Source: s2.sol ====
|
||||
import "s1.sol";
|
||||
contract C
|
||||
{
|
||||
function foo() public pure returns (uint) { normal(); }
|
||||
function bar() public pure returns (uint) { reverting(); }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (s2.sol:67-71): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,16 @@
|
||||
==== Source: s1.sol ====
|
||||
library L
|
||||
{
|
||||
function normal() public pure returns (uint) { return 1337; }
|
||||
function reverting() public pure returns (uint) { revert(); }
|
||||
}
|
||||
==== Source: s2.sol ====
|
||||
import "s1.sol";
|
||||
contract C
|
||||
{
|
||||
function foo() public pure returns (uint) { L.normal(); }
|
||||
function bar() public pure returns (uint) { L.reverting(); }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (s2.sol:67-71): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (s2.sol:126-130): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,15 @@
|
||||
==== Source: s1.sol ====
|
||||
contract C
|
||||
{
|
||||
function normal() public pure returns (uint) { return 1337; }
|
||||
function reverting() public pure returns (uint) { revert(); }
|
||||
}
|
||||
==== Source: s2.sol ====
|
||||
import "s1.sol";
|
||||
contract D is C
|
||||
{
|
||||
function foo() public pure returns (uint) { normal(); }
|
||||
function bar() public pure returns (uint) { reverting(); }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (s2.sol:72-76): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -0,0 +1,24 @@
|
||||
==== Source: s1.sol ====
|
||||
contract C
|
||||
{
|
||||
function normal(bool x) public pure returns (uint)
|
||||
{
|
||||
if (x)
|
||||
return xxx();
|
||||
else
|
||||
return yyy();
|
||||
}
|
||||
function yyy() public pure returns (uint) { revert(); }
|
||||
function bar() public pure returns (uint) { normal(true); }
|
||||
|
||||
function xxx() public virtual pure returns (uint) { return 1; }
|
||||
}
|
||||
==== Source: s2.sol ====
|
||||
import "s1.sol";
|
||||
contract D is C
|
||||
{
|
||||
function foo() public pure returns (uint) { normal(false); }
|
||||
function xxx() public override pure returns(uint) { revert(); }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (s1.sol:215-219): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
@ -5,4 +5,3 @@ contract C {
|
||||
}
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (46-50): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
|
@ -10,8 +10,4 @@ contract C {
|
||||
function h() internal pure returns (bytes memory, string storage s) { s = s; }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (51-55): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (57-61): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (63-67): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (69-73): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
// Warning 6321: (250-262): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
|
@ -5,4 +5,3 @@ contract C {
|
||||
function i() payable public { i(); h(); g(); f(); }
|
||||
}
|
||||
// ----
|
||||
// Warning 6321: (89-93): Unnamed return variable can remain unassigned. Add an explicit return with value to all non-reverting code paths or name the variable.
|
||||
|
Loading…
Reference in New Issue
Block a user