Skip to content

Commit

Permalink
Control flow analysis for inline assembly.
Browse files Browse the repository at this point in the history
  • Loading branch information
ekpyron committed Mar 9, 2020
1 parent b7c001e commit 809e350
Show file tree
Hide file tree
Showing 29 changed files with 526 additions and 46 deletions.
1 change: 1 addition & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
Language Features:
* Inline Assembly: Allow assigning to `_slot` of local storage variable pointers.
* General: Deprecated `value(...)` and `gas(...)` in favor of `{value: ...}` and `{gas: ...}`
* Inline Assembly: Perform control flow analysis on inline assembly. Allows storage returns to be set in assembly only.


Compiler Features:
Expand Down
20 changes: 20 additions & 0 deletions libevmasm/SemanticInformation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,26 @@ bool SemanticInformation::terminatesControlFlow(Instruction _instruction)
}
}

bool SemanticInformation::reverts(AssemblyItem const& _item)
{
if (_item.type() != Operation)
return false;
else
return reverts(_item.instruction());
}

bool SemanticInformation::reverts(Instruction _instruction)
{
switch (_instruction)
{
case Instruction::INVALID:
case Instruction::REVERT:
return true;
default:
return false;
}
}

bool SemanticInformation::isDeterministic(AssemblyItem const& _item)
{
if (_item.type() != Operation)
Expand Down
2 changes: 2 additions & 0 deletions libevmasm/SemanticInformation.h
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ struct SemanticInformation
static bool altersControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(AssemblyItem const& _item);
static bool terminatesControlFlow(Instruction _instruction);
static bool reverts(AssemblyItem const& _item);
static bool reverts(Instruction _instruction);
/// @returns false if the value put on the stack by _item depends on anything else than
/// the information in the current block header, memory, storage or stack.
static bool isDeterministic(AssemblyItem const& _item);
Expand Down
10 changes: 5 additions & 5 deletions libsolidity/analysis/ControlFlowAnalyzer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 ") +
Expand All @@ -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(
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions libsolidity/analysis/ControlFlowAnalyzer.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ class ControlFlowAnalyzer: private ASTConstVisitor
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;
Expand Down
186 changes: 164 additions & 22 deletions libsolidity/analysis/ControlFlowBuilder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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
Expand All @@ -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);

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
);

}
Expand Down Expand Up @@ -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);
Expand All @@ -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)
{
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 variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
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)
Expand All @@ -384,23 +528,21 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)

m_currentNode->variableOccurrences.emplace_back(
_variableDeclaration,
VariableOccurrence::Kind::Declaration,
nullptr
VariableOccurrence::Kind::Declaration
);

// Handle declaration with immediate assignment.
if (_variableDeclaration.value())
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;
}
Expand Down Expand Up @@ -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>{}
);
}
}
Expand All @@ -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;
Expand Down
Loading

0 comments on commit 809e350

Please sign in to comment.