mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
228 lines
6.9 KiB
C++
228 lines
6.9 KiB
C++
|
/*
|
||
|
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);
|
||
|
});
|
||
|
}
|
||
|
|
||
|
}
|