Skip to content

Commit 950e193

Browse files
authored
Merge pull request #5765 from ethereum/unreachableCode
Warn about unreachable code.
2 parents a379873 + 0dfd4a7 commit 950e193

27 files changed

+258
-12
lines changed

Changelog.md

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ Language Features:
44

55

66
Compiler Features:
7+
* Control Flow Graph: Warn about unreachable code.
78

89

910
Bugfixes:

libdevcore/Algorithms.h

+43
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,47 @@ class CycleDetector
7575
V const* m_firstCycleVertex = nullptr;
7676
};
7777

78+
/**
79+
* Generic breadth first search.
80+
*
81+
* Example: Gather all (recursive) children in a graph starting at (and including) ``root``:
82+
*
83+
* Node const* root = ...;
84+
* std::set<Node> allNodes = BreadthFirstSearch<Node>{{root}}.run([](Node const& _node, auto&& _addChild) {
85+
* // Potentially process ``_node``.
86+
* for (Node const& _child: _node.children())
87+
* // Potentially filter the children to be visited.
88+
* _addChild(_child);
89+
* }).visited;
90+
*
91+
* Note that the order of the traversal is *non-deterministic* (the children are stored in a std::set of pointers).
92+
*/
93+
template<typename V>
94+
struct BreadthFirstSearch
95+
{
96+
/// Runs the breadth first search. The verticesToTraverse member of the struct needs to be initialized.
97+
/// @param _forEachChild is a callable of the form [...](V const& _node, auto&& _addChild) { ... }
98+
/// that is called for each visited node and is supposed to call _addChild(childNode) for every child
99+
/// node of _node.
100+
template<typename ForEachChild>
101+
BreadthFirstSearch& run(ForEachChild&& _forEachChild)
102+
{
103+
while (!verticesToTraverse.empty())
104+
{
105+
V const* v = *verticesToTraverse.begin();
106+
verticesToTraverse.erase(verticesToTraverse.begin());
107+
visited.insert(v);
108+
109+
_forEachChild(*v, [this](V const& _vertex) {
110+
if (!visited.count(&_vertex))
111+
verticesToTraverse.insert(&_vertex);
112+
});
113+
}
114+
return *this;
115+
}
116+
117+
std::set<V const*> verticesToTraverse;
118+
std::set<V const*> visited{};
119+
};
120+
78121
}

liblangutil/SourceLocation.h

+20
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,26 @@ struct SourceLocation
4949

5050
bool isEmpty() const { return start == -1 && end == -1; }
5151

52+
/// @returns the smallest SourceLocation that contains both @param _a and @param _b.
53+
/// Assumes that @param _a and @param _b refer to the same source (exception: if the source of either one
54+
/// is unset, the source of the other will be used for the result, even if that is unset as well).
55+
/// Invalid start and end positions (with value of -1) are ignored (if start or end are -1 for both @param _a and
56+
/// @param _b, then start resp. end of the result will be -1 as well).
57+
static SourceLocation smallestCovering(SourceLocation _a, SourceLocation const& _b)
58+
{
59+
if (!_a.source)
60+
_a.source = _b.source;
61+
62+
if (_a.start < 0)
63+
_a.start = _b.start;
64+
else if (_b.start >= 0 && _b.start < _a.start)
65+
_a.start = _b.start;
66+
if (_b.end > _a.end)
67+
_a.end = _b.end;
68+
69+
return _a;
70+
}
71+
5272
int start = -1;
5373
int end = -1;
5474
std::shared_ptr<CharStream> source;

libsolidity/analysis/ControlFlowAnalyzer.cpp

+34
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <libsolidity/analysis/ControlFlowAnalyzer.h>
1919

2020
#include <liblangutil/SourceLocation.h>
21+
#include <libdevcore/Algorithms.h>
2122
#include <boost/range/algorithm/sort.hpp>
2223

2324
using namespace std;
@@ -36,6 +37,7 @@ bool ControlFlowAnalyzer::visit(FunctionDefinition const& _function)
3637
{
3738
auto const& functionFlow = m_cfg.functionFlow(_function);
3839
checkUninitializedAccess(functionFlow.entry, functionFlow.exit);
40+
checkUnreachable(functionFlow.entry, functionFlow.exit, functionFlow.revert);
3941
}
4042
return false;
4143
}
@@ -145,3 +147,35 @@ void ControlFlowAnalyzer::checkUninitializedAccess(CFGNode const* _entry, CFGNod
145147
}
146148
}
147149
}
150+
151+
void ControlFlowAnalyzer::checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const
152+
{
153+
// collect all nodes reachable from the entry point
154+
std::set<CFGNode const*> reachable = BreadthFirstSearch<CFGNode>{{_entry}}.run(
155+
[](CFGNode const& _node, auto&& _addChild) {
156+
for (CFGNode const* exit: _node.exits)
157+
_addChild(*exit);
158+
}
159+
).visited;
160+
161+
// traverse all paths backwards from exit and revert
162+
// and extract (valid) source locations of unreachable nodes into sorted set
163+
std::set<SourceLocation> unreachable;
164+
BreadthFirstSearch<CFGNode>{{_exit, _revert}}.run(
165+
[&](CFGNode const& _node, auto&& _addChild) {
166+
if (!reachable.count(&_node) && !_node.location.isEmpty())
167+
unreachable.insert(_node.location);
168+
for (CFGNode const* entry: _node.entries)
169+
_addChild(*entry);
170+
}
171+
);
172+
173+
for (auto it = unreachable.begin(); it != unreachable.end();)
174+
{
175+
SourceLocation location = *it++;
176+
// Extend the location, as long as the next location overlaps (unreachable is sorted).
177+
for (; it != unreachable.end() && it->start <= location.end; ++it)
178+
location.end = std::max(location.end, it->end);
179+
m_errorReporter.warning(location, "Unreachable code.");
180+
}
181+
}

libsolidity/analysis/ControlFlowAnalyzer.h

+3
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,9 @@ class ControlFlowAnalyzer: private ASTConstVisitor
3838
private:
3939
/// Checks for uninitialized variable accesses in the control flow between @param _entry and @param _exit.
4040
void checkUninitializedAccess(CFGNode const* _entry, CFGNode const* _exit) const;
41+
/// Checks for unreachable code, i.e. code ending in @param _exit or @param _revert
42+
/// that can not be reached from @param _entry.
43+
void checkUnreachable(CFGNode const* _entry, CFGNode const* _exit, CFGNode const* _revert) const;
4144

4245
CFG const& m_cfg;
4346
langutil::ErrorReporter& m_errorReporter;

libsolidity/analysis/ControlFlowBuilder.cpp

+29-8
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include <libsolidity/analysis/ControlFlowBuilder.h>
1919

2020
using namespace dev;
21+
using namespace langutil;
2122
using namespace solidity;
2223
using namespace std;
2324

@@ -53,6 +54,7 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
5354
case Token::Or:
5455
case Token::And:
5556
{
57+
visitNode(_operation);
5658
appendControlFlow(_operation.leftExpression());
5759

5860
auto nodes = splitFlow<2>();
@@ -62,14 +64,14 @@ bool ControlFlowBuilder::visit(BinaryOperation const& _operation)
6264
return false;
6365
}
6466
default:
65-
break;
67+
return ASTConstVisitor::visit(_operation);
6668
}
67-
return ASTConstVisitor::visit(_operation);
6869
}
6970

7071
bool ControlFlowBuilder::visit(Conditional const& _conditional)
7172
{
7273
solAssert(!!m_currentNode, "");
74+
visitNode(_conditional);
7375

7476
_conditional.condition().accept(*this);
7577

@@ -86,6 +88,7 @@ bool ControlFlowBuilder::visit(Conditional const& _conditional)
8688
bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
8789
{
8890
solAssert(!!m_currentNode, "");
91+
visitNode(_ifStatement);
8992

9093
_ifStatement.condition().accept(*this);
9194

@@ -106,6 +109,7 @@ bool ControlFlowBuilder::visit(IfStatement const& _ifStatement)
106109
bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
107110
{
108111
solAssert(!!m_currentNode, "");
112+
visitNode(_forStatement);
109113

110114
if (_forStatement.initializationExpression())
111115
_forStatement.initializationExpression()->accept(*this);
@@ -139,6 +143,7 @@ bool ControlFlowBuilder::visit(ForStatement const& _forStatement)
139143
bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
140144
{
141145
solAssert(!!m_currentNode, "");
146+
visitNode(_whileStatement);
142147

143148
if (_whileStatement.isDoWhile())
144149
{
@@ -183,28 +188,31 @@ bool ControlFlowBuilder::visit(WhileStatement const& _whileStatement)
183188
return false;
184189
}
185190

186-
bool ControlFlowBuilder::visit(Break const&)
191+
bool ControlFlowBuilder::visit(Break const& _break)
187192
{
188193
solAssert(!!m_currentNode, "");
189194
solAssert(!!m_breakJump, "");
195+
visitNode(_break);
190196
connect(m_currentNode, m_breakJump);
191197
m_currentNode = newLabel();
192198
return false;
193199
}
194200

195-
bool ControlFlowBuilder::visit(Continue const&)
201+
bool ControlFlowBuilder::visit(Continue const& _continue)
196202
{
197203
solAssert(!!m_currentNode, "");
198204
solAssert(!!m_continueJump, "");
205+
visitNode(_continue);
199206
connect(m_currentNode, m_continueJump);
200207
m_currentNode = newLabel();
201208
return false;
202209
}
203210

204-
bool ControlFlowBuilder::visit(Throw const&)
211+
bool ControlFlowBuilder::visit(Throw const& _throw)
205212
{
206213
solAssert(!!m_currentNode, "");
207214
solAssert(!!m_revertNode, "");
215+
visitNode(_throw);
208216
connect(m_currentNode, m_revertNode);
209217
m_currentNode = newLabel();
210218
return false;
@@ -232,6 +240,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
232240
{
233241
case FunctionType::Kind::Revert:
234242
solAssert(!!m_revertNode, "");
243+
visitNode(_functionCall);
235244
_functionCall.expression().accept(*this);
236245
ASTNode::listAccept(_functionCall.arguments(), *this);
237246
connect(m_currentNode, m_revertNode);
@@ -241,6 +250,7 @@ bool ControlFlowBuilder::visit(FunctionCall const& _functionCall)
241250
case FunctionType::Kind::Assert:
242251
{
243252
solAssert(!!m_revertNode, "");
253+
visitNode(_functionCall);
244254
_functionCall.expression().accept(*this);
245255
ASTNode::listAccept(_functionCall.arguments(), *this);
246256
connect(m_currentNode, m_revertNode);
@@ -314,6 +324,7 @@ bool ControlFlowBuilder::visit(Return const& _return)
314324
{
315325
solAssert(!!m_currentNode, "");
316326
solAssert(!!m_returnNode, "");
327+
visitNode(_return);
317328
if (_return.expression())
318329
{
319330
appendControlFlow(*_return.expression());
@@ -327,11 +338,12 @@ bool ControlFlowBuilder::visit(Return const& _return)
327338
}
328339
connect(m_currentNode, m_returnNode);
329340
m_currentNode = newLabel();
330-
return true;
341+
return false;
331342
}
332343

333-
bool ControlFlowBuilder::visit(FunctionTypeName const&)
344+
bool ControlFlowBuilder::visit(FunctionTypeName const& _functionTypeName)
334345
{
346+
visitNode(_functionTypeName);
335347
// Do not visit the parameters and return values of a function type name.
336348
// We do not want to consider them as variable declarations for the control flow graph.
337349
return false;
@@ -340,6 +352,7 @@ bool ControlFlowBuilder::visit(FunctionTypeName const&)
340352
bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
341353
{
342354
solAssert(!!m_currentNode, "");
355+
visitNode(_inlineAssembly);
343356
for (auto const& ref: _inlineAssembly.annotation().externalReferences)
344357
{
345358
if (auto variableDeclaration = dynamic_cast<VariableDeclaration const*>(ref.second.declaration))
@@ -355,6 +368,7 @@ bool ControlFlowBuilder::visit(InlineAssembly const& _inlineAssembly)
355368
bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
356369
{
357370
solAssert(!!m_currentNode, "");
371+
visitNode(_variableDeclaration);
358372

359373
m_currentNode->variableOccurrences.emplace_back(
360374
_variableDeclaration,
@@ -382,6 +396,7 @@ bool ControlFlowBuilder::visit(VariableDeclaration const& _variableDeclaration)
382396
bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDeclarationStatement)
383397
{
384398
solAssert(!!m_currentNode, "");
399+
visitNode(_variableDeclarationStatement);
385400

386401
for (auto const& var: _variableDeclarationStatement.declarations())
387402
if (var)
@@ -417,6 +432,7 @@ bool ControlFlowBuilder::visit(VariableDeclarationStatement const& _variableDecl
417432
bool ControlFlowBuilder::visit(Identifier const& _identifier)
418433
{
419434
solAssert(!!m_currentNode, "");
435+
visitNode(_identifier);
420436

421437
if (auto const* variableDeclaration = dynamic_cast<VariableDeclaration const*>(_identifier.annotation().referencedDeclaration))
422438
m_currentNode->variableOccurrences.emplace_back(
@@ -430,7 +446,12 @@ bool ControlFlowBuilder::visit(Identifier const& _identifier)
430446
return true;
431447
}
432448

433-
449+
bool ControlFlowBuilder::visitNode(ASTNode const& _node)
450+
{
451+
solAssert(!!m_currentNode, "");
452+
m_currentNode->location = langutil::SourceLocation::smallestCovering(m_currentNode->location, _node.location());
453+
return true;
454+
}
434455

435456
void ControlFlowBuilder::appendControlFlow(ASTNode const& _node)
436457
{

libsolidity/analysis/ControlFlowBuilder.h

+5-3
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,11 @@ class ControlFlowBuilder: private ASTConstVisitor
6666
bool visit(VariableDeclarationStatement const& _variableDeclarationStatement) override;
6767
bool visit(Identifier const& _identifier) override;
6868

69+
protected:
70+
bool visitNode(ASTNode const&) override;
71+
72+
private:
73+
6974
/// Appends the control flow of @a _node to the current control flow.
7075
void appendControlFlow(ASTNode const& _node);
7176

@@ -77,9 +82,6 @@ class ControlFlowBuilder: private ASTConstVisitor
7782
/// Creates an arc from @a _from to @a _to.
7883
static void connect(CFGNode* _from, CFGNode* _to);
7984

80-
81-
private:
82-
8385
/// Splits the control flow starting at the current node into n paths.
8486
/// m_currentNode is set to nullptr and has to be set manually or
8587
/// using mergeFlow later.

libsolidity/analysis/ControlFlowGraph.h

+3
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
#include <libsolidity/ast/AST.h>
2121
#include <libsolidity/ast/ASTVisitor.h>
2222
#include <liblangutil/ErrorReporter.h>
23+
#include <liblangutil/SourceLocation.h>
2324

2425
#include <map>
2526
#include <memory>
@@ -98,6 +99,8 @@ struct CFGNode
9899

99100
/// Variable occurrences in the node.
100101
std::vector<VariableOccurrence> variableOccurrences;
102+
// Source location of this control flow block.
103+
langutil::SourceLocation location;
101104
};
102105

103106
/** Describes the control flow of a function. */

test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_err.sol

+4
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,11 @@ contract C {
4646
}
4747
// ----
4848
// TypeError: (87-98): This variable is of storage pointer type and can be returned without prior assignment.
49+
// Warning: (146-151): Unreachable code.
50+
// Warning: (169-174): Unreachable code.
4951
// TypeError: (223-234): This variable is of storage pointer type and can be returned without prior assignment.
52+
// Warning: (316-321): Unreachable code.
5053
// TypeError: (440-451): This variable is of storage pointer type and can be returned without prior assignment.
5154
// TypeError: (654-665): This variable is of storage pointer type and can be returned without prior assignment.
5255
// TypeError: (871-882): This variable is of storage pointer type and can be returned without prior assignment.
56+
// Warning: (933-938): Unreachable code.

test/libsolidity/syntaxTests/controlFlow/storageReturn/dowhile_fine.sol

+1
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,4 @@ contract C {
2929
}
3030
}
3131
// ----
32+
// Warning: (567-572): Unreachable code.

test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/always_revert.sol

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,6 @@ contract C {
55
revert();
66
b;
77
}
8-
}
8+
}
9+
// ----
10+
// Warning: (125-126): Unreachable code.

test/libsolidity/syntaxTests/controlFlow/uninitializedAccess/unreachable.sol

+1
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,4 @@ contract C {
88
}
99
}
1010
// ----
11+
// Warning: (112-135): Unreachable code.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
contract C {
2+
function f() public pure {
3+
return;
4+
// unreachable comment
5+
}
6+
}

0 commit comments

Comments
 (0)