mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
Merge pull request #10143 from ethereum/issue-10084
ControlFlowAnalyser: Also consider called functions in a flow
This commit is contained in:
commit
4cbf9ff74c
@ -16,6 +16,7 @@ Compiler Features:
|
|||||||
|
|
||||||
Bugfixes:
|
Bugfixes:
|
||||||
* AST: Do not output value of Yul literal if it is not a valid UTF-8 string.
|
* 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.
|
* SMTChecker: Fix internal error on struct constructor with fixed bytes member initialized with string literal.
|
||||||
* SMTChecker: Fix internal error on external calls from the constructor.
|
* SMTChecker: Fix internal error on external calls from the constructor.
|
||||||
* SMTChecker: Fix internal error on conversion from ``bytes`` to ``fixed bytes``.
|
* SMTChecker: Fix internal error on conversion from ``bytes`` to ``fixed bytes``.
|
||||||
@ -28,6 +29,7 @@ AST Changes:
|
|||||||
* Add member `hexValue` for Yul string and hex literals.
|
* Add member `hexValue` for Yul string and hex literals.
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
### 0.8.4 (2021-04-21)
|
### 0.8.4 (2021-04-21)
|
||||||
|
|
||||||
Important Bugfixes:
|
Important Bugfixes:
|
||||||
|
@ -10,6 +10,8 @@ set(sources
|
|||||||
analysis/ControlFlowBuilder.h
|
analysis/ControlFlowBuilder.h
|
||||||
analysis/ControlFlowGraph.cpp
|
analysis/ControlFlowGraph.cpp
|
||||||
analysis/ControlFlowGraph.h
|
analysis/ControlFlowGraph.h
|
||||||
|
analysis/ControlFlowRevertPruner.cpp
|
||||||
|
analysis/ControlFlowRevertPruner.h
|
||||||
analysis/DeclarationContainer.cpp
|
analysis/DeclarationContainer.cpp
|
||||||
analysis/DeclarationContainer.h
|
analysis/DeclarationContainer.h
|
||||||
analysis/DeclarationTypeChecker.cpp
|
analysis/DeclarationTypeChecker.cpp
|
||||||
|
@ -22,28 +22,45 @@
|
|||||||
#include <libsolutil/Algorithms.h>
|
#include <libsolutil/Algorithms.h>
|
||||||
#include <boost/range/algorithm/sort.hpp>
|
#include <boost/range/algorithm/sort.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
using namespace std::placeholders;
|
||||||
using namespace solidity::langutil;
|
using namespace solidity::langutil;
|
||||||
using namespace solidity::frontend;
|
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());
|
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())
|
if (!_function.isImplemented())
|
||||||
{
|
return;
|
||||||
auto const& functionFlow = m_cfg.functionFlow(_function);
|
|
||||||
checkUninitializedAccess(functionFlow.entry, functionFlow.exit, _function.body().statements().empty());
|
optional<string> mostDerivedContractName;
|
||||||
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
|
|
||||||
}
|
// The name of the most derived contract only required if it differs from
|
||||||
return false;
|
// 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
|
struct NodeInfo
|
||||||
{
|
{
|
||||||
@ -156,16 +173,27 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
|
|||||||
" without prior assignment, which would lead to undefined behaviour."
|
" without prior assignment, which would lead to undefined behaviour."
|
||||||
);
|
);
|
||||||
else if (!_emptyBody && varDecl.name().empty())
|
else if (!_emptyBody && varDecl.name().empty())
|
||||||
|
{
|
||||||
|
if (!m_unassignedReturnVarsAlreadyWarnedFor.emplace(&varDecl).second)
|
||||||
|
continue;
|
||||||
|
|
||||||
m_errorReporter.warning(
|
m_errorReporter.warning(
|
||||||
6321_error,
|
6321_error,
|
||||||
varDecl.location(),
|
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
|
// collect all nodes reachable from the entry point
|
||||||
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
|
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).
|
// Extend the location, as long as the next location overlaps (unreachable is sorted).
|
||||||
for (; it != unreachable.end() && it->start <= location.end; ++it)
|
for (; it != unreachable.end() && it->start <= location.end; ++it)
|
||||||
location.end = std::max(location.end, it->end);
|
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
|
#pragma once
|
||||||
|
|
||||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||||
|
#include <liblangutil/ErrorReporter.h>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
|
||||||
namespace solidity::frontend
|
namespace solidity::frontend
|
||||||
{
|
{
|
||||||
|
|
||||||
class ControlFlowAnalyzer: private ASTConstVisitor
|
class ControlFlowAnalyzer
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit ControlFlowAnalyzer(CFG const& _cfg, langutil::ErrorReporter& _errorReporter):
|
explicit ControlFlowAnalyzer(CFG const& _cfg, langutil::ErrorReporter& _errorReporter):
|
||||||
m_cfg(_cfg), m_errorReporter(_errorReporter) {}
|
m_cfg(_cfg), m_errorReporter(_errorReporter) {}
|
||||||
|
|
||||||
bool analyze(ASTNode const& _astRoot);
|
bool run();
|
||||||
|
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
|
||||||
|
|
||||||
private:
|
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.
|
/// 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
|
/// Checks for unreachable code, i.e. code ending in @param _exit, @param _revert or @param _transactionReturn
|
||||||
/// that can not be reached from @param _entry.
|
/// 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;
|
CFG const& m_cfg;
|
||||||
langutil::ErrorReporter& m_errorReporter;
|
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;
|
m_currentNode = nextNode;
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
case FunctionType::Kind::Internal:
|
||||||
|
{
|
||||||
|
m_currentNode->functionCalls.emplace_back(&_functionCall);
|
||||||
|
break;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,25 @@ bool CFG::constructFlow(ASTNode const& _astRoot)
|
|||||||
|
|
||||||
bool CFG::visit(FunctionDefinition const& _function)
|
bool CFG::visit(FunctionDefinition const& _function)
|
||||||
{
|
{
|
||||||
if (_function.isImplemented())
|
if (_function.isImplemented() && _function.isFree())
|
||||||
m_functionControlFlow[&_function] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
|
m_functionControlFlow[{nullptr, &_function}] = ControlFlowBuilder::createFunctionFlow(m_nodeContainer, _function);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
FunctionFlow const& CFG::functionFlow(FunctionDefinition const& _function) const
|
bool CFG::visit(ContractDefinition const& _contract)
|
||||||
{
|
{
|
||||||
solAssert(m_functionControlFlow.count(&_function), "");
|
for (ContractDefinition const* contract: _contract.annotation().linearizedBaseContracts)
|
||||||
return *m_functionControlFlow.find(&_function)->second;
|
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()
|
CFGNode* CFG::NodeContainer::newNode()
|
||||||
|
@ -98,6 +98,8 @@ struct CFGNode
|
|||||||
std::vector<CFGNode*> entries;
|
std::vector<CFGNode*> entries;
|
||||||
/// Exit nodes. All CFG nodes to which control flow may continue after this node.
|
/// Exit nodes. All CFG nodes to which control flow may continue after this node.
|
||||||
std::vector<CFGNode*> exits;
|
std::vector<CFGNode*> exits;
|
||||||
|
/// Function calls done by this node
|
||||||
|
std::vector<FunctionCall const*> functionCalls;
|
||||||
|
|
||||||
/// Variable occurrences in the node.
|
/// Variable occurrences in the node.
|
||||||
std::vector<VariableOccurrence> variableOccurrences;
|
std::vector<VariableOccurrence> variableOccurrences;
|
||||||
@ -118,7 +120,7 @@ struct FunctionFlow
|
|||||||
/// (e.g. all return statements of the function).
|
/// (e.g. all return statements of the function).
|
||||||
CFGNode* exit = nullptr;
|
CFGNode* exit = nullptr;
|
||||||
/// Revert node. Control flow of the function in case of revert.
|
/// 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).
|
/// (e.g. all assert, require, revert and throw statements).
|
||||||
CFGNode* revert = nullptr;
|
CFGNode* revert = nullptr;
|
||||||
/// Transaction return node. Destination node for inline assembly "return" calls.
|
/// Transaction return node. Destination node for inline assembly "return" calls.
|
||||||
@ -130,13 +132,39 @@ struct FunctionFlow
|
|||||||
class CFG: private ASTConstVisitor
|
class CFG: private ASTConstVisitor
|
||||||
{
|
{
|
||||||
public:
|
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) {}
|
explicit CFG(langutil::ErrorReporter& _errorReporter): m_errorReporter(_errorReporter) {}
|
||||||
|
|
||||||
bool constructFlow(ASTNode const& _astRoot);
|
bool constructFlow(ASTNode const& _astRoot);
|
||||||
|
|
||||||
bool visit(FunctionDefinition const& _function) override;
|
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
|
class NodeContainer
|
||||||
{
|
{
|
||||||
@ -153,7 +181,7 @@ private:
|
|||||||
/// are owned by the CFG class and stored in this container.
|
/// are owned by the CFG class and stored in this container.
|
||||||
NodeContainer m_nodeContainer;
|
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/ControlFlowAnalyzer.h>
|
||||||
#include <libsolidity/analysis/ControlFlowGraph.h>
|
#include <libsolidity/analysis/ControlFlowGraph.h>
|
||||||
|
#include <libsolidity/analysis/ControlFlowRevertPruner.h>
|
||||||
#include <libsolidity/analysis/ContractLevelChecker.h>
|
#include <libsolidity/analysis/ContractLevelChecker.h>
|
||||||
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
#include <libsolidity/analysis/DeclarationTypeChecker.h>
|
||||||
#include <libsolidity/analysis/DocStringAnalyser.h>
|
#include <libsolidity/analysis/DocStringAnalyser.h>
|
||||||
@ -516,10 +517,12 @@ bool CompilerStack::analyze()
|
|||||||
|
|
||||||
if (noErrors)
|
if (noErrors)
|
||||||
{
|
{
|
||||||
|
ControlFlowRevertPruner pruner(cfg);
|
||||||
|
pruner.run();
|
||||||
|
|
||||||
ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter);
|
ControlFlowAnalyzer controlFlowAnalyzer(cfg, m_errorReporter);
|
||||||
for (Source const* source: m_sourceOrder)
|
if (!controlFlowAnalyzer.run())
|
||||||
if (source->ast && !controlFlowAnalyzer.analyze(*source->ast))
|
noErrors = false;
|
||||||
noErrors = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -9,5 +9,3 @@ contract C {
|
|||||||
// ====
|
// ====
|
||||||
// SMTEngine: all
|
// 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
|
// 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: (39-57): Statement has no effect.
|
||||||
// Warning 6133: (63-74): Statement has no effect.
|
// Warning 6133: (63-74): Statement has no effect.
|
||||||
|
@ -17,3 +17,5 @@ contract C
|
|||||||
// ====
|
// ====
|
||||||
// SMTEngine: all
|
// 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; }
|
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.
|
// 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(); }
|
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