blob: 19dc3b0732f6f581caf5e7cffb51053dafd54218 [file] [log] [blame]
// Copyright (c) 2019, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis.dart';
import 'package:analyzer/src/dart/resolver/flow_analysis_visitor.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import 'driver_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(NullableFlowTest);
defineReflectiveTests(DefiniteAssignmentFlowTest);
defineReflectiveTests(ReachableFlowTest);
defineReflectiveTests(TypePromotionFlowTest);
});
}
@reflectiveTest
class DefiniteAssignmentFlowTest extends DriverResolutionTest {
final List<LocalVariableElement> readBeforeWritten = [];
/// Assert that only local variables with the given names are marked as read
/// before being written. All the other local variables are implicitly
/// considered definitely assigned.
void assertReadBeforeWritten(
[String name1, String name2, String name3, String name4]) {
var expected = [name1, name2, name3, name4]
.where((i) => i != null)
.map((name) => findElement.localVar(name))
.toList();
expect(readBeforeWritten, unorderedEquals(expected));
}
test_assignment_leftExpression() async {
await trackCode(r'''
void f() {
List<int> v;
v[0] = (v = [1, 2])[1];
v;
}
''');
assertReadBeforeWritten('v');
}
test_assignment_leftLocal_compound() async {
await trackCode(r'''
void f() {
int v;
v += 1;
}
''');
assertReadBeforeWritten('v');
}
test_assignment_leftLocal_compound_assignInRight() async {
await trackCode(r'''
void f() {
int v;
v += (v = v);
}
''');
assertReadBeforeWritten('v');
}
test_assignment_leftLocal_pure_eq() async {
await trackCode(r'''
void f() {
int v;
v = 0;
}
''');
assertReadBeforeWritten();
}
test_assignment_leftLocal_pure_eq_self() async {
await trackCode(r'''
void f() {
int v;
v = v;
}
''');
assertReadBeforeWritten('v');
}
test_assignment_leftLocal_pure_questionEq() async {
await trackCode(r'''
void f() {
int v;
v ??= 0;
}
''');
assertReadBeforeWritten('v');
}
test_assignment_leftLocal_pure_questionEq_self() async {
await trackCode(r'''
void f() {
int v;
v ??= v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_ifNull_left() async {
await trackCode(r'''
void f() {
int v;
(v = 0) ?? 0;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_ifNull_right() async {
await trackCode(r'''
void f(int a) {
int v;
a ?? (v = 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_logicalAnd_left() async {
await trackCode(r'''
main(bool c) {
int v;
((v = 0) >= 0) && c;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_logicalAnd_right() async {
await trackCode(r'''
main(bool c) {
int v;
c && ((v = 0) >= 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_logicalOr_left() async {
await trackCode(r'''
main(bool c) {
int v;
((v = 0) >= 0) || c;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_logicalOr_right() async {
await trackCode(r'''
main(bool c) {
int v;
c || ((v = 0) >= 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_binaryExpression_plus_left() async {
await trackCode(r'''
main() {
int v;
(v = 0) + 1;
v;
}
''');
assertReadBeforeWritten();
}
test_binaryExpression_plus_right() async {
await trackCode(r'''
main() {
int v;
1 + (v = 0);
v;
}
''');
assertReadBeforeWritten();
}
test_conditional_both() async {
await trackCode(r'''
f(bool v) {
int v;
b ? (v = 1) : (v = 2);
v;
}
''');
assertReadBeforeWritten();
}
test_conditional_else() async {
await trackCode(r'''
f(bool v) {
int v;
b ? 1 : (v = 2);
v;
}
''');
assertReadBeforeWritten('v');
}
test_conditional_then() async {
await trackCode(r'''
f(bool v) {
int v;
b ? (v = 1) : 2;
v;
}
''');
assertReadBeforeWritten('v');
}
test_conditionalExpression_condition() async {
await trackCode(r'''
main() {
int v;
(v = 0) >= 0 ? 1 : 2;
v;
}
''');
assertReadBeforeWritten();
}
test_doWhile_break_afterAssignment() async {
await trackCode(r'''
void f(bool b) {
int v;
do {
v = 0;
v;
if (b) break;
} while (b);
v;
}
''');
assertReadBeforeWritten();
}
test_doWhile_break_beforeAssignment() async {
await trackCode(r'''
void f(bool b) {
int v;
do {
if (b) break;
v = 0;
} while (b);
v;
}
''');
assertReadBeforeWritten('v');
}
test_doWhile_breakOuterFromInner() async {
await trackCode(r'''
void f(bool b) {
int v1, v2, v3;
L1: do {
do {
v1 = 0;
if (b) break L1;
v2 = 0;
v3 = 0;
} while (b);
v2;
} while (b);
v1;
v3;
}
''');
assertReadBeforeWritten('v3');
}
test_doWhile_condition() async {
await trackCode(r'''
void f() {
int v1, v2;
do {
v1; // assigned in the condition, but not yet
} while ((v1 = 0) + (v2 = 0) >= 0);
v2;
}
''');
assertReadBeforeWritten('v1');
}
test_doWhile_condition_break() async {
await trackCode(r'''
void f(bool b) {
int v;
do {
if (b) break;
} while ((v = 0) >= 0);
v;
}
''');
assertReadBeforeWritten('v');
}
test_doWhile_condition_break_continue() async {
await trackCode(r'''
void f(bool b1, b2) {
int v1, v2, v3, v4, v5, v6;
do {
v1 = 0; // visible outside, visible to the condition
if (b1) break;
v2 = 0; // not visible outside, visible to the condition
v3 = 0; // not visible outside, visible to the condition
if (b2) continue;
v4 = 0; // not visible
v5 = 0; // not visible
} while ((v6 = v1 + v2 + v4) == 0); // has break => v6 is not visible outside
v1;
v3;
v5;
v6;
}
''');
assertReadBeforeWritten('v3', 'v4', 'v5', 'v6');
}
test_doWhile_condition_continue() async {
await trackCode(r'''
void f(bool b) {
int v1, v2, v3, v4;
do {
v1 = 0; // visible outside, visible to the condition
if (b) continue;
v2 = 0; // not visible
v3 = 0; // not visible
} while ((v4 = v1 + v2) == 0); // no break => v4 visible outside
v1;
v3;
v4;
}
''');
assertReadBeforeWritten('v2', 'v3');
}
test_doWhile_continue_beforeAssignment() async {
await trackCode(r'''
void f(bool b) {
int v;
do {
if (b) continue;
v = 0;
} while (b);
v;
}
''');
assertReadBeforeWritten('v');
}
test_doWhile_true_assignInBreak() async {
await trackCode(r'''
void f(bool b) {
int v;
do {
if (b) {
v = 0;
break;
}
} while (true);
v;
}
''');
assertReadBeforeWritten();
}
test_for_body() async {
await trackCode(r'''
void f(bool b) {
int v;
for (; b;) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_for_break() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (; b;) {
v1 = 0;
if (b) break;
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v1', 'v2');
}
test_for_break_updaters() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (; b; v1 + v2) {
v1 = 0;
if (b) break;
v2 = 0;
}
}
''');
assertReadBeforeWritten();
}
test_for_condition() async {
await trackCode(r'''
void f() {
int v;
for (; (v = 0) >= 0;) {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_for_continue() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (; b;) {
v1 = 0;
if (b) continue;
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v1', 'v2');
}
test_for_continue_updaters() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (; b; v1 + v2) {
v1 = 0;
if (b) continue;
v2 = 0;
}
}
''');
assertReadBeforeWritten('v2');
}
test_for_initializer_expression() async {
await trackCode(r'''
void f() {
int v;
for (v = 0;;) {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_for_initializer_variable() async {
await trackCode(r'''
void f() {
int v;
for (var t = (v = 0);;) {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_for_updaters() async {
await trackCode(r'''
void f(bool b) {
int v1, v2, v3, v4;
for (; b; v1 = 0, v2 = 0, v3 = 0, v4) {
v1;
}
v2;
}
''');
assertReadBeforeWritten('v1', 'v2', 'v4');
}
test_for_updaters_afterBody() async {
await trackCode(r'''
void f(bool b) {
int v;
for (; b; v) {
v = 0;
}
}
''');
assertReadBeforeWritten();
}
test_forEach() async {
await trackCode(r'''
void f() {
int v1, v2;
for (var _ in (v1 = [0, 1, 2])) {
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v2');
}
test_forEach_break() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (var _ in [0, 1, 2]) {
v1 = 0;
if (b) break;
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v1', 'v2');
}
test_forEach_continue() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
for (var _ in [0, 1, 2]) {
v1 = 0;
if (b) continue;
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v1', 'v2');
}
test_functionExpression_closure_read() async {
await trackCode(r'''
void f() {
int v1, v2;
v1 = 0;
[0, 1, 2].forEach((t) {
v1;
v2;
});
}
''');
assertReadBeforeWritten('v2');
}
test_functionExpression_closure_write() async {
await trackCode(r'''
void f() {
int v;
[0, 1, 2].forEach((t) {
v = t;
});
v;
}
''');
assertReadBeforeWritten('v');
}
test_functionExpression_localFunction_local() async {
await trackCode(r'''
void f() {
int v;
v = 0;
void f() {
int v; // 1
v;
}
}
''');
var localV = findNode.simple('v; // 1').staticElement;
expect(readBeforeWritten, unorderedEquals([localV]));
}
test_functionExpression_localFunction_local2() async {
await trackCode(r'''
void f() {
int v1;
v1 = 0;
void f() {
int v2, v3;
v2 = 0;
v1;
v2;
v3;
}
}
''');
assertReadBeforeWritten('v3');
}
test_functionExpression_localFunction_read() async {
await trackCode(r'''
void f() {
int v1, v2, v3;
v1 = 0;
void f() {
v1;
v2;
}
v2 = 0;
}
''');
assertReadBeforeWritten('v2');
}
test_functionExpression_localFunction_write() async {
await trackCode(r'''
void f() {
int v;
void f() {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_if_condition() async {
await trackCode(r'''
main() {
int v;
if ((v = 0) >= 0) {
v;
} else {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_false() async {
await trackCode(r'''
void f() {
int v;
if (false) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalAnd_else() async {
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b && (v = i) > 0) {
} else {
v;
}
}
''');
assertReadBeforeWritten('v');
}
test_if_condition_logicalAnd_then() async {
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b && (v = i) > 0) {
v;
}
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalOr_else() async {
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b || (v = i) > 0) {
} else {
v;
}
}
''');
assertReadBeforeWritten();
}
test_if_condition_logicalOr_then() async {
await trackCode(r'''
void f(bool b, int i) {
int v;
if (b || (v = i) > 0) {
v;
} else {
}
}
''');
assertReadBeforeWritten('v');
}
test_if_condition_notFalse() async {
await trackCode(r'''
void f() {
int v;
if (!false) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_notTrue() async {
await trackCode(r'''
void f() {
int v;
if (!true) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_condition_true() async {
await trackCode(r'''
void f() {
int v;
if (true) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_then() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_if_thenElse_all() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
v;
} else {
v = 0;
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_if_thenElse_else() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
// not assigned
} else {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_if_thenElse_then() async {
await trackCode(r'''
main(bool c) {
int v;
if (c) {
v = 0;
} else {
// not assigned
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_switch_case1_default() async {
await trackCode(r'''
void f(int e) {
int v;
switch (e) {
case 1:
v = 0;
break;
case 2:
// not assigned
break;
default:
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_switch_case2_default() async {
await trackCode(r'''
void f(int e) {
int v1, v2;
switch (e) {
case 1:
v1 = 0;
v2 = 0;
v1;
break;
default:
v1 = 0;
v1;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v2');
}
test_switch_case_default_break() async {
await trackCode(r'''
void f(bool b, int e) {
int v1, v2;
switch (e) {
case 1:
v1 = 0;
if (b) break;
v2 = 0;
break;
default:
v1 = 0;
if (b) break;
v2 = 0;
}
v1;
v2;
}
''');
assertReadBeforeWritten('v2');
}
test_switch_case_default_continue() async {
await trackCode(r'''
void f(int e) {
int v;
switch (e) {
L: case 1:
v = 0;
break;
case 2:
continue L;
break;
default:
v = 0;
}
v;
}
''');
// We don't analyze to which `case` we go from `continue L`,
// but we don't have to. If all cases assign, then the variable is
// removed from the unassigned set in the `breakState`. And if there is a
// case when it is not assigned, then the variable will be left unassigned
// in the `breakState`.
assertReadBeforeWritten();
}
test_switch_case_noDefault() async {
await trackCode(r'''
void f(int e) {
int v;
switch (e) {
case 1:
v = 0;
break;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_switch_expression() async {
await trackCode(r'''
void f() {
int v;
switch (v = 0) {}
v;
}
''');
assertReadBeforeWritten();
}
test_tryCatch_body() async {
await trackCode(r'''
void f() {
int v;
try {
v = 0;
} catch (_) {
// not assigned
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_tryCatch_body_catch() async {
await trackCode(r'''
void f() {
int v;
try {
g();
v = 0;
} catch (_) {
v = 0;
}
v;
}
void g() {}
''');
assertReadBeforeWritten();
}
test_tryCatch_body_catchRethrow() async {
await trackCode(r'''
void f() {
int v;
try {
v = 0;
} catch (_) {
rethrow;
}
v;
}
''');
assertReadBeforeWritten();
}
test_tryCatch_catch() async {
await trackCode(r'''
void f() {
int v;
try {
// not assigned
} catch (_) {
v = 0;
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_tryCatchFinally_body() async {
await trackCode(r'''
void f() {
int v;
try {
v = 0;
} catch (_) {
// not assigned
} finally {
// not assigned
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_tryCatchFinally_catch() async {
await trackCode(r'''
void f() {
int v;
try {
// not assigned
} catch (_) {
v = 0;
} finally {
// not assigned
}
v;
}
''');
assertReadBeforeWritten('v');
}
test_tryCatchFinally_finally() async {
await trackCode(r'''
void f() {
int v;
try {
// not assigned
} catch (_) {
// not assigned
} finally {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_tryCatchFinally_useInFinally() async {
await trackCode(r'''
f() {
int x;
try {
g(); // may throw an exception
x = 1;
} catch (_) {
x = 1;
} finally {
x; // BAD
}
}
void g() {}
''');
assertReadBeforeWritten('x');
}
test_tryFinally_body() async {
await trackCode(r'''
void f() {
int v;
try {
v = 0;
} finally {
// not assigned
}
v;
}
''');
assertReadBeforeWritten();
}
test_tryFinally_finally() async {
await trackCode(r'''
void f() {
int v;
try {
// not assigned
} finally {
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_while_condition() async {
await trackCode(r'''
void f() {
int v;
while ((v = 0) >= 0) {
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_while_condition_notTrue() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
while (b) {
v1 = 0;
v2 = 0;
v1;
}
v2;
}
''');
assertReadBeforeWritten('v2');
}
test_while_true_break_afterAssignment() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
while (true) {
v1 = 0;
v1;
if (b) break;
v1;
v2 = 0;
v2;
}
v1;
}
''');
assertReadBeforeWritten();
}
test_while_true_break_beforeAssignment() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
while (true) {
if (b) break;
v1 = 0;
v2 = 0;
v2;
}
v1;
}
''');
assertReadBeforeWritten('v1');
}
test_while_true_break_if() async {
await trackCode(r'''
void f(bool b) {
int v;
while (true) {
if (b) {
v = 0;
break;
} else {
v = 0;
break;
}
v;
}
v;
}
''');
assertReadBeforeWritten();
}
test_while_true_break_if2() async {
await trackCode(r'''
void f(bool b) {
var v;
while (true) {
if (b) {
break;
} else {
v = 0;
}
v;
}
}
''');
assertReadBeforeWritten();
}
test_while_true_break_if3() async {
await trackCode(r'''
void f(bool b) {
int v1, v2;
while (true) {
if (b) {
v1 = 0;
v2 = 0;
if (b) break;
} else {
if (b) break;
v1 = 0;
v2 = 0;
}
v1;
}
v2;
}
''');
assertReadBeforeWritten('v2');
}
test_while_true_breakOuterFromInner() async {
await trackCode(r'''
void f(bool b) {
int v1, v2, v3;
L1: while (true) {
L2: while (true) {
v1 = 0;
if (b) break L1;
v2 = 0;
v3 = 0;
if (b) break L2;
}
v2;
}
v1;
v3;
}
''');
assertReadBeforeWritten('v3');
}
test_while_true_continue() async {
await trackCode(r'''
void f(bool b) {
int v;
while (true) {
if (b) continue;
v = 0;
}
v;
}
''');
assertReadBeforeWritten();
}
test_while_true_noBreak() async {
await trackCode(r'''
void f() {
int v;
while (true) {
// No assignment, but no break.
// So, we don't exit the loop.
// So, all variables are assigned.
}
v;
}
''');
assertReadBeforeWritten();
}
/// Resolve the given [code] and track assignments in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var unit = result.unit;
var typeSystem = result.typeSystem;
var flowAnalysisResult = performFlowAnalysis(typeSystem, unit);
readBeforeWritten.addAll(flowAnalysisResult.readBeforeWritten);
}
}
@reflectiveTest
class NullableFlowTest extends DriverResolutionTest {
final List<AstNode> nullableNodes = [];
final List<AstNode> nonNullableNodes = [];
void assertNonNullable([
String search1,
String search2,
String search3,
String search4,
String search5,
]) {
var expected = [search1, search2, search3, search4, search5]
.where((i) => i != null)
.map((search) => findNode.simple(search))
.toList();
expect(nonNullableNodes, unorderedEquals(expected));
}
void assertNullable([
String search1,
String search2,
String search3,
String search4,
String search5,
]) {
var expected = [search1, search2, search3, search4, search5]
.where((i) => i != null)
.map((search) => findNode.simple(search))
.toList();
expect(nullableNodes, unorderedEquals(expected));
}
test_assign_toNonNull() async {
await trackCode(r'''
void f(int x) {
if (x != null) return;
x; // 1
x = 0;
x; // 2
}
''');
assertNullable('x; // 1');
assertNonNullable('x; // 2');
}
test_assign_toNull() async {
await trackCode(r'''
void f(int x) {
if (x == null) return;
x; // 1
x = null;
x; // 2
}
''');
assertNullable('x; // 2');
assertNonNullable('x; // 1');
}
test_assign_toUnknown_fromNotNull() async {
await trackCode(r'''
void f(int a, int b) {
if (a == null) return;
a; // 1
a = b;
a; // 2
}
''');
assertNullable();
assertNonNullable('a; // 1');
}
test_assign_toUnknown_fromNull() async {
await trackCode(r'''
void f(int a, int b) {
if (a != null) return;
a; // 1
a = b;
a; // 2
}
''');
assertNullable('a; // 1');
assertNonNullable();
}
test_binaryExpression_logicalAnd() async {
await trackCode(r'''
void f(int x) {
x == null && x.isEven;
}
''');
assertNullable('x.isEven');
assertNonNullable();
}
test_binaryExpression_logicalOr() async {
await trackCode(r'''
void f(int x) {
x == null || x.isEven;
}
''');
assertNullable();
assertNonNullable('x.isEven');
}
test_if_joinThenElse_ifNull() async {
await trackCode(r'''
void f(int a, int b) {
if (a == null) {
a; // 1
if (b == null) return;
b; // 2
} else {
a; // 3
if (b == null) return;
b; // 4
}
a; // 5
b; // 6
}
''');
assertNullable('a; // 1');
assertNonNullable('b; // 2', 'a; // 3', 'b; // 4', 'b; // 6');
}
test_if_notNull_thenExit() async {
await trackCode(r'''
void f(int x) {
if (x != null) return;
x; // 1
}
''');
assertNullable('x; // 1');
assertNonNullable();
}
test_if_null_thenExit() async {
await trackCode(r'''
void f(int x) {
if (x == null) return;
x; // 1
}
''');
assertNullable();
assertNonNullable('x; // 1');
}
test_if_then_else() async {
await trackCode(r'''
void f(int x) {
if (x == null) {
x; // 1
} else {
x; // 2
}
}
''');
assertNullable('x; // 1');
assertNonNullable('x; // 2');
}
test_potentiallyMutatedInClosure() async {
await trackCode(r'''
f(int a, int b) {
localFunction() {
a = b;
}
if (a == null) {
a; // 1
localFunction();
a; // 2
}
}
''');
assertNullable();
assertNonNullable();
}
test_tryFinally_eqNullExit_body() async {
await trackCode(r'''
void f(int x) {
try {
if (x == null) return;
x; // 1
} finally {
x; // 2
}
x; // 3
}
''');
assertNullable();
assertNonNullable('x; // 1', 'x; // 3');
}
test_tryFinally_eqNullExit_finally() async {
await trackCode(r'''
void f(int x) {
try {
x; // 1
} finally {
if (x == null) return;
x; // 2
}
x; // 3
}
''');
assertNullable();
assertNonNullable('x; // 2', 'x; // 3');
}
test_tryFinally_outerEqNotNullExit_assignUnknown_body() async {
await trackCode(r'''
void f(int a, int b) {
if (a != null) return;
try {
a; // 1
a = b;
a; // 2
} finally {
a; // 3
}
a; // 4
}
''');
assertNullable('a; // 1');
assertNonNullable();
}
test_tryFinally_outerEqNullExit_assignUnknown_body() async {
await trackCode(r'''
void f(int a, int b) {
if (a == null) return;
try {
a; // 1
a = b;
a; // 2
} finally {
a; // 3
}
a; // 4
}
''');
assertNullable();
assertNonNullable('a; // 1');
}
test_tryFinally_outerEqNullExit_assignUnknown_finally() async {
await trackCode(r'''
void f(int a, int b) {
if (a == null) return;
try {
a; // 1
} finally {
a; // 2
a = b;
a; // 3
}
a; // 4
}
''');
assertNullable();
assertNonNullable('a; // 1', 'a; // 2');
}
test_while_eqNull() async {
await trackCode(r'''
void f(int x) {
while (x == null) {
x; // 1
}
x; // 2
}
''');
assertNullable('x; // 1');
assertNonNullable('x; // 2');
}
test_while_notEqNull() async {
await trackCode(r'''
void f(int x) {
while (x != null) {
x; // 1
}
x; // 2
}
''');
assertNullable('x; // 2');
assertNonNullable('x; // 1');
}
/// Resolve the given [code] and track nullability in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
{},
[],
nullableNodes,
nonNullableNodes,
[],
[],
));
}
}
@reflectiveTest
class ReachableFlowTest extends DriverResolutionTest {
final List<AstNode> unreachableNodes = [];
final List<FunctionBody> functionBodiesThatDontComplete = [];
test_conditional_false() async {
await trackCode(r'''
void f() {
false ? 1 : 2;
}
''');
verify(unreachableExpressions: ['1']);
}
test_conditional_true() async {
await trackCode(r'''
void f() {
true ? 1 : 2;
}
''');
verify(unreachableExpressions: ['2']);
}
test_do_false() async {
await trackCode(r'''
void f() {
do (true) {
1;
} while (false);
2;
}
''');
verify();
}
test_do_true() async {
await trackCode(r'''
void f() { // f
do (true) {
1;
} while (true);
2;
}
''');
verify(
unreachableStatements: ['2;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_exit_beforeSplitStatement() async {
await trackCode(r'''
void f(bool b, int i) { // f
return;
do {} while (b);
for (;;) {}
for (_ in []) {}
if (b) {}
switch (i) {}
try {} finally {}
while (b) {}
}
''');
verify(
unreachableStatements: [
'do {}',
'for (;;',
'for (_',
'if (b)',
'try {',
'switch (i)',
'while (b) {}'
],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_for_condition_true() async {
await trackCode(r'''
void f() { // f
for (; true;) {
1;
}
2;
}
''');
verify(
unreachableStatements: ['2;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_for_condition_true_implicit() async {
await trackCode(r'''
void f() { // f
for (;;) {
1;
}
2;
}
''');
verify(
unreachableStatements: ['2;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_forEach() async {
await trackCode(r'''
void f() {
for (_ in [0, 1, 2]) {
1;
return;
}
2;
}
''');
verify();
}
test_functionBody_hasReturn() async {
await trackCode(r'''
int f() { // f
return 42;
}
''');
verify(functionBodiesThatDontComplete: ['{ // f']);
}
test_functionBody_noReturn() async {
await trackCode(r'''
void f() {
1;
}
''');
verify();
}
test_if_condition() async {
await trackCode(r'''
void f(bool b) {
if (b) {
1;
} else {
2;
}
3;
}
''');
verify();
}
test_if_false_then_else() async {
await trackCode(r'''
void f() {
if (false) { // 1
1;
} else { // 2
}
3;
}
''');
verify(unreachableStatements: ['{ // 1']);
}
test_if_true_return() async {
await trackCode(r'''
void f() { // f
1;
if (true) {
return;
}
2;
}
''');
verify(
unreachableStatements: ['2;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_if_true_then_else() async {
await trackCode(r'''
void f() {
if (true) { // 1
} else { // 2
2;
}
3;
}
''');
verify(unreachableStatements: ['{ // 2']);
}
test_logicalAnd_leftFalse() async {
await trackCode(r'''
void f(int x) {
false && (x == 1);
}
''');
verify(unreachableExpressions: ['(x == 1)']);
}
test_logicalOr_leftTrue() async {
await trackCode(r'''
void f(int x) {
true || (x == 1);
}
''');
verify(unreachableExpressions: ['(x == 1)']);
}
test_switch_case_neverCompletes() async {
await trackCode(r'''
void f(bool b, int i) {
switch (i) {
case 1:
1;
if (b) {
return;
} else {
return;
}
2;
}
3;
}
''');
verify(unreachableStatements: ['2;']);
}
test_tryCatch() async {
await trackCode(r'''
void f() {
try {
1;
} catch (_) {
2;
}
3;
}
''');
verify();
}
test_tryCatch_return_body() async {
await trackCode(r'''
void f() {
try {
1;
return;
2;
} catch (_) {
3;
}
4;
}
''');
verify(unreachableStatements: ['2;']);
}
test_tryCatch_return_catch() async {
await trackCode(r'''
void f() {
try {
1;
} catch (_) {
2;
return;
3;
}
4;
}
''');
verify(unreachableStatements: ['3;']);
}
test_tryCatchFinally_return_body() async {
await trackCode(r'''
void f() {
try {
1;
return;
} catch (_) {
2;
} finally {
3;
}
4;
}
''');
verify();
}
test_tryCatchFinally_return_bodyCatch() async {
await trackCode(r'''
void f() { // f
try {
1;
return;
} catch (_) {
2;
return;
} finally {
3;
}
4;
}
''');
verify(
unreachableStatements: ['4;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_tryCatchFinally_return_catch() async {
await trackCode(r'''
void f() {
try {
1;
} catch (_) {
2;
return;
} finally {
3;
}
4;
}
''');
verify();
}
test_tryFinally_return_body() async {
await trackCode(r'''
void f() { // f
try {
1;
return;
} finally {
2;
}
3;
}
''');
verify(
unreachableStatements: ['3;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_while_false() async {
await trackCode(r'''
void f() {
while (false) { // 1
1;
}
2;
}
''');
verify(unreachableStatements: ['{ // 1']);
}
test_while_true() async {
await trackCode(r'''
void f() { // f
while (true) {
1;
}
2;
3;
}
''');
verify(
unreachableStatements: ['2;', '3;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
test_while_true_break() async {
await trackCode(r'''
void f() {
while (true) {
1;
break;
2;
}
3;
}
''');
verify(unreachableStatements: ['2;']);
}
test_while_true_breakIf() async {
await trackCode(r'''
void f(bool b) {
while (true) {
1;
if (b) break;
2;
}
3;
}
''');
verify();
}
test_while_true_continue() async {
await trackCode(r'''
void f() { // f
while (true) {
1;
continue;
2;
}
3;
}
''');
verify(
unreachableStatements: ['2;', '3;'],
functionBodiesThatDontComplete: ['{ // f'],
);
}
/// Resolve the given [code] and track unreachable nodes in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
{},
[],
[],
[],
unreachableNodes,
functionBodiesThatDontComplete,
));
}
void verify({
List<String> unreachableExpressions = const [],
List<String> unreachableStatements = const [],
List<String> functionBodiesThatDontComplete = const [],
}) {
var expectedUnreachableNodes = <AstNode>[];
expectedUnreachableNodes.addAll(
unreachableStatements.map((search) => findNode.statement(search)),
);
expectedUnreachableNodes.addAll(
unreachableExpressions.map((search) => findNode.expression(search)),
);
expect(
this.unreachableNodes,
unorderedEquals(expectedUnreachableNodes),
);
expect(
this.functionBodiesThatDontComplete,
unorderedEquals(
functionBodiesThatDontComplete
.map((search) => findNode.functionBody(search))
.toList(),
),
);
}
}
@reflectiveTest
class TypePromotionFlowTest extends DriverResolutionTest {
final Map<AstNode, DartType> promotedTypes = {};
void assertNotPromoted(String search) {
var node = findNode.simple(search);
var actualType = promotedTypes[node];
expect(actualType, isNull, reason: search);
}
void assertPromoted(String search, String expectedType) {
var node = findNode.simple(search);
var actualType = promotedTypes[node];
if (actualType == null) {
fail('$expectedType expected, but actually not promoted\n$search');
}
assertElementTypeString(actualType, expectedType);
}
test_assignment() async {
await trackCode(r'''
f(Object x) {
if (x is String) {
x = 42;
x; // 1
}
}
''');
assertNotPromoted('x; // 1');
}
test_binaryExpression_ifNull() async {
await trackCode(r'''
void f(Object x) {
((x is num) || (throw 1)) ?? ((x is int) || (throw 2));
x; // 1
}
''');
assertPromoted('x; // 1', 'num');
}
test_binaryExpression_ifNull_rightUnPromote() async {
await trackCode(r'''
void f(Object x, Object y, Object z) {
if (x is int) {
x; // 1
y ?? (x = z);
x; // 2
}
}
''');
assertPromoted('x; // 1', 'int');
assertNotPromoted('x; // 2');
}
test_conditional_both() async {
await trackCode(r'''
void f(bool b, Object x) {
b ? ((x is num) || (throw 1)) : ((x is int) || (throw 2));
x; // 1
}
''');
assertPromoted('x; // 1', 'num');
}
test_conditional_else() async {
await trackCode(r'''
void f(bool b, Object x) {
b ? 0 : ((x is int) || (throw 2));
x; // 1
}
''');
assertNotPromoted('x; // 1');
}
test_conditional_then() async {
await trackCode(r'''
void f(bool b, Object x) {
b ? ((x is num) || (throw 1)) : 0;
x; // 1
}
''');
assertNotPromoted('x; // 1');
}
test_do_condition_isNotType() async {
await trackCode(r'''
void f(Object x) {
do {
x; // 1
x = '';
} while (x is! String)
x; // 2
}
''');
assertNotPromoted('x; // 1');
assertPromoted('x; // 2', 'String');
}
test_do_condition_isType() async {
await trackCode(r'''
void f(Object x) {
do {
x; // 1
} while (x is String)
x; // 2
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_do_outerIsType() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
do {
x; // 1
} while (b);
x; // 2
}
}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
}
test_do_outerIsType_loopAssigned_body() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
do {
x; // 1
x = x.length;
} while (b);
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_do_outerIsType_loopAssigned_condition() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
do {
x; // 1
x = x.length;
} while (x != 0);
x; // 2
}
}
''');
assertNotPromoted('x != 0');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_do_outerIsType_loopAssigned_condition2() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
do {
x; // 1
} while ((x = 1) != 0);
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_for_outerIsType() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
for (; b;) {
x; // 1
}
x; // 2
}
}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
}
test_for_outerIsType_loopAssigned_body() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
for (; b;) {
x; // 1
x = 42;
}
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_for_outerIsType_loopAssigned_condition() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
for (; (x = 42) > 0;) {
x; // 1
}
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_for_outerIsType_loopAssigned_updaters() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
for (; b; x = 42) {
x; // 1
}
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_forEach_outerIsType_loopAssigned() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
for (var _ in (v1 = [0, 1, 2])) {
x; // 1
x = 42;
}
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_functionExpression_isType() async {
await trackCode(r'''
void f() {
void g(Object x) {
if (x is String) {
x; // 1
}
x = 42;
}
}
''');
assertPromoted('x; // 1', 'String');
}
test_functionExpression_isType_mutatedInClosure2() async {
await trackCode(r'''
void f() {
void g(Object x) {
if (x is String) {
x; // 1
}
void h() {
x = 42;
}
}
}
''');
assertNotPromoted('x; // 1');
}
test_functionExpression_outerIsType_assignedOutside() async {
await trackCode(r'''
void f(Object x) {
void Function() g;
if (x is String) {
x; // 1
g = () {
x; // 2
}
}
x = 42;
x; // 3
g();
}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
assertNotPromoted('x; // 3');
}
test_if_combine_empty() async {
await trackCode(r'''
main(bool b, Object v) {
if (b) {
v is int || (throw 1);
} else {
v is String || (throw 2);
}
v; // 3
}
''');
assertNotPromoted('v; // 3');
}
test_if_conditional_isNotType() async {
await trackCode(r'''
f(bool b, Object v) {
if (b ? (v is! int) : (v is! num)) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertNotPromoted('v; // 1');
assertPromoted('v; // 2', 'num');
assertNotPromoted('v; // 3');
}
test_if_conditional_isType() async {
await trackCode(r'''
f(bool b, Object v) {
if (b ? (v is int) : (v is num)) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertPromoted('v; // 1', 'num');
assertNotPromoted('v; // 2');
assertNotPromoted('v; // 3');
}
test_if_isNotType() async {
await trackCode(r'''
main(v) {
if (v is! String) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertNotPromoted('v; // 1');
assertPromoted('v; // 2', 'String');
assertNotPromoted('v; // 3');
}
test_if_isNotType_return() async {
await trackCode(r'''
main(v) {
if (v is! String) return;
v; // ref
}
''');
assertPromoted('v; // ref', 'String');
}
test_if_isNotType_throw() async {
await trackCode(r'''
main(v) {
if (v is! String) throw 42;
v; // ref
}
''');
assertPromoted('v; // ref', 'String');
}
test_if_isType() async {
await trackCode(r'''
main(v) {
if (v is String) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertPromoted('v; // 1', 'String');
assertNotPromoted('v; // 2');
assertNotPromoted('v; // 3');
}
test_if_isType_thenNonBoolean() async {
await trackCode(r'''
f(Object x) {
if ((x is String) != 3) {
x; // 1
}
}
''');
assertNotPromoted('x; // 1');
}
test_if_logicalNot_isType() async {
await trackCode(r'''
main(v) {
if (!(v is String)) {
v; // 1
} else {
v; // 2
}
v; // 3
}
''');
assertNotPromoted('v; // 1');
assertPromoted('v; // 2', 'String');
assertNotPromoted('v; // 3');
}
test_if_then_isNotType_return() async {
await trackCode(r'''
void f(bool b, Object x) {
if (b) {
if (x is! String) return;
}
x; // 1
}
''');
assertNotPromoted('x; // 1');
}
test_logicalOr_throw() async {
await trackCode(r'''
main(v) {
v is String || (throw 42);
v; // ref
}
''');
assertPromoted('v; // ref', 'String');
}
test_potentiallyMutatedInClosure() async {
await trackCode(r'''
f(Object x) {
localFunction() {
x = 42;
}
if (x is String) {
localFunction();
x; // 1
}
}
''');
assertNotPromoted('x; // 1');
}
test_potentiallyMutatedInScope() async {
await trackCode(r'''
f(Object x) {
if (x is String) {
x; // 1
}
x = 42;
}
''');
assertPromoted('x; // 1', 'String');
}
test_switch_outerIsType_assignedInCase() async {
await trackCode(r'''
void f(int e, Object x) {
if (x is String) {
switch (e) {
L: case 1:
x; // 1
break;
case 2: // no label
x; // 2
break;
case 3:
x = 42;
continue L;
}
x; // 3
}
}
''');
assertNotPromoted('x; // 1');
assertPromoted('x; // 2', 'String');
assertNotPromoted('x; // 3');
}
test_tryCatch_assigned_body() async {
await trackCode(r'''
void f(Object x) {
if (x is! String) return;
x; // 1
try {
x = 42;
g(); // might throw
if (x is! String) return;
x; // 2
} catch (_) {}
x; // 3
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
assertNotPromoted('x; // 3');
}
test_tryCatch_isNotType_exit_body() async {
await trackCode(r'''
void f(Object x) {
try {
if (x is! String) return;
x; // 1
} catch (_) {}
x; // 2
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
}
test_tryCatch_isNotType_exit_body_catch() async {
await trackCode(r'''
void f(Object x) {
try {
if (x is! String) return;
x; // 1
} catch (_) {
if (x is! String) return;
x; // 2
}
x; // 3
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
assertPromoted('x; // 3', 'String');
}
test_tryCatch_isNotType_exit_body_catchRethrow() async {
await trackCode(r'''
void f(Object x) {
try {
if (x is! String) return;
x; // 1
} catch (_) {
x; // 2
rethrow;
}
x; // 3
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
assertPromoted('x; // 3', 'String');
}
test_tryCatch_isNotType_exit_catch() async {
await trackCode(r'''
void f(Object x) {
try {
} catch (_) {
if (x is! String) return;
x; // 1
}
x; // 2
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
}
test_tryCatchFinally_outerIsType() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
try {
x; // 1
} catch (_) {
x; // 2
} finally {
x; // 3
}
x; // 4
}
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
assertPromoted('x; // 3', 'String');
assertPromoted('x; // 4', 'String');
}
test_tryCatchFinally_outerIsType_assigned_body() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
try {
x; // 1
x = 42;
g();
} catch (_) {
x; // 2
} finally {
x; // 3
}
x; // 4
}
}
void g() {}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
assertNotPromoted('x; // 3');
assertNotPromoted('x; // 4');
}
test_tryCatchFinally_outerIsType_assigned_catch() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
try {
x; // 1
} catch (_) {
x; // 2
x = 42;
} finally {
x; // 3
}
x; // 4
}
}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
assertNotPromoted('x; // 3');
assertNotPromoted('x; // 4');
}
test_tryFinally_outerIsType_assigned_body() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
try {
x; // 1
x = 42;
} finally {
x; // 2
}
x; // 3
}
}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
assertNotPromoted('x; // 3');
}
test_tryFinally_outerIsType_assigned_finally() async {
await trackCode(r'''
void f(Object x) {
if (x is String) {
try {
x; // 1
} finally {
x; // 2
x = 42;
}
x; // 3
}
}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
assertNotPromoted('x; // 3');
}
test_while_condition_false() async {
await trackCode(r'''
void f(Object x) {
while (x is! String) {
x; // 1
}
x; // 2
}
''');
assertNotPromoted('x; // 1');
assertPromoted('x; // 2', 'String');
}
test_while_condition_true() async {
await trackCode(r'''
void f(Object x) {
while (x is String) {
x; // 1
}
x; // 2
}
''');
assertPromoted('x; // 1', 'String');
assertNotPromoted('x; // 2');
}
test_while_outerIsType() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
while (b) {
x; // 1
}
x; // 2
}
}
''');
assertPromoted('x; // 1', 'String');
assertPromoted('x; // 2', 'String');
}
test_while_outerIsType_loopAssigned_body() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
while (b) {
x; // 1
x = x.length;
}
x; // 2
}
}
''');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
test_while_outerIsType_loopAssigned_condition() async {
await trackCode(r'''
void f(bool b, Object x) {
if (x is String) {
while (x != 0) {
x; // 1
x = x.length;
}
x; // 2
}
}
''');
assertNotPromoted('x != 0');
assertNotPromoted('x; // 1');
assertNotPromoted('x; // 2');
}
/// Resolve the given [code] and track assignments in the unit.
Future<void> trackCode(String code) async {
addTestFile(code);
await resolveTestFile();
var unit = result.unit;
var assignedVariables = AssignedVariables<Statement, VariableElement>();
unit.accept(AssignedVariablesVisitor(assignedVariables));
var typeSystem = unit.declaredElement.context.typeSystem;
unit.accept(FlowAnalysisVisitor(
typeSystem,
assignedVariables,
promotedTypes,
[],
[],
[],
[],
[],
));
}
}