blob: 9683a929c729c38e8d88a96eb38d6283282e1b7a [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:test_reflective_loader/test_reflective_loader.dart';
import 'migration_visitor_test_base.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(EdgeBuilderFlowAnalysisTest);
});
}
@reflectiveTest
class EdgeBuilderFlowAnalysisTest extends EdgeBuilderTestBase {
Future<void> test_as() async {
await analyze('''
void f(num n) {
h(n);
n as int;
g(n);
}
void g(int i) {}
void h(num m) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var nNode = decoratedTypeAnnotation('num n').node;
var mNode = decoratedTypeAnnotation('num m').node;
// No edge from n to i because n is known to be non-nullable at the site of
// the call to g
assertNoEdge(nNode, iNode);
// But there is an edge from n to m.
assertEdge(nNode, mNode, hard: true);
}
Future<void> test_assert_initializer_condition_promotes_to_message() async {
await analyze('''
class C {
C(int i)
: assert(i == null, g(i)) {
h(i);
}
}
String g(int j) => 'foo';
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i is known to be non-nullable at the site of
// the call to g
assertNoEdge(iNode, jNode);
// But there is an edge from i to k.
assertEdge(iNode, kNode, hard: true);
}
Future<void> test_assert_initializer_does_not_promote_beyond_assert() async {
await analyze('''
class C {
C(int i)
: assert(i != null) {
g(i);
if (i == null) return;
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// There is an edge from i to j because i not promoted by the assert.
assertEdge(iNode, jNode, hard: true);
// But there is no edge from i to k.
assertNoEdge(iNode, kNode);
}
Future<void> test_assert_statement_condition_promotes_to_message() async {
await analyze('''
void f(int i) {
assert(i == null, g(i));
h(i);
}
String g(int j) => 'foo';
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i is known to be non-nullable at the site of
// the call to g
assertNoEdge(iNode, jNode);
// But there is an edge from i to k.
assertEdge(iNode, kNode, hard: true);
}
Future<void> test_assert_statement_does_not_promote_beyond_assert() async {
await analyze('''
void f(int i) {
assert(i != null);
g(i);
if (i == null) return;
h(i);
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// There is an edge from i to j because i not promoted by the assert.
assertEdge(iNode, jNode, hard: true);
// But there is no edge from i to k.
assertNoEdge(iNode, kNode);
}
Future<void> test_assignmentExpression() async {
await analyze('''
void f(int i, int j) {
if (i != null) {
g(i);
i = j;
h(i);
}
}
void g(int k) {}
void h(int l) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
var lNode = decoratedTypeAnnotation('int l').node;
// No edge from i to k because i's type is promoted to non-nullable
assertNoEdge(iNode, kNode);
// But there is an edge from i to l, because it is after the assignment
assertEdge(iNode, lNode, hard: false);
// And there is an edge from j to i, because a null value of j would lead to
// a null value for i.
assertEdge(jNode, iNode, hard: false);
}
Future<void> test_assignmentExpression_lhs_before_rhs() async {
await analyze('''
void f(int i, List<int> l) {
if (i != null) {
l[i = g(i)] = h(i);
}
}
int g(int j) => 1;
int h(int k) => 1;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
var gReturnNode = decoratedTypeAnnotation('int g').node;
// No edge from i to j, because i's type is promoted before the call to g.
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, because the call to h happens after the
// assignment.
assertEdge(iNode, kNode, hard: false);
// And there is an edge from g's return type to i, due to the assignment.
assertEdge(gReturnNode, iNode, hard: false);
}
Future<void> test_assignmentExpression_null_aware() async {
await analyze('''
void f(bool b, int i, int j) {
if (b) {
j ??= i is int ? i : throw 'foo';
g(i);
j = i is int ? i : throw 'foo';
h(i);
}
}
void g(int k) {}
void h(int l) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var kNode = decoratedTypeAnnotation('int k').node;
var lNode = decoratedTypeAnnotation('int l').node;
// No edge from i to l because i's type is promoted to non-nullable
assertNoEdge(iNode, lNode);
// But there is an edge from i to k, because the RHS of the `??=` is not
// guaranteed to execute
assertEdge(iNode, kNode, hard: false);
}
Future<void> test_assignmentExpression_write_after_rhs() async {
await analyze('''
void f(int i) {
if (i != null) {
i = g(i);
h(i);
}
}
int g(int j) => 1;
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
var gReturnNode = decoratedTypeAnnotation('int g').node;
// No edge from i to j because i's type is promoted before the call to g.
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, because the call to h happens after the
// assignment.
assertEdge(iNode, kNode, hard: false);
// And there is an edge from g's return type to i, due to the assignment.
assertEdge(gReturnNode, iNode, hard: false);
}
Future<void> test_binaryExpression_ampersandAmpersand_left() async {
await analyze('''
bool f(int i) => i != null && i.isEven;
bool g(int j) => j.isEven;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_binaryExpression_ampersandAmpersand_right() async {
await analyze('''
void f(bool b, int i, int j) {
if (b && i != null) {
print(i.isEven);
print(j.isEven);
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_binaryExpression_barBar_left() async {
await analyze('''
bool f(int i) => i == null || i.isEven;
bool g(int j) => j.isEven;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_binaryExpression_barBar_right() async {
await analyze('''
void f(bool b, int i, int j) {
if (b || i == null) {} else {
print(i.isEven);
print(j.isEven);
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_booleanLiteral_false() async {
await analyze('''
void f(int i, int j) {
if (i != null || false) {} else return;
if (j != null || true) {} else return;
i.isEven;
j.isEven;
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never i is known to be non-nullable at the site of
// the call to i.isEven
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_booleanLiteral_true() async {
await analyze('''
void f(int i, int j) {
if (i == null && true) return;
if (j == null && false) return;
i.isEven;
j.isEven;
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never i is known to be non-nullable at the site of
// the call to i.isEven
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_break_labeled() async {
await analyze('''
void f(int i) {
L: while(true) {
while (b()) {
if (i != null) break L;
}
g(i);
}
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_break_unlabeled() async {
await analyze('''
void f(int i) {
while (true) {
if (i != null) break;
g(i);
}
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void>
test_catch_cancels_promotions_based_on_assignments_in_body() async {
await analyze('''
void f(int i) {
if (i == null) return;
try {
g(i);
i = null;
if (i == null) return;
g(i);
} catch (_) {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i is promoted at the time of both calls to g.
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, because there is no guarantee that i is
// promoted at all times during the execution of the try block.
assertEdge(iNode, kNode, hard: false);
}
Future<void> test_catch_falls_through_to_after_try() async {
await analyze('''
void f(int i) {
try {
g(i);
return;
} catch (_) {
if (i == null) return;
}
h(i);
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i's type is promoted to non-nullable
assertNoEdge(iNode, kNode);
// But there is an edge from i to j.
assertEdge(iNode, jNode, hard: true);
}
Future<void> test_catch_resets_to_state_before_try() async {
await analyze('''
void f(int i) {
try {
if (i == null) return;
g(i);
} catch (_) {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i's type is promoted to non-nullable
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, since we assume an exception might
// occur at any time during the body of the try.
assertEdge(iNode, kNode, hard: false);
}
Future<void> test_conditionalExpression() async {
await analyze('''
int f(int i, int l) => i == null ? g(l) : h(i);
int g(int j) => 1;
int h(int k) => 1;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
var lNode = decoratedTypeAnnotation('int l').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from l to j
assertEdge(lNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_conditionalExpression_propagates_promotions() async {
await analyze('''
void f(bool b, int i, int j, int k) {
if (b ? (i != null && j != null) : (i != null && k != null)) {
i.isEven;
j.isEven;
k.isEven;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to never because i is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there are edges from j and k to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
assertEdge(kNode, inSet(pointsToNever), hard: false);
}
Future<void> test_constructorDeclaration_assert() async {
await analyze('''
class C {
C(int i, int j) : assert(i == null || i.isEven, j.isEven);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_constructorDeclaration_initializer() async {
await analyze('''
class C {
bool b1;
bool b2;
C(int i, int j) : b1 = i == null || i.isEven, b2 = j.isEven;
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_constructorDeclaration_redirection() async {
await analyze('''
class C {
C(bool b1, bool b2);
C.redirect(int i, int j) : this(i == null || i.isEven, j.isEven);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_continue_labeled() async {
await analyze('''
void f(int i) {
L: do {
do {
if (i != null) continue L;
} while (g(i));
break;
} while (h(i));
}
bool g(int j) => true;
bool h(int k) => true;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_continue_unlabeled() async {
await analyze('''
void f(int i) {
do {
if (i != null) continue;
h(i);
break;
} while (g(i));
}
bool g(int j) => true;
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i is promoted at the time of the call to g.
assertNoEdge(iNode, jNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to h.
assertEdge(iNode, kNode, hard: false);
}
Future<void> test_do_break_target() async {
await analyze('''
void f(int i) {
L: do {
do {
if (i != null) break L;
if (b()) break;
} while (true);
g(i);
} while (true);
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_do_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
do {
i.isEven;
j.isEven;
j = null;
} while (true);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_do_cancels_promotions_for_assignments_in_condition() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
do {} while (i.isEven && j.isEven && g(j = null));
}
bool g(int k) => true;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_do_continue_target() async {
await analyze('''
void f(int i) {
L: do {
do {
if (i != null) continue L;
g(i);
} while (true);
} while (h(i));
}
void g(int j) {}
bool h(int k) => true;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_field_initializer() async {
await analyze('''
bool b1 = true;
bool b2 = true;
class C {
bool b = b1 || b2;
}
''');
// No assertions; we just want to verify that the presence of `||` inside a
// field doesn't cause flow analysis to crash.
}
Future<void> test_finally_promotions_are_preserved() async {
await analyze('''
void f(int i) {
try {
g(i);
} finally {
if (i == null) return;
}
h(i);
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i's type is promoted to non-nullable in the
// finally block.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j.
assertEdge(iNode, jNode, hard: true);
}
Future<void> test_finally_temporarily_resets_to_state_before_try() async {
await analyze('''
void f(int i) {
try {
if (i == null) return;
g(i);
} finally {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i's type is promoted to non-nullable in the
// try-block.
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, since we assume an exception might
// occur at any time during the body of the try.
assertEdge(iNode, kNode, hard: false);
}
Future<void> test_for_break_target() async {
await analyze('''
void f(int i) {
L: for (;;) {
for (;;) {
if (i != null) break L;
if (b()) break;
}
g(i);
}
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_for_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
for (;;) {
i.isEven;
j.isEven;
j = null;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_for_cancels_promotions_for_assignments_in_updaters() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
for (;; j = null) {
i.isEven;
j.isEven;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_collection_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
<Object>[for (;;) <Object>[i.isEven, j.isEven, (j = null)]];
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_collection_cancels_promotions_for_assignments_in_updaters() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
<Object>[for (;; j = null) <Object>[i.isEven, j.isEven]];
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_collection_preserves_promotions_for_assignments_in_initializer() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
<Object>[for(var v = h(i.isEven && j.isEven && g(i = null));;) null];
}
bool g(int k) => true;
int h(bool b) => 0;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because it is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_for_continue_target() async {
await analyze('''
void f(int i) {
L: for (; b(); h(i)) {
for (; b(); g(i)) {
if (i != null) continue L;
}
return;
}
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_for_each_assigns_to_declared_var() async {
await analyze('''
void f(Iterable<int> x) {
for (int i in x) {
g(i);
}
}
void g(int j) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
// No edge from never to i because it is assigned before it is used.
assertNoEdge(never, iNode);
}
Future<void> test_for_each_assigns_to_identifier() async {
await analyze('''
void f(Iterable<int> x) {
int i;
for (i in x) {
g(i);
}
}
void g(int j) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
// No edge from never to i because it is assigned before it is used.
assertNoEdge(never, iNode);
}
Future<void>
test_for_each_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j, Iterable<Object> x) {
if (i == null) return;
if (j == null) return;
for (var v in x) {
i.isEven;
j.isEven;
j = null;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_for_each_collection_assigns_to_declared_var() async {
await analyze('''
void f(Iterable<int> x) {
[for (int i in x) g(i)];
}
void g(int j) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
// No edge from never to i because it is assigned before it is used.
assertNoEdge(never, iNode);
}
Future<void> test_for_each_collection_assigns_to_identifier() async {
await analyze('''
void f(Iterable<int> x) {
int i;
[for (i in x) g(i)];
}
void g(int j) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
// No edge from never to i because it is assigned before it is used.
assertNoEdge(never, iNode);
}
Future<void>
test_for_each_collection_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j, Iterable<Object> x) {
if (i == null) return;
if (j == null) return;
<Object>[for (var v in x) <Object>[i.isEven, j.isEven, (j = null)]];
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_each_collection_preserves_promotions_for_assignments_in_iterable() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
<Object>[for(var v in h(i.isEven && j.isEven && g(i = null))) null];
}
bool g(int k) => true;
Iterable<Object> h(bool b) => <Object>[];
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because it is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_each_preserves_promotions_for_assignments_in_iterable() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
for(var v in h(i.isEven && j.isEven && g(i = null))) {}
}
bool g(int k) => true;
Iterable<Object> h(bool b) => <Object>[];
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because it is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_for_preserves_promotions_for_assignments_in_initializer() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
for(var v = h(i.isEven && j.isEven && g(i = null));;) {}
}
bool g(int k) => true;
int h(bool b) => 0;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because it is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_function_withFormals() async {
await analyze('''
void f(Function<T>() f) {
if (f == null) return;
f();
}
''');
var fNode = decoratedGenericFunctionTypeAnnotation('Function<T>() f').node;
// No edge to never because it had been promoted before invoked.
assertNoEdge(fNode, graph.never);
}
Future<void> test_functionDeclaration() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
print(i.isEven);
print(j.isEven);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_functionDeclaration_expression_body() async {
await analyze('''
bool f(int i) => i == null || i.isEven;
bool g(int j) => j.isEven;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void>
test_functionDeclaration_resets_unconditional_control_flow() async {
await analyze('''
void f(bool b, int i, int j) {
assert(i != null);
if (b) return;
assert(j != null);
}
void g(int k) {
assert(k != null);
}
''');
assertEdge(decoratedTypeAnnotation('int i').node, never, hard: true);
assertNoEdge(always, decoratedTypeAnnotation('int j').node);
assertEdge(decoratedTypeAnnotation('int k').node, never, hard: true);
}
Future<void> test_functionExpression_parameters() async {
await analyze('''
void f() {
var g = (int i, int j) {
if (i == null) return;
print(i.isEven);
print(j.isEven);
};
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_if() async {
await analyze('''
void f(int i) {
if (i == null) {
g(i);
} else {
h(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_if_without_else() async {
await analyze('''
void f(int i) {
if (i == null) {
g(i);
return;
}
h(i);
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false, guards: [iNode]);
}
Future<void> test_ifNull() async {
await analyze('''
void f(int i, int x) {
x ?? (i == null ? throw 'foo' : g(i));
h(i);
}
int g(int j) => 0;
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to j because i's type is promoted to non-nullable
assertNoEdge(iNode, jNode);
// But there is an edge from i to k, because the RHS of the `??` isn't
// guaranteed to execute.
assertEdge(iNode, kNode, hard: true);
}
Future<void> test_is() async {
await analyze('''
void f(num n) {
if (n is int) {
g(n);
}
h(n);
}
void g(int i) {}
void h(num m) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var nNode = decoratedTypeAnnotation('num n').node;
var mNode = decoratedTypeAnnotation('num m').node;
// No edge from n to i because n is known to be non-nullable at the site of
// the call to g
assertNoEdge(nNode, iNode);
// But there is an edge from n to m.
assertEdge(nNode, mNode, hard: true);
}
Future<void> test_is_not() async {
await analyze('''
void f(num n) {
if (n is! int) {} else {
g(n);
}
h(n);
}
void g(int i) {}
void h(num m) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var nNode = decoratedTypeAnnotation('num n').node;
var mNode = decoratedTypeAnnotation('num m').node;
// No edge from n to i because n is known to be non-nullable at the site of
// the call to g
assertNoEdge(nNode, iNode);
// But there is an edge from n to m.
assertEdge(nNode, mNode, hard: true);
}
Future<void> test_local_function_parameters() async {
await analyze('''
void f() {
void g(int i, int j) {
if (i == null) return;
print(i.isEven);
print(j.isEven);
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_not() async {
await analyze('''
void f(int i) {
if (!(i == null)) {
h(i);
} else {
g(i);
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is known to be non-nullable at the site of
// the call to h()
assertNoEdge(iNode, kNode);
// But there is an edge from i to j
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_not_null_then_promote() async {
await analyze('''
void f(dynamic n) {
if (n == null) {
return;
} else if (n is List<int>) {
n.length; // Ensure this doesn't crash during method lookup.
g(n); // Test this is wired correctly in graph.
}
}
void g(List<int> j) {}
''');
var nNode = decoratedTypeAnnotation('dynamic n').node;
var jNode = decoratedTypeAnnotation('List<int> j').node;
var jParamNode = decoratedTypeAnnotation('int> j').node;
var intParamNode = decoratedTypeAnnotation('int>)').node;
// No edge from n to g because i is known to be non-nullable at the site of
// the call to g()
assertNoEdge(nNode, jNode);
// But there is an edge from nonNull(List<int>) to j
assertEdge(inSet(neverClosure), jNode, hard: false);
assertEdge(intParamNode, jParamNode, hard: false, checkable: false);
}
Future<void> test_postfixDecrement() async {
await analyze('''
void f(C c1) {
if (c1 != null) {
g(c1);
c1--;
h(c1);
}
}
void g(C c2) {}
void h(C c3) {}
class C {
C operator-(int i) => this;
}
''');
var c1Node = decoratedTypeAnnotation('C c1').node;
var c2Node = decoratedTypeAnnotation('C c2').node;
var c3Node = decoratedTypeAnnotation('C c3').node;
// No edge from c1 to c2 because c1's type is promoted to non-nullable
assertNoEdge(c1Node, c2Node);
// But there is an edge from c1 to c3, because the decrement un-does the
// promotion.
assertEdge(c1Node, c3Node, hard: false);
}
Future<void> test_postfixIncrement() async {
await analyze('''
void f(C c1) {
if (c1 != null) {
g(c1);
c1++;
h(c1);
}
}
void g(C c2) {}
void h(C c3) {}
class C {
C operator+(int i) => this;
}
''');
var c1Node = decoratedTypeAnnotation('C c1').node;
var c2Node = decoratedTypeAnnotation('C c2').node;
var c3Node = decoratedTypeAnnotation('C c3').node;
// No edge from c1 to c2 because c1's type is promoted to non-nullable
assertNoEdge(c1Node, c2Node);
// But there is an edge from c1 to c3, because the increment un-does the
// promotion.
assertEdge(c1Node, c3Node, hard: false);
}
Future<void> test_prefixDecrement() async {
await analyze('''
void f(C c1) {
if (c1 != null) {
g(c1);
--c1;
h(c1);
}
}
void g(C c2) {}
void h(C c3) {}
class C {
C operator-(int i) => this;
}
''');
var c1Node = decoratedTypeAnnotation('C c1').node;
var c2Node = decoratedTypeAnnotation('C c2').node;
var c3Node = decoratedTypeAnnotation('C c3').node;
// No edge from c1 to c2 because c1's type is promoted to non-nullable
assertNoEdge(c1Node, c2Node);
// But there is an edge from c1 to c3, because the decrement un-does the
// promotion.
assertEdge(c1Node, c3Node, hard: false);
}
Future<void> test_prefixIncrement() async {
await analyze('''
void f(C c1) {
if (c1 != null) {
g(c1);
++c1;
h(c1);
}
}
void g(C c2) {}
void h(C c3) {}
class C {
C operator+(int i) => this;
}
''');
var c1Node = decoratedTypeAnnotation('C c1').node;
var c2Node = decoratedTypeAnnotation('C c2').node;
var c3Node = decoratedTypeAnnotation('C c3').node;
// No edge from c1 to c2 because c1's type is promoted to non-nullable
assertNoEdge(c1Node, c2Node);
// But there is an edge from c1 to c3, because the increment un-does the
// promotion.
assertEdge(c1Node, c3Node, hard: false);
}
Future<void> test_rethrow() async {
await analyze('''
void f(int i, int j) {
try {
g();
} catch (_) {
if (i == null) rethrow;
print(i.isEven);
print(j.isEven);
}
}
void g() {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_return() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
print(i.isEven);
print(j.isEven);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_switch_break_target() async {
await analyze('''
void f(int i, int x, int y) {
L: switch (x) {
default:
switch (y) {
default:
if (i != null) break L;
if (b()) break;
return;
}
g(i);
return;
}
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_switch_cancels_promotions_for_labeled_cases() async {
await analyze('''
void f(int i, int x, bool b) {
if (i == null) return;
switch (x) {
L:
case 1:
g(i);
break;
case 2:
h(i);
i = null;
if (b) continue L;
break;
}
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i's type is promoted to non-nullable at the
// time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_switch_default() async {
await analyze('''
void f(int i, int j, int x, int y) {
if (i == null) {
switch (x) {
default: return;
}
}
if (j == null) {
switch (y) {
case 0: return;
}
}
i.isEven;
j.isEven;
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because the switch statement is guaranteed to
// complete by returning, so i is promoted to non-nullable.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never, because the switch statement is not
// guaranteed to complete by returning, so j is not promoted.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_throw() async {
await analyze('''
void f(int i, int j) {
if (i == null) throw 'foo';
print(i.isEven);
print(j.isEven);
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to `never` because i's type is promoted to non-nullable
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to `never`.
assertEdge(jNode, inSet(pointsToNever), hard: true);
}
Future<void> test_topLevelVar_initializer() async {
await analyze('''
bool b1 = true;
bool b2 = true;
bool b3 = b1 || b2;
''');
// No assertions; we just want to verify that the presence of `||` inside a
// top level variable doesn't cause flow analysis to crash.
}
Future<void> test_try_falls_through_to_after_try() async {
await analyze('''
void f(int i) {
try {
g(i);
if (i == null) return;
} catch (_) {
return;
}
h(i);
}
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i's type is promoted to non-nullable
assertNoEdge(iNode, kNode);
// But there is an edge from i to j.
assertEdge(iNode, jNode, hard: true);
}
Future<void> test_while_break_target() async {
await analyze('''
void f(int i) {
L: while (true) {
while (true) {
if (i != null) break L;
if (b()) break;
}
g(i);
}
h(i);
}
bool b() => true;
void g(int j) {}
void h(int k) {}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
var kNode = decoratedTypeAnnotation('int k').node;
// No edge from i to k because i is promoted at the time of the call to h.
assertNoEdge(iNode, kNode);
// But there is an edge from i to j, because i is not promoted at the time
// of the call to g.
assertEdge(iNode, jNode, hard: false);
}
Future<void> test_while_cancels_promotions_for_assignments_in_body() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
while (true) {
i.isEven;
j.isEven;
j = null;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void>
test_while_cancels_promotions_for_assignments_in_condition() async {
await analyze('''
void f(int i, int j) {
if (i == null) return;
if (j == null) return;
while (i.isEven && j.isEven && g(j = null)) {}
}
bool g(int k) => true;
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never because its promotion was cancelled.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
Future<void> test_while_promotes() async {
await analyze('''
void f(int i, int j) {
while (i != null) {
i.isEven;
j.isEven;
}
}
''');
var iNode = decoratedTypeAnnotation('int i').node;
var jNode = decoratedTypeAnnotation('int j').node;
// No edge from i to never because is is promoted.
assertNoEdge(iNode, inSet(pointsToNever));
// But there is an edge from j to never.
assertEdge(jNode, inSet(pointsToNever), hard: false);
}
}