Skip to content

Commit ee5a90c

Browse files
stereotype441commit-bot@chromium.org
authored andcommitted
Flow analysis: test that all variables are registered with add().
This uncovered two more instances where we weren't properly registering variables: - Local function parameters - Variables introduced by "catch" syntax. - Variables introduced by "for (...; ...; ...)" syntax. Change-Id: Id5e8aa1b598962cca1bb19df6d4ede99a672f108 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/109885 Reviewed-by: Konstantin Shcheglov <[email protected]> Commit-Queue: Paul Berry <[email protected]>
1 parent 477a3c4 commit ee5a90c

File tree

5 files changed

+138
-32
lines changed

5 files changed

+138
-32
lines changed

pkg/analyzer/lib/src/dart/resolver/flow_analysis.dart

Lines changed: 70 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,14 @@ class AssignedVariables<Statement, Variable> {
4242
}
4343

4444
class FlowAnalysis<Statement, Expression, Variable, Type> {
45+
static bool get _assertionsEnabled {
46+
bool result = false;
47+
assert(result = true);
48+
return result;
49+
}
50+
4551
final _VariableSet<Variable> _emptySet;
52+
4653
final State<Variable, Type> _identity;
4754

4855
/// The [NodeOperations], used to manipulate expressions.
@@ -63,8 +70,8 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
6370
/// states.
6471
final Map<Statement, int> _statementToStackIndex = {};
6572

66-
/// The list of all variables.
67-
final List<Variable> _variables = [];
73+
/// List of all variables passed to [add].
74+
final List<Variable> _addedVariables = [];
6875

6976
State<Variable, Type> _current;
7077

@@ -77,6 +84,19 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
7784
/// The state when [_condition] evaluates to `false`.
7885
State<Variable, Type> _conditionFalse;
7986

87+
/// If assertions are enabled, keeps track of all variables that have been
88+
/// passed into the API (other than through a call to [add]). The [finish]
89+
/// method uses this to verify that the caller doesn't forget to pass a
90+
/// variable to [add].
91+
///
92+
/// Note: the reason we have to keep track of this set (rather than simply
93+
/// checking each variable at the time it is passed into the API) is because
94+
/// the client doesn't call `add` until a variable is declared, and in
95+
/// erroneous code, it's possible that a variable might be used before its
96+
/// declaration.
97+
final Set<Variable> _referencedVariables =
98+
_assertionsEnabled ? <Variable>{} : null;
99+
80100
factory FlowAnalysis(
81101
NodeOperations<Expression> nodeOperations,
82102
TypeOperations<Variable, Type> typeOperations,
@@ -108,7 +128,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
108128

109129
/// Add a new [variable], which might be already [assigned].
110130
void add(Variable variable, {bool assigned: false}) {
111-
_variables.add(variable);
131+
_addedVariables.add(variable);
112132
_current = _current.add(variable, assigned: assigned);
113133
}
114134

@@ -174,6 +194,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
174194

175195
/// The [binaryExpression] checks that the [variable] is equal to `null`.
176196
void conditionEqNull(Expression binaryExpression, Variable variable) {
197+
_variableReferenced(variable);
177198
if (functionBody.isPotentiallyMutatedInClosure(variable)) {
178199
return;
179200
}
@@ -185,6 +206,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
185206

186207
/// The [binaryExpression] checks that the [variable] is not equal to `null`.
187208
void conditionNotEqNull(Expression binaryExpression, Variable variable) {
209+
_variableReferenced(variable);
188210
if (functionBody.isPotentiallyMutatedInClosure(variable)) {
189211
return;
190212
}
@@ -196,6 +218,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
196218

197219
void doStatement_bodyBegin(
198220
Statement doStatement, Set<Variable> loopAssigned) {
221+
_variablesReferenced(loopAssigned);
199222
_current = _current.removePromotedAll(loopAssigned);
200223

201224
_statementToStackIndex[doStatement] = _stack.length;
@@ -221,7 +244,21 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
221244
_current = _join(falseCondition, breakState);
222245
}
223246

247+
/// This method should be called at the conclusion of flow analysis for a top
248+
/// level function or method. Performs assertion checks.
249+
void finish() {
250+
assert(_stack.isEmpty);
251+
assert(() {
252+
var variablesNotAdded =
253+
_referencedVariables.difference(Set<Variable>.from(_addedVariables));
254+
assert(variablesNotAdded.isEmpty,
255+
'Variables not passed to add: $variablesNotAdded');
256+
return true;
257+
}());
258+
}
259+
224260
void forEachStatement_bodyBegin(Set<Variable> loopAssigned) {
261+
_variablesReferenced(loopAssigned);
225262
_stack.add(_current);
226263
_current = _current.removePromotedAll(loopAssigned);
227264
}
@@ -245,6 +282,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
245282
}
246283

247284
void forStatement_conditionBegin(Set<Variable> loopAssigned) {
285+
_variablesReferenced(loopAssigned);
248286
_current = _current.removePromotedAll(loopAssigned);
249287
}
250288

@@ -346,11 +384,13 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
346384

347385
/// Return whether the [variable] is definitely assigned in the current state.
348386
bool isAssigned(Variable variable) {
387+
_variableReferenced(variable);
349388
return !_current.notAssigned.contains(variable);
350389
}
351390

352391
void isExpression_end(
353392
Expression isExpression, Variable variable, bool isNot, Type type) {
393+
_variableReferenced(variable);
354394
if (functionBody.isPotentiallyMutatedInClosure(variable)) {
355395
return;
356396
}
@@ -474,6 +514,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
474514
/// Retrieves the type that the [variable] is promoted to, if the [variable]
475515
/// is currently promoted. Otherwise returns `null`.
476516
Type promotedType(Variable variable) {
517+
_variableReferenced(variable);
477518
return _current.promoted[variable];
478519
}
479520

@@ -482,6 +523,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
482523
/// these variables might have different types and are "un-promoted" from
483524
/// the "afterExpression" state.
484525
void switchStatement_beginCase(Set<Variable> notPromoted) {
526+
_variablesReferenced(notPromoted);
485527
_current = _stack.last.removePromotedAll(notPromoted);
486528
}
487529

@@ -511,6 +553,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
511553
}
512554

513555
void tryCatchStatement_bodyEnd(Set<Variable> assignedInBody) {
556+
_variablesReferenced(assignedInBody);
514557
var beforeBody = _stack.removeLast();
515558
var beforeCatch = beforeBody.removePromotedAll(assignedInBody);
516559
_stack.add(beforeCatch);
@@ -539,6 +582,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
539582
}
540583

541584
void tryFinallyStatement_end(Set<Variable> assignedInFinally) {
585+
_variablesReferenced(assignedInFinally);
542586
var afterBody = _stack.removeLast();
543587
_current = _current.restrict(
544588
typeOperations,
@@ -549,16 +593,13 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
549593
}
550594

551595
void tryFinallyStatement_finallyBegin(Set<Variable> assignedInBody) {
596+
_variablesReferenced(assignedInBody);
552597
var beforeTry = _stack.removeLast();
553598
var afterBody = _current;
554599
_stack.add(afterBody);
555600
_current = _join(afterBody, beforeTry.removePromotedAll(assignedInBody));
556601
}
557602

558-
void verifyStackEmpty() {
559-
assert(_stack.isEmpty);
560-
}
561-
562603
void whileStatement_bodyBegin(
563604
Statement whileStatement, Expression condition) {
564605
_conditionalEnd(condition);
@@ -574,6 +615,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
574615
}
575616

576617
void whileStatement_conditionBegin(Set<Variable> loopAssigned) {
618+
_variablesReferenced(loopAssigned);
577619
_current = _current.removePromotedAll(loopAssigned);
578620
}
579621

@@ -587,6 +629,7 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
587629

588630
/// Register write of the given [variable] in the current state.
589631
void write(Variable variable) {
632+
_variableReferenced(variable);
590633
_current = _current.write(typeOperations, _emptySet, variable);
591634
}
592635

@@ -623,6 +666,26 @@ class FlowAnalysis<Statement, Expression, Variable, Type> {
623666
newPromoted,
624667
);
625668
}
669+
670+
/// If assertions are enabled, records that the given variable has been
671+
/// referenced. The [finish] method will verify that all referenced variables
672+
/// were eventually passed to [add].
673+
void _variableReferenced(Variable variable) {
674+
assert(() {
675+
_referencedVariables.add(variable);
676+
return true;
677+
}());
678+
}
679+
680+
/// If assertions are enabled, records that the given variables have been
681+
/// referenced. The [finish] method will verify that all referenced variables
682+
/// were eventually passed to [add].
683+
void _variablesReferenced(Iterable<Variable> variables) {
684+
assert(() {
685+
_referencedVariables.addAll(variables);
686+
return true;
687+
}());
688+
}
626689
}
627690

628691
/// Accessor for function body information.

pkg/analyzer/lib/src/dart/resolver/flow_analysis_visitor.dart

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -133,15 +133,14 @@ class FlowAnalysisHelper {
133133

134134
if (_blockFunctionBodyLevel > 1) {
135135
assert(flow != null);
136-
return;
136+
} else {
137+
flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>(
138+
_nodeOperations,
139+
_typeOperations,
140+
_FunctionBodyAccess(node),
141+
);
137142
}
138143

139-
flow = FlowAnalysis<Statement, Expression, VariableElement, DartType>(
140-
_nodeOperations,
141-
_typeOperations,
142-
_FunctionBodyAccess(node),
143-
);
144-
145144
var parameters = _enclosingExecutableParameters(node);
146145
if (parameters != null) {
147146
for (var parameter in parameters.parameters) {
@@ -157,12 +156,17 @@ class FlowAnalysisHelper {
157156
return;
158157
}
159158

159+
// Set this.flow to null before doing any clean-up so that if an exception
160+
// is raised, the state is already updated correctly, and we don't have
161+
// cascading failures.
162+
var flow = this.flow;
163+
this.flow = null;
164+
160165
if (!flow.isReachable) {
161166
result.functionBodiesThatDontComplete.add(node);
162167
}
163168

164-
flow.verifyStackEmpty();
165-
flow = null;
169+
flow.finish();
166170
}
167171

168172
void breakStatement(BreakStatement node) {
@@ -247,12 +251,14 @@ class FlowAnalysisHelper {
247251
}
248252
}
249253

250-
void variableDeclarationStatement(VariableDeclarationStatement node) {
251-
var variables = node.variables.variables;
252-
for (var i = 0; i < variables.length; ++i) {
253-
var variable = variables[i];
254-
flow.add(variable.declaredElement,
255-
assigned: variable.initializer != null);
254+
void variableDeclarationList(VariableDeclarationList node) {
255+
if (flow != null) {
256+
var variables = node.variables;
257+
for (var i = 0; i < variables.length; ++i) {
258+
var variable = variables[i];
259+
flow.add(variable.declaredElement,
260+
assigned: variable.initializer != null);
261+
}
256262
}
257263
}
258264

pkg/analyzer/lib/src/generated/resolver.dart

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5118,6 +5118,12 @@ class ResolverVisitor extends ScopedVisitor {
51185118
for (var i = 0; i < catchLength; ++i) {
51195119
var catchClause = catchClauses[i];
51205120
flow.tryCatchStatement_catchBegin();
5121+
if (catchClause.exceptionParameter != null) {
5122+
flow.add(catchClause.exceptionParameter.staticElement, assigned: true);
5123+
}
5124+
if (catchClause.stackTraceParameter != null) {
5125+
flow.add(catchClause.stackTraceParameter.staticElement, assigned: true);
5126+
}
51215127
catchClause.accept(this);
51225128
flow.tryCatchStatement_catchEnd();
51235129
}
@@ -5159,6 +5165,7 @@ class ResolverVisitor extends ScopedVisitor {
51595165

51605166
@override
51615167
void visitVariableDeclarationList(VariableDeclarationList node) {
5168+
_flowAnalysis?.variableDeclarationList(node);
51625169
for (VariableDeclaration decl in node.variables) {
51635170
VariableElement variableElement =
51645171
resolutionMap.elementDeclaredByVariableDeclaration(decl);
@@ -5167,12 +5174,6 @@ class ResolverVisitor extends ScopedVisitor {
51675174
super.visitVariableDeclarationList(node);
51685175
}
51695176

5170-
@override
5171-
void visitVariableDeclarationStatement(VariableDeclarationStatement node) {
5172-
_flowAnalysis?.variableDeclarationStatement(node);
5173-
super.visitVariableDeclarationStatement(node);
5174-
}
5175-
51765177
@override
51775178
void visitWhileStatement(WhileStatement node) {
51785179
_flowAnalysis?.checkUnreachableNode(node);

pkg/analyzer/test/src/dart/resolution/flow_analysis_unit_test.dart

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ main() {
1717
h.flow.ifStatement_elseBegin();
1818
expect(h.flow.promotedType(x), isNull);
1919
h.flow.ifStatement_end(true);
20-
h.flow.verifyStackEmpty();
20+
h.flow.finish();
2121
});
2222

2323
test('conditionEqNull promotes false branch', () {
@@ -30,7 +30,21 @@ main() {
3030
h.flow.ifStatement_elseBegin();
3131
expect(h.flow.promotedType(x).type, 'int');
3232
h.flow.ifStatement_end(true);
33-
h.flow.verifyStackEmpty();
33+
h.flow.finish();
34+
});
35+
36+
test('finish checks proper nesting', () {
37+
var h = _Harness();
38+
var expr = _Expression();
39+
h.flow.ifStatement_thenBegin(expr);
40+
expect(() => h.flow.finish(), _asserts);
41+
});
42+
43+
test('finish checks for un-added variables', () {
44+
var h = _Harness();
45+
var x = _Var('x', _Type('int'));
46+
h.flow.isAssigned(x);
47+
expect(() => h.flow.finish(), _asserts);
3448
});
3549

3650
test('ifStatement_end(false) keeps else branch if then branch exits', () {
@@ -42,7 +56,7 @@ main() {
4256
h.flow.handleExit();
4357
h.flow.ifStatement_end(false);
4458
expect(h.flow.promotedType(x).type, 'int');
45-
h.flow.verifyStackEmpty();
59+
h.flow.finish();
4660
});
4761

4862
void _checkIs(String declaredType, String tryPromoteType,
@@ -60,7 +74,7 @@ main() {
6074
h.flow.ifStatement_elseBegin();
6175
expect(h.flow.promotedType(x), isNull);
6276
h.flow.ifStatement_end(true);
63-
h.flow.verifyStackEmpty();
77+
h.flow.finish();
6478
}
6579

6680
test('isExpression_end promotes to a subtype', () {
@@ -439,6 +453,18 @@ main() {
439453
});
440454
}
441455

456+
/// Returns the appropriate matcher for expecting an assertion error to be
457+
/// thrown or not, based on whether assertions are enabled.
458+
Matcher get _asserts {
459+
var matcher = throwsA(TypeMatcher<AssertionError>());
460+
bool assertionsEnabled = false;
461+
assert(assertionsEnabled = true);
462+
if (!assertionsEnabled) {
463+
matcher = isNot(matcher);
464+
}
465+
return matcher;
466+
}
467+
442468
class _Expression {}
443469

444470
class _Harness

pkg/analyzer/test/src/dart/resolution/type_promotion_test.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,16 @@ void f(bool b, Object x) {
172172
''');
173173
}
174174

175+
test_for_declaredVar() async {
176+
await resolveCode(r'''
177+
void f() {
178+
for (Object x = g(); x is int; x = g()) {
179+
/*int*/ x;
180+
}
181+
}
182+
''');
183+
}
184+
175185
test_for_outerIsType() async {
176186
await resolveCode(r'''
177187
void f(bool b, Object x) {

0 commit comments

Comments
 (0)