ControlFlowAnalyser: Also consider called functions in a flow

This commit is contained in:
Mathias Baumann 2020-10-28 20:32:46 +01:00
parent 3dfa68a574
commit 56ebea8b2f
33 changed files with 723 additions and 52 deletions

View File

@ -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:

View File

@ -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

View File

@ -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.");
}
}

View File

@ -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;
};
}

View File

@ -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;
}

View File

@ -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()

View File

@ -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;
};
}

View 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);
});
}
}

View 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;
};
}

View File

@ -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;
}
}

View File

@ -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.

View File

@ -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.

View File

@ -17,3 +17,5 @@ contract C
// ====
// SMTEngine: all
// ----
// Warning 5740: (137-157): Unreachable code.
// Warning 5740: (199-237): Unreachable code.

View File

@ -0,0 +1,12 @@
contract C
{
function iWillRevert() pure public { revert(); }
function test(bool _param) pure external returns(uint256)
{
if (_param) return 1;
iWillRevert();
}
}

View File

@ -0,0 +1,11 @@
function iWillRevert() pure { revert(); }
contract C {
function test(bool _param) pure external returns(uint256) {
if (_param)
return 1;
iWillRevert();
}
}

View File

@ -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.

View File

@ -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();
}
}

View File

@ -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();
}
}

View File

@ -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();
}
}
// ----

View File

@ -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();
}
}
// ----

View File

@ -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();
}
}
// ----

View File

@ -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.

View File

@ -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();
}
}
// ----

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.