/*
This file is part of solidity.
solidity is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
solidity is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with solidity. If not, see .
*/
// SPDX-License-Identifier: GPL-3.0
#include
#include
#include
#include
using namespace solidity::langutil;
using namespace solidity::frontend;
ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow, ContractDefinition const* _contract):
m_nodeContainer(_nodeContainer),
m_currentNode(_functionFlow.entry),
m_returnNode(_functionFlow.exit),
m_revertNode(_functionFlow.revert),
m_transactionReturnNode(_functionFlow.transactionReturn),
m_contract(_contract)
{
}
std::unique_ptr ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function,
ContractDefinition const* _contract
)
{
auto functionFlow = std::make_unique();
functionFlow->entry = _nodeContainer.newNode();
functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode();
functionFlow->transactionReturn = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow, _contract);
builder.appendControlFlow(_function);
return functionFlow;
}
bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
{
solAssert(!!m_currentNode, "");
switch (_operation.getOperator())
{
case Token::Or:
case Token::And:
{
visitNode(_operation);
solAssert(*_operation.annotation().userDefinedFunction == nullptr);
appendControlFlow(_operation.leftExpression());
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _operation.rightExpression());
mergeFlow(nodes, nodes[1]);
return false;
}
default:
{
if (*_operation.annotation().userDefinedFunction != nullptr)
{
visitNode(_operation);
_operation.leftExpression().accept(*this);
_operation.rightExpression().accept(*this);
m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction;
auto nextNode = newLabel();
connect(m_currentNode, nextNode);
m_currentNode = nextNode;
return false;
}
}
}
return ASTConstVisitor::visit(_operation);
}
bool ControlFlowBuilder::visit(UnaryOperation const& _operation)
{
solAssert(!!m_currentNode);
if (*_operation.annotation().userDefinedFunction != nullptr)
{
visitNode(_operation);
_operation.subExpression().accept(*this);
m_currentNode->functionDefinition = *_operation.annotation().userDefinedFunction;
auto nextNode = newLabel();
connect(m_currentNode, nextNode);
m_currentNode = nextNode;
return false;
}
return ASTConstVisitor::visit(_operation);
}
bool ControlFlowBuilder::visit(Conditional const& _conditional)
{
solAssert(!!m_currentNode, "");
visitNode(_conditional);
_conditional.condition().accept(*this);
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _conditional.trueExpression());
nodes[1] = createFlow(nodes[1], _conditional.falseExpression());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(TryStatement const& _tryStatement)
{
appendControlFlow(_tryStatement.externalCall());
auto nodes = splitFlow(_tryStatement.clauses().size());
for (size_t i = 0; i < _tryStatement.clauses().size(); ++i)
nodes[i] = createFlow(nodes[i], _tryStatement.clauses()[i]->block());
mergeFlow(nodes);
return false;
}
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
{
solAssert(!!m_currentNode, "");
visitNode(_ifStatement);
_ifStatement.condition().accept(*this);
auto nodes = splitFlow<2>();
nodes[0] = createFlow(nodes[0], _ifStatement.trueStatement());
if (_ifStatement.falseStatement())
{
nodes[1] = createFlow(nodes[1], *_ifStatement.falseStatement());
mergeFlow(nodes);
}
else
mergeFlow(nodes, nodes[1]);
return false;
}
bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
{
solAssert(!!m_currentNode, "");
visitNode(_forStatement);
if (_forStatement.initializationExpression())
_forStatement.initializationExpression()->accept(*this);
auto condition = createLabelHere();
if (_forStatement.condition())
appendControlFlow(*_forStatement.condition());
auto postPart = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, postPart);
appendControlFlow(_forStatement.body());
}
placeAndConnectLabel(postPart);
if (auto expression = _forStatement.loopExpression())
appendControlFlow(*expression);
connect(m_currentNode, condition);
m_currentNode = afterFor;
return false;
}
bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
{
solAssert(!!m_currentNode, "");
visitNode(_whileStatement);
if (_whileStatement.isDoWhile())
{
auto afterWhile = newLabel();
auto whileBody = createLabelHere();
auto condition = newLabel();
{
BreakContinueScope scope(*this, afterWhile, condition);
appendControlFlow(_whileStatement.body());
}
placeAndConnectLabel(condition);
appendControlFlow(_whileStatement.condition());
connect(m_currentNode, whileBody);
placeAndConnectLabel(afterWhile);
}
else
{
auto whileCondition = createLabelHere();
appendControlFlow(_whileStatement.condition());
auto nodes = splitFlow<2>();
auto whileBody = nodes[0];
auto afterWhile = nodes[1];
m_currentNode = whileBody;
{
BreakContinueScope scope(*this, afterWhile, whileCondition);
appendControlFlow(_whileStatement.body());
}
connect(m_currentNode, whileCondition);
m_currentNode = afterWhile;
}
return false;
}
bool ControlFlowBuilder::visit(Break const& _break)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_breakJump, "");
visitNode(_break);
connect(m_currentNode, m_breakJump);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(Continue const& _continue)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_continueJump, "");
visitNode(_continue);
connect(m_currentNode, m_continueJump);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(Throw const& _throw)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_revertNode, "");
visitNode(_throw);
connect(m_currentNode, m_revertNode);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(RevertStatement const& _revert)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_revertNode, "");
visitNode(_revert);
connect(m_currentNode, m_revertNode);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(PlaceholderStatement const&)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_placeholderEntry, "");
solAssert(!!m_placeholderExit, "");
connect(m_currentNode, m_placeholderEntry);
m_currentNode = newLabel();
connect(m_placeholderExit, m_currentNode);
return false;
}
bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
{
solAssert(!!m_revertNode, "");
solAssert(!!m_currentNode, "");
solAssert(!!_functionCall.expression().annotation().type, "");
if (auto functionType = dynamic_cast(_functionCall.expression().annotation().type))
switch (functionType->kind())
{
case FunctionType::Kind::Revert:
visitNode(_functionCall);
_functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this);
connect(m_currentNode, m_revertNode);
m_currentNode = newLabel();
return false;
case FunctionType::Kind::Require:
case FunctionType::Kind::Assert:
{
visitNode(_functionCall);
_functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this);
connect(m_currentNode, m_revertNode);
auto nextNode = newLabel();
connect(m_currentNode, nextNode);
m_currentNode = nextNode;
return false;
}
case FunctionType::Kind::Internal:
{
visitNode(_functionCall);
_functionCall.expression().accept(*this);
ASTNode::listAccept(_functionCall.arguments(), *this);
m_currentNode->functionDefinition = ASTNode::resolveFunctionCall(_functionCall, m_contract);
auto nextNode = newLabel();
connect(m_currentNode, nextNode);
m_currentNode = nextNode;
return false;
}
default:
break;
}
return ASTConstVisitor::visit(_functionCall);
}
bool ControlFlowBuilder::visit(ModifierInvocation const& _modifierInvocation)
{
solAssert(m_contract, "Free functions cannot have modifiers");
if (auto arguments = _modifierInvocation.arguments())
for (auto& argument: *arguments)
appendControlFlow(*argument);
auto modifierDefinition = dynamic_cast(
_modifierInvocation.name().annotation().referencedDeclaration
);
if (!modifierDefinition)
return false;
VirtualLookup const& requiredLookup = *_modifierInvocation.name().annotation().requiredLookup;
if (requiredLookup == VirtualLookup::Virtual)
modifierDefinition = &modifierDefinition->resolveVirtual(*m_contract);
else
solAssert(requiredLookup == VirtualLookup::Static);
if (!modifierDefinition->isImplemented())
return false;
solAssert(!!m_returnNode, "");
m_placeholderEntry = newLabel();
m_placeholderExit = newLabel();
appendControlFlow(*modifierDefinition);
connect(m_currentNode, m_returnNode);
m_currentNode = m_placeholderEntry;
m_returnNode = m_placeholderExit;
m_placeholderEntry = nullptr;
m_placeholderExit = nullptr;
return false;
}
bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
{
for (auto const& parameter: _functionDefinition.parameters())
appendControlFlow(*parameter);
for (auto const& returnParameter: _functionDefinition.returnParameters())
{
appendControlFlow(*returnParameter);
m_returnNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Return
);
}
for (auto const& modifierInvocation: _functionDefinition.modifiers())
appendControlFlow(*modifierInvocation);
appendControlFlow(_functionDefinition.body());
connect(m_currentNode, m_returnNode);
m_currentNode = nullptr;
return false;
}
bool ControlFlowBuilder::visit(Return const& _return)
{
solAssert(!!m_currentNode, "");
solAssert(!!m_returnNode, "");
visitNode(_return);
if (_return.expression())
{
appendControlFlow(*_return.expression());
// Returns with return expression are considered to be assignments to the return parameters.
for (auto returnParameter: _return.annotation().functionReturnParameters->parameters())
m_currentNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Assignment,
_return.location()
);
}
connect(m_currentNode, m_returnNode);
m_currentNode = newLabel();
return false;
}
bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
{
visitNode(_functionTypeName);
// Do not visit the parameters and return values of a function type name.
// We do not want to consider them as variable declarations for the control flow graph.
return false;
}
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!!m_currentNode && !m_inlineAssembly, "");
m_inlineAssembly = &_inlineAssembly;
(*this)(_inlineAssembly.operations());
m_inlineAssembly = nullptr;
return false;
}
void ControlFlowBuilder::visit(yul::Statement const& _statement)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(nativeLocationOf(_statement) == originLocationOf(_statement), "");
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, nativeLocationOf(_statement));
ASTWalker::visit(_statement);
}
void ControlFlowBuilder::operator()(yul::If const& _if)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_if.condition);
auto nodes = splitFlow<2>();
m_currentNode = nodes[0];
(*this)(_if.body);
nodes[0] = m_currentNode;
mergeFlow(nodes, nodes[1]);
}
void ControlFlowBuilder::operator()(yul::Switch const& _switch)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_switch.expression);
auto beforeSwitch = m_currentNode;
auto nodes = splitFlow(_switch.cases.size());
for (size_t i = 0u; i < _switch.cases.size(); ++i)
{
m_currentNode = nodes[i];
(*this)(_switch.cases[i].body);
nodes[i] = m_currentNode;
}
mergeFlow(nodes);
if (!hasDefaultCase(_switch))
connect(beforeSwitch, m_currentNode);
}
void ControlFlowBuilder::operator()(yul::ForLoop const& _forLoop)
{
solAssert(m_currentNode && m_inlineAssembly, "");
(*this)(_forLoop.pre);
auto condition = createLabelHere();
if (_forLoop.condition)
visit(*_forLoop.condition);
auto loopExpression = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
(*this)(_forLoop.body);
}
placeAndConnectLabel(loopExpression);
(*this)(_forLoop.post);
connect(m_currentNode, condition);
m_currentNode = afterFor;
}
void ControlFlowBuilder::operator()(yul::Break const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_breakJump, "");
connect(m_currentNode, m_breakJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Continue const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
solAssert(m_continueJump, "");
connect(m_currentNode, m_continueJump);
m_currentNode = newLabel();
}
void ControlFlowBuilder::operator()(yul::Identifier const& _identifier)
{
solAssert(m_currentNode && m_inlineAssembly, "");
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
if (externalReferences.count(&_identifier))
if (auto const* declaration = dynamic_cast(externalReferences.at(&_identifier).declaration))
{
solAssert(nativeLocationOf(_identifier) == originLocationOf(_identifier), "");
m_currentNode->variableOccurrences.emplace_back(
*declaration,
VariableOccurrence::Kind::Access,
nativeLocationOf(_identifier)
);
}
}
void ControlFlowBuilder::operator()(yul::Assignment const& _assignment)
{
solAssert(m_currentNode && m_inlineAssembly, "");
visit(*_assignment.value);
auto const& externalReferences = m_inlineAssembly->annotation().externalReferences;
for (auto const& variable: _assignment.variableNames)
if (externalReferences.count(&variable))
if (auto const* declaration = dynamic_cast(externalReferences.at(&variable).declaration))
{
solAssert(nativeLocationOf(variable) == originLocationOf(variable), "");
m_currentNode->variableOccurrences.emplace_back(
*declaration,
VariableOccurrence::Kind::Assignment,
nativeLocationOf(variable)
);
}
}
void ControlFlowBuilder::operator()(yul::FunctionCall const& _functionCall)
{
using namespace yul;
solAssert(m_currentNode && m_inlineAssembly, "");
yul::ASTWalker::operator()(_functionCall);
if (auto const *builtinFunction = m_inlineAssembly->dialect().builtin(_functionCall.functionName.name))
{
if (builtinFunction->controlFlowSideEffects.canTerminate)
connect(m_currentNode, m_transactionReturnNode);
if (builtinFunction->controlFlowSideEffects.canRevert)
connect(m_currentNode, m_revertNode);
if (!builtinFunction->controlFlowSideEffects.canContinue)
m_currentNode = newLabel();
}
}
void ControlFlowBuilder::operator()(yul::FunctionDefinition const&)
{
solAssert(m_currentNode && m_inlineAssembly, "");
// External references cannot be accessed from within functions, so we can ignore their control flow.
// TODO: we might still want to track if they always revert or return, though.
}
void ControlFlowBuilder::operator()(yul::Leave const&)
{
// This has to be implemented, if we ever decide to visit functions.
solUnimplemented("");
}
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
{
solAssert(!!m_currentNode, "");
visitNode(_variableDeclaration);
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Declaration
);
// Handle declaration with immediate assignment.
if (_variableDeclaration.value())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,
_variableDeclaration.value()->location()
);
// Function arguments are considered to be immediately assigned as well (they are "externally assigned").
else if (_variableDeclaration.isCallableOrCatchParameter() && !_variableDeclaration.isReturnParameter())
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment
);
return true;
}
bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
{
solAssert(!!m_currentNode, "");
visitNode(_variableDeclarationStatement);
for (auto const& var: _variableDeclarationStatement.declarations())
if (var)
var->accept(*this);
if (_variableDeclarationStatement.initialValue())
{
_variableDeclarationStatement.initialValue()->accept(*this);
for (size_t i = 0; i < _variableDeclarationStatement.declarations().size(); i++)
if (auto const& var = _variableDeclarationStatement.declarations()[i])
{
auto expression = _variableDeclarationStatement.initialValue();
if (auto tupleExpression = dynamic_cast(expression))
if (tupleExpression->components().size() > 1)
{
solAssert(tupleExpression->components().size() > i, "");
expression = tupleExpression->components()[i].get();
}
expression = resolveOuterUnaryTuples(expression);
m_currentNode->variableOccurrences.emplace_back(
*var,
VariableOccurrence::Kind::Assignment,
expression ? std::make_optional(expression->location()) : std::optional{}
);
}
}
return false;
}
bool ControlFlowBuilder::visit(Identifier const& _identifier)
{
solAssert(!!m_currentNode, "");
visitNode(_identifier);
if (auto const* variableDeclaration = dynamic_cast(_identifier.annotation().referencedDeclaration))
m_currentNode->variableOccurrences.emplace_back(
*variableDeclaration,
static_cast(_identifier).annotation().willBeWrittenTo ?
VariableOccurrence::Kind::Assignment :
VariableOccurrence::Kind::Access,
_identifier.location()
);
return true;
}
bool ControlFlowBuilder::visitNode(ASTNode const& _node)
{
solAssert(!!m_currentNode, "");
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, _node.location());
return true;
}
void ControlFlowBuilder::appendControlFlow(ASTNode const& _node)
{
_node.accept(*this);
}
CFGNode* ControlFlowBuilder::createFlow(CFGNode* _entry, ASTNode const& _node)
{
auto oldCurrentNode = m_currentNode;
m_currentNode = _entry;
appendControlFlow(_node);
auto endNode = m_currentNode;
m_currentNode = oldCurrentNode;
return endNode;
}
void ControlFlowBuilder::connect(CFGNode* _from, CFGNode* _to)
{
solAssert(_from, "");
solAssert(_to, "");
_from->exits.push_back(_to);
_to->entries.push_back(_from);
}
CFGNode* ControlFlowBuilder::newLabel()
{
return m_nodeContainer.newNode();
}
CFGNode* ControlFlowBuilder::createLabelHere()
{
auto label = m_nodeContainer.newNode();
connect(m_currentNode, label);
m_currentNode = label;
return label;
}
void ControlFlowBuilder::placeAndConnectLabel(CFGNode* _node)
{
connect(m_currentNode, _node);
m_currentNode = _node;
}
ControlFlowBuilder::BreakContinueScope::BreakContinueScope(
ControlFlowBuilder& _parser,
CFGNode* _breakJump,
CFGNode* _continueJump
): m_parser(_parser), m_origBreakJump(_parser.m_breakJump), m_origContinueJump(_parser.m_continueJump)
{
m_parser.m_breakJump = _breakJump;
m_parser.m_continueJump = _continueJump;
}
ControlFlowBuilder::BreakContinueScope::~BreakContinueScope()
{
m_parser.m_breakJump = m_origBreakJump;
m_parser.m_continueJump = m_origContinueJump;
}