Control flow analysis for inline assembly.

This commit is contained in:
Daniel Kirchner
2020-03-09 16:23:10 +01:00
parent b7c001eb7f
commit 809e3503ba
29 changed files with 526 additions and 46 deletions
+5 -5
View File
@@ -37,7 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
{
auto const& functionFlow = m_cfg.functionFlow(_function);
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert, functionFlow.transactionReturn);
}
return false;
}
@@ -137,7 +137,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
m_errorReporter.typeError(
variableOccurrence->occurrence() ?
variableOccurrence->occurrence()->location() :
*variableOccurrence->occurrence() :
variableOccurrence->declaration().location(),
ssl,
string("This variable is of storage pointer type and can be ") +
@@ -148,7 +148,7 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
}
}
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const
{
// collect all nodes reachable from the entry point
std::set<CFGNode const*> reachable = util::BreadthFirstSearch<CFGNode const*>{{_entry}}.run(
@@ -158,10 +158,10 @@ void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const*
}
).visited;
// traverse all paths backwards from exit and revert
// traverse all paths backwards from exit, revert and transaction return
// and extract (valid) source locations of unreachable nodes into sorted set
std::set<SourceLocation> unreachable;
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert}}.run(
util::BreadthFirstSearch<CFGNode const*>{{_exit, _revert, _transactionReturn}}.run(
[&](CFGNode const* _node, auto&& _addChild) {
if (!reachable.count(_node) && _node->location.isValid())
unreachable.insert(_node->location);
+2 -2
View File
@@ -36,9 +36,9 @@ public:
private:
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
/// 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) const;
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert, CFGNode const* _transactionReturn) const;
CFG const& m_cfg;
langutil::ErrorReporter& m_errorReporter;
+164 -22
View File
@@ -16,6 +16,8 @@
*/
#include <libsolidity/analysis/ControlFlowBuilder.h>
#include <libyul/AsmData.h>
#include <libyul/backends/evm/EVMDialect.h>
using namespace solidity;
using namespace solidity::langutil;
@@ -26,10 +28,12 @@ ControlFlowBuilder::ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, Funct
m_nodeContainer(_nodeContainer),
m_currentNode(_functionFlow.entry),
m_returnNode(_functionFlow.exit),
m_revertNode(_functionFlow.revert)
m_revertNode(_functionFlow.revert),
m_transactionReturnNode(_functionFlow.transactionReturn)
{
}
unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
CFG::NodeContainer& _nodeContainer,
FunctionDefinition const& _function
@@ -39,6 +43,7 @@ unique_ptr<FunctionFlow> ControlFlowBuilder::createFunctionFlow(
functionFlow->entry = _nodeContainer.newNode();
functionFlow->exit = _nodeContainer.newNode();
functionFlow->revert = _nodeContainer.newNode();
functionFlow->transactionReturn = _nodeContainer.newNode();
ControlFlowBuilder builder(_nodeContainer, *functionFlow);
builder.appendControlFlow(_function);
@@ -131,17 +136,17 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
if (_forStatement.condition())
appendControlFlow(*_forStatement.condition());
auto loopExpression = newLabel();
auto postPart = newLabel();
auto nodes = splitFlow<2>();
auto afterFor = nodes[1];
m_currentNode = nodes[0];
{
BreakContinueScope scope(*this, afterFor, loopExpression);
BreakContinueScope scope(*this, afterFor, postPart);
appendControlFlow(_forStatement.body());
}
placeAndConnectLabel(loopExpression);
placeAndConnectLabel(postPart);
if (auto expression = _forStatement.loopExpression())
appendControlFlow(*expression);
@@ -315,8 +320,7 @@ bool ControlFlowBuilder::visit(FunctionDefinition const& _functionDefinition)
appendControlFlow(*returnParameter);
m_returnNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Return,
nullptr
VariableOccurrence::Kind::Return
);
}
@@ -345,7 +349,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
m_currentNode->variableOccurrences.emplace_back(
*returnParameter,
VariableOccurrence::Kind::Assignment,
&_return
_return.location()
);
}
connect(m_currentNode, m_returnNode);
@@ -363,18 +367,158 @@ bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
{
solAssert(!!m_currentNode, "");
visitNode(_inlineAssembly);
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
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, "");
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, locationOf(_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)
{
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
m_currentNode = nodes[i];
(*this)(_switch.cases[i].body);
nodes[i] = m_currentNode;
}
mergeFlow(nodes);
bool hasDefault = util::contains_if(_switch.cases, [](yul::Case const& _case) { return !_case.value; });
if (!hasDefault)
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<VariableDeclaration const*>(externalReferences.at(&_identifier).declaration))
m_currentNode->variableOccurrences.emplace_back(
*variableDeclaration,
VariableOccurrence::Kind::InlineAssembly,
&_inlineAssembly
*declaration,
VariableOccurrence::Kind::Access,
_identifier.location
);
}
return true;
}
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<VariableDeclaration const*>(externalReferences.at(&variable).declaration))
m_currentNode->variableOccurrences.emplace_back(
*declaration,
VariableOccurrence::Kind::Assignment,
variable.location
);
}
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.terminates)
{
if (builtinFunction->controlFlowSideEffects.reverts)
connect(m_currentNode, m_revertNode);
else
connect(m_currentNode, m_transactionReturnNode);
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.
solUnimplementedAssert(false, "");
}
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
@@ -384,8 +528,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Declaration,
nullptr
VariableOccurrence::Kind::Declaration
);
// Handle declaration with immediate assignment.
@@ -393,14 +536,13 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Assignment,
_variableDeclaration.value().get()
_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,
nullptr
VariableOccurrence::Kind::Assignment
);
return true;
}
@@ -434,7 +576,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
m_currentNode->variableOccurrences.emplace_back(
*var,
VariableOccurrence::Kind::Assignment,
expression
expression ? std::make_optional(expression->location()) : std::optional<langutil::SourceLocation>{}
);
}
}
@@ -452,7 +594,7 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
static_cast<Expression const&>(_identifier).annotation().lValueRequested ?
VariableOccurrence::Kind::Assignment :
VariableOccurrence::Kind::Access,
&_identifier
_identifier.location()
);
return true;
+23 -2
View File
@@ -20,6 +20,7 @@
#include <libsolidity/analysis/ControlFlowGraph.h>
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <libyul/optimiser/ASTWalker.h>
#include <array>
#include <memory>
@@ -30,7 +31,7 @@ namespace solidity::frontend {
* Modifiers are not yet applied to the functions. This is done in a second
* step in the CFG class.
*/
class ControlFlowBuilder: private ASTConstVisitor
class ControlFlowBuilder: private ASTConstVisitor, private yul::ASTWalker
{
public:
static std::unique_ptr<FunctionFlow> createFunctionFlow(
@@ -39,7 +40,10 @@ public:
);
private:
explicit ControlFlowBuilder(CFG::NodeContainer& _nodeContainer, FunctionFlow const& _functionFlow);
explicit ControlFlowBuilder(
CFG::NodeContainer& _nodeContainer,
FunctionFlow const& _functionFlow
);
// Visits for constructing the control flow.
bool visit(BinaryOperation const& _operation) override;
@@ -62,6 +66,17 @@ private:
// Visits for filling variable occurrences.
bool visit(FunctionTypeName const& _functionTypeName) override;
bool visit(InlineAssembly const& _inlineAssembly) override;
void visit(yul::Statement const& _statement) override;
void operator()(yul::If const& _if) override;
void operator()(yul::Switch const& _switch) override;
void operator()(yul::ForLoop const& _for) override;
void operator()(yul::Break const&) override;
void operator()(yul::Continue const&) override;
void operator()(yul::Identifier const& _identifier) override;
void operator()(yul::Assignment const& _assignment) override;
void operator()(yul::FunctionCall const& _functionCall) override;
void operator()(yul::FunctionDefinition const& _functionDefinition) override;
void operator()(yul::Leave const& _leave) override;
bool visit(VariableDeclaration const& _variableDeclaration) override;
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
bool visit(Identifier const& _identifier) override;
@@ -70,6 +85,9 @@ protected:
bool visitNode(ASTNode const&) override;
private:
using ASTConstVisitor::visit;
using yul::ASTWalker::visit;
using yul::ASTWalker::operator();
/// Appends the control flow of @a _node to the current control flow.
void appendControlFlow(ASTNode const& _node);
@@ -136,6 +154,7 @@ private:
CFGNode* m_currentNode = nullptr;
CFGNode* m_returnNode = nullptr;
CFGNode* m_revertNode = nullptr;
CFGNode* m_transactionReturnNode = nullptr;
/// The current jump destination of break Statements.
CFGNode* m_breakJump = nullptr;
@@ -145,6 +164,8 @@ private:
CFGNode* m_placeholderEntry = nullptr;
CFGNode* m_placeholderExit = nullptr;
InlineAssembly const* m_inlineAssembly = nullptr;
/// Helper class that replaces the break and continue jump destinations for the
/// current scope and restores the originals at the end of the scope.
class BreakContinueScope
+13 -9
View File
@@ -20,6 +20,7 @@
#include <libsolidity/ast/AST.h>
#include <libsolidity/ast/ASTVisitor.h>
#include <liblangutil/ErrorReporter.h>
#include <liblangutil/EVMVersion.h>
#include <liblangutil/SourceLocation.h>
#include <map>
@@ -33,8 +34,8 @@ namespace solidity::frontend
/**
* Occurrence of a variable in a block of control flow.
* Stores the declaration of the referenced variable, the
* kind of the occurrence and possibly the node at which
* it occurred.
* kind of the occurrence and possibly the source location
* at which it occurred.
*/
class VariableOccurrence
{
@@ -47,7 +48,7 @@ public:
Assignment,
InlineAssembly
};
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, ASTNode const* _occurrence):
VariableOccurrence(VariableDeclaration const& _declaration, Kind _kind, std::optional<langutil::SourceLocation> const& _occurrence = {}):
m_declaration(_declaration), m_occurrenceKind(_kind), m_occurrence(_occurrence)
{
}
@@ -57,8 +58,8 @@ public:
{
if (m_occurrence && _rhs.m_occurrence)
{
if (m_occurrence->id() < _rhs.m_occurrence->id()) return true;
if (_rhs.m_occurrence->id() < m_occurrence->id()) return false;
if (*m_occurrence < *_rhs.m_occurrence) return true;
if (*_rhs.m_occurrence < *m_occurrence) return false;
}
else if (_rhs.m_occurrence)
return true;
@@ -74,14 +75,14 @@ public:
VariableDeclaration const& declaration() const { return m_declaration; }
Kind kind() const { return m_occurrenceKind; };
ASTNode const* occurrence() const { return m_occurrence; }
std::optional<langutil::SourceLocation> const& occurrence() const { return m_occurrence; }
private:
/// Declaration of the occurring variable.
VariableDeclaration const& m_declaration;
/// Kind of occurrence.
Kind m_occurrenceKind = Kind::Access;
/// AST node at which the variable occurred, if available (may be nullptr).
ASTNode const* m_occurrence = nullptr;
/// Source location at which the variable occurred, if available (may be nullptr).
std::optional<langutil::SourceLocation> m_occurrence;
};
/**
@@ -119,6 +120,10 @@ struct FunctionFlow
/// This node is empty 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.
/// This node is empty and does not have any exits, but may have multiple entries
/// (e.g. all inline assembly return calls).
CFGNode* transactionReturn = nullptr;
};
class CFG: private ASTConstVisitor
@@ -140,7 +145,6 @@ public:
std::vector<std::unique_ptr<CFGNode>> m_nodes;
};
private:
langutil::ErrorReporter& m_errorReporter;
/// Node container.