blob: 46c0d8d34f326b142b6990ba33f96718b654dc42 [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/src/generated/resolver.dart';
import 'package:analyzer/src/test_utilities/find_node.dart';
import 'package:test/test.dart';
import 'package:test_reflective_loader/test_reflective_loader.dart';
import '../ast/parse_base.dart';
import '../resolution/driver_resolution.dart';
main() {
defineReflectiveSuite(() {
defineReflectiveTests(ExitDetectorParsedStatementTest);
defineReflectiveTests(ExitDetectorResolvedStatementTest);
defineReflectiveTests(ExitDetectorForCodeAsUiTest);
});
}
/// Tests for the [ExitDetector] that require that the control flow and spread
/// experiments be enabled.
@reflectiveTest
class ExitDetectorForCodeAsUiTest extends ParseBase {
test_for_condition() async {
_assertTrue('[for (; throw 0;) 0]');
}
test_for_implicitTrue() async {
_assertTrue('[for (;;) 0]');
}
test_for_initialization() async {
_assertTrue('[for (i = throw 0;;) 0]');
}
test_for_true() async {
_assertTrue('[for (; true; ) 0]');
}
test_for_true_if_return() async {
_assertTrue('[for (; true; ) if (true) throw 42]');
}
test_for_true_noBreak() async {
_assertTrue('[for (; true; ) 0]');
}
test_for_updaters() async {
_assertTrue('[for (;; i++, throw 0) 1]');
}
test_for_variableDeclaration() async {
_assertTrue('[for (int i = throw 0;;) 1]');
}
test_forEach() async {
_assertFalse('[for (element in list) 0]');
}
test_forEach_throw() async {
_assertTrue('[for (element in throw 42) 0]');
}
test_if_false_else_throw() async {
_assertTrue('[if (false) 0 else throw 42]');
}
test_if_false_noThrow() async {
_assertFalse('[if (false) 0]');
}
test_if_false_throw() async {
_assertFalse('[if (false) throw 42]');
}
test_if_noThrow() async {
_assertFalse('[if (c) i++]');
}
test_if_throw() async {
_assertFalse('[if (c) throw 42]');
}
test_if_true_noThrow() async {
_assertFalse('[if (true) 0]');
}
test_if_true_throw() async {
_assertTrue('[if (true) throw 42]');
}
test_ifElse_bothThrow() async {
_assertTrue("[if (c) throw 0 else throw 1]");
}
test_ifElse_elseThrow() async {
_assertFalse('[if (c) 0 else throw 42]');
}
test_ifElse_noThrow() async {
_assertFalse('[if (c) 0 else 1]');
}
test_ifElse_thenThrow() async {
_assertFalse('[if (c) throw 42 else 0]');
}
void _assertFalse(String expressionCode) {
_assertHasReturn(expressionCode, false);
}
void _assertHasReturn(String expressionCode, bool expected) {
var path = convertPath('/test/lib/test.dart');
newFile(path, content: '''
void f() { // ref
$expressionCode;
}
''');
var parseResult = parseUnit(path);
expect(parseResult.errors, isEmpty);
var findNode = FindNode(parseResult.content, parseResult.unit);
var block = findNode.block('{ // ref');
var statement = block.statements.single as ExpressionStatement;
var expression = statement.expression;
var actual = ExitDetector.exits(expression);
expect(actual, expected);
}
void _assertTrue(String expressionCode) {
_assertHasReturn(expressionCode, true);
}
}
/// Tests for the [ExitDetector] that do not require that the AST be resolved.
///
/// See [ExitDetectorResolvedStatementTest] for tests that require the AST to be resolved.
@reflectiveTest
class ExitDetectorParsedStatementTest extends ParseBase {
test_asExpression() async {
_assertFalse('a as Object;');
}
test_asExpression_throw() async {
_assertTrue('throw 42 as Object;');
}
test_assertStatement() async {
_assertFalse("assert(a);");
}
test_assertStatement_throw() async {
_assertFalse('assert((throw 0));');
}
test_assignmentExpression() async {
_assertFalse('v = 1;');
}
@failingTest
test_assignmentExpression_compound_lazy() async {
_assertFalse('v ||= false;');
}
test_assignmentExpression_lhs_throw() async {
_assertTrue('a[throw 42] = 0;');
}
test_assignmentExpression_rhs_throw() async {
_assertTrue('v = throw 42;');
}
test_await_false() async {
_assertFalse('await x;');
}
test_await_throw_true() async {
_assertTrue('bool b = await (throw 42 || true);');
}
test_binaryExpression_and() async {
_assertFalse('a && b;');
}
test_binaryExpression_and_lhs() async {
_assertTrue('throw 42 && b;');
}
test_binaryExpression_and_rhs() async {
_assertFalse('a && (throw 42);');
}
test_binaryExpression_and_rhs2() async {
_assertFalse('false && (throw 42);');
}
test_binaryExpression_and_rhs3() async {
_assertTrue('true && (throw 42);');
}
test_binaryExpression_ifNull() async {
_assertFalse('a ?? b;');
}
test_binaryExpression_ifNull_lhs() async {
_assertTrue('throw 42 ?? b;');
}
test_binaryExpression_ifNull_rhs() async {
_assertFalse('a ?? (throw 42);');
}
test_binaryExpression_ifNull_rhs2() async {
_assertFalse('null ?? (throw 42);');
}
test_binaryExpression_or() async {
_assertFalse('a || b;');
}
test_binaryExpression_or_lhs() async {
_assertTrue('throw 42 || b;');
}
test_binaryExpression_or_rhs() async {
_assertFalse('a || (throw 42);');
}
test_binaryExpression_or_rhs2() async {
_assertFalse('true || (throw 42);');
}
test_binaryExpression_or_rhs3() async {
_assertTrue('false || (throw 42);');
}
test_block_empty() async {
_assertFalse('{}');
}
test_block_noReturn() async {
_assertFalse('{ int i = 0; }');
}
test_block_return() async {
_assertTrue('{ return 0; }');
}
test_block_returnNotLast() async {
_assertTrue('{ return 0; throw 42; }');
}
test_block_throwNotLast() async {
_assertTrue('{ throw 0; x = null; }');
}
test_cascadeExpression_argument() async {
_assertTrue('a..b(throw 42);');
}
test_cascadeExpression_index() async {
_assertTrue('a..[throw 42];');
}
test_cascadeExpression_target() async {
_assertTrue('throw a..b();');
}
test_conditional_ifElse_bothThrows() async {
_assertTrue('c ? throw 42 : throw 42;');
}
test_conditional_ifElse_elseThrows() async {
_assertFalse('c ? i : throw 42;');
}
test_conditional_ifElse_noThrow() async {
_assertFalse('c ? i : j;');
}
test_conditional_ifElse_thenThrow() async {
_assertFalse('c ? throw 42 : j;');
}
test_conditionalAccess() async {
_assertFalse('a?.b;');
}
test_conditionalAccess_lhs() async {
_assertTrue('(throw 42)?.b;');
}
test_conditionalAccessAssign() async {
_assertFalse('a?.b = c;');
}
test_conditionalAccessAssign_lhs() async {
_assertTrue('(throw 42)?.b = c;');
}
test_conditionalAccessAssign_rhs() async {
_assertFalse('a?.b = throw 42;');
}
test_conditionalAccessAssign_rhs2() async {
_assertFalse("null?.b = throw 42;");
}
test_conditionalAccessIfNullAssign() async {
_assertFalse('a?.b ??= c;');
}
test_conditionalAccessIfNullAssign_lhs() async {
_assertTrue('(throw 42)?.b ??= c;');
}
test_conditionalAccessIfNullAssign_rhs() async {
_assertFalse('a?.b ??= throw 42;');
}
test_conditionalAccessIfNullAssign_rhs2() async {
_assertFalse('null?.b ??= throw 42;');
}
test_conditionalCall() async {
_assertFalse('a?.b(c);');
}
test_conditionalCall_lhs() async {
_assertTrue('(throw 42)?.b(c);');
}
test_conditionalCall_rhs() async {
_assertFalse('a?.b(throw 42);');
}
test_conditionalCall_rhs2() async {
_assertFalse('null?.b(throw 42);');
}
test_doStatement_break_and_throw() async {
_assertFalse('''
{
do {
if (1 == 1) break;
throw 42;
} while (0 == 1);
}
''');
}
test_doStatement_continue_and_throw() async {
_assertFalse('''
{
do {
if (1 == 1) continue;
throw 42;
} while (0 == 1);
}
''');
}
test_doStatement_continueDoInSwitch_and_throw() async {
_assertFalse('''
{
D: do {
switch (1) {
L: case 0: continue D;
M: case 1: break;
}
throw 42;
} while (0 == 1);
}''');
}
test_doStatement_continueInSwitch_and_throw() async {
_assertFalse('''
{
do {
switch (1) {
L: case 0: continue;
M: case 1: break;
}
throw 42;
} while (0 == 1);
}''');
}
test_doStatement_return() async {
_assertTrue('{ do { return null; } while (1 == 2); }');
}
test_doStatement_throwCondition() async {
_assertTrue('{ do {} while (throw 42); }');
}
test_doStatement_true_break() async {
_assertFalse('{ do { break; } while (true); }');
}
test_doStatement_true_continue() async {
_assertTrue('{ do { continue; } while (true); }');
}
test_doStatement_true_continueWithLabel() async {
_assertTrue('{ x: do { continue x; } while (true); }');
}
test_doStatement_true_if_return() async {
_assertTrue('{ do { if (true) {return null;} } while (true); }');
}
test_doStatement_true_noBreak() async {
_assertTrue('{ do {} while (true); }');
}
test_doStatement_true_return() async {
_assertTrue('{ do { return null; } while (true); }');
}
test_emptyStatement() async {
_assertFalse(';');
}
test_forEachStatement() async {
_assertFalse('for (element in list) {}');
}
test_forEachStatement_throw() async {
_assertTrue('for (element in throw 42) {}');
}
test_forStatement_condition() async {
_assertTrue('for (; throw 0;) {}');
}
test_forStatement_implicitTrue() async {
_assertTrue('for (;;) {}');
}
test_forStatement_implicitTrue_break() async {
_assertFalse('for (;;) { break; }');
}
test_forStatement_implicitTrue_if_break() async {
_assertFalse('''
{
for (;;) {
if (1==2) {
var a = 1;
} else {
break;
}
}
}
''');
}
test_forStatement_initialization() async {
_assertTrue('for (i = throw 0;;) {}');
}
test_forStatement_true() async {
_assertTrue('for (; true; ) {}');
}
test_forStatement_true_break() async {
_assertFalse('{ for (; true; ) { break; } }');
}
test_forStatement_true_continue() async {
_assertTrue('{ for (; true; ) { continue; } }');
}
test_forStatement_true_if_return() async {
_assertTrue('{ for (; true; ) { if (true) {return null;} } }');
}
test_forStatement_true_noBreak() async {
_assertTrue('{ for (; true; ) {} }');
}
test_forStatement_updaters() async {
_assertTrue('for (;; i++, throw 0) {}');
}
test_forStatement_variableDeclaration() async {
_assertTrue('for (int i = throw 0;;) {}');
}
test_functionExpression() async {
_assertFalse('(){};');
}
test_functionExpression_bodyThrows() async {
_assertFalse('(int i) => throw 42;');
}
test_functionExpressionInvocation() async {
_assertFalse('f(g);');
}
test_functionExpressionInvocation_argumentThrows() async {
_assertTrue('f(throw 42);');
}
test_functionExpressionInvocation_targetThrows() async {
_assertTrue("(throw 42)(g);");
}
test_identifier_prefixedIdentifier() async {
_assertFalse('a.b;');
}
test_identifier_simpleIdentifier() async {
_assertFalse('a;');
}
test_if_false_else_return() async {
_assertTrue('if (false) {} else { return 0; }');
}
test_if_false_noReturn() async {
_assertFalse('if (false) {}');
}
test_if_false_return() async {
_assertFalse('if (false) { return 0; }');
}
test_if_noReturn() async {
_assertFalse('if (c) i++;');
}
test_if_return() async {
_assertFalse('if (c) return 0;');
}
test_if_true_noReturn() async {
_assertFalse('if (true) {}');
}
test_if_true_return() async {
_assertTrue('if (true) { return 0; }');
}
test_ifElse_bothReturn() async {
_assertTrue('if (c) return 0; else return 1;');
}
test_ifElse_elseReturn() async {
_assertFalse('if (c) i++; else return 1;');
}
test_ifElse_noReturn() async {
_assertFalse('if (c) i++; else j++;');
}
test_ifElse_thenReturn() async {
_assertFalse('if (c) return 0; else j++;');
}
test_ifNullAssign() async {
_assertFalse('a ??= b;');
}
test_ifNullAssign_rhs() async {
_assertFalse('a ??= throw 42;');
}
test_indexExpression() async {
_assertFalse('a[b];');
}
test_indexExpression_index() async {
_assertTrue('a[throw 42];');
}
test_indexExpression_target() async {
_assertTrue("(throw 42)[b];");
}
test_instanceCreationExpression() async {
_assertFalse('new A(b);');
}
test_instanceCreationExpression_argumentThrows() async {
_assertTrue('new A(throw 42);');
}
test_isExpression() async {
_assertFalse('A is B;');
}
test_isExpression_throws() async {
_assertTrue('throw 42 is B;');
}
test_labeledStatement() async {
_assertFalse('label: a;');
}
test_labeledStatement_throws() async {
_assertTrue('label: throw 42;');
}
test_literal_boolean() async {
_assertFalse('true;');
}
test_literal_double() async {
_assertFalse('1.1;');
}
test_literal_integer() async {
_assertFalse('1;');
}
test_literal_null() async {
_assertFalse('null;');
}
test_literal_String() async {
_assertFalse('"str";');
}
test_methodInvocation() async {
_assertFalse('a.b(c);');
}
test_methodInvocation_argument() async {
_assertTrue('a.b(throw 42);');
}
test_methodInvocation_target() async {
_assertTrue("(throw 42).b(c);");
}
test_parenthesizedExpression() async {
_assertFalse('(a);');
}
test_parenthesizedExpression_throw() async {
_assertTrue('(throw 42);');
}
test_propertyAccess() async {
_assertFalse('new Object().a;');
}
test_propertyAccess_throws() async {
_assertTrue('(throw 42).a;');
}
test_rethrow() async {
_assertTrue('rethrow;');
}
test_return() async {
_assertTrue('return 0;');
}
test_superExpression() async {
_assertFalse('super.a;');
}
test_switch_allReturn() async {
_assertTrue('switch (i) { case 0: return 0; default: return 1; }');
}
test_switch_defaultWithNoStatements() async {
_assertFalse('switch (i) { case 0: return 0; default: }');
}
test_switch_fallThroughToNotReturn() async {
_assertFalse(r'''
switch (i) {
case 0:
case 1:
break;
default:
return 1;
}
''');
}
test_switch_fallThroughToReturn() async {
_assertTrue(r'''
switch (i) {
case 0:
case 1:
return 0;
default:
return 1;
}
''');
}
@failingTest
test_switch_includesContinue() async {
_assertTrue('''
switch (i) {
zero: case 0: return 0;
case 1: continue zero;
default: return 1;
}''');
}
test_switch_noDefault() async {
_assertFalse('switch (i) { case 0: return 0; }');
}
// The ExitDetector could conceivably follow switch continue labels and
// determine that `case 0` exits, `case 1` continues to an exiting case, and
// `default` exits, so the switch exits.
test_switch_nonReturn() async {
_assertFalse('switch (i) { case 0: i++; default: return 1; }');
}
test_thisExpression() async {
_assertFalse('this.a;');
}
test_throwExpression() async {
_assertTrue('throw new Object();');
}
test_tryStatement_noReturn() async {
_assertFalse('try {} catch (e, s) {} finally {}');
}
test_tryStatement_noReturn_noFinally() async {
_assertFalse('try {} catch (e, s) {}');
}
test_tryStatement_return_catch() async {
_assertFalse('try {} catch (e, s) { return 1; } finally {}');
}
test_tryStatement_return_catch_noFinally() async {
_assertFalse('try {} catch (e, s) { return 1; }');
}
test_tryStatement_return_finally() async {
_assertTrue('try {} catch (e, s) {} finally { return 1; }');
}
test_tryStatement_return_try_noCatch() async {
_assertTrue('try { return 1; } finally {}');
}
test_tryStatement_return_try_oneCatchDoesNotExit() async {
_assertFalse('try { return 1; } catch (e, s) {} finally {}');
}
test_tryStatement_return_try_oneCatchDoesNotExit_noFinally() async {
_assertFalse('try { return 1; } catch (e, s) {}');
}
test_tryStatement_return_try_oneCatchExits() async {
_assertTrue('''
try {
return 1;
} catch (e, s) {
return 1;
} finally {}
''');
}
test_tryStatement_return_try_oneCatchExits_noFinally() async {
_assertTrue('try { return 1; } catch (e, s) { return 1; }');
}
test_tryStatement_return_try_twoCatchesDoExit() async {
_assertTrue('''
try { return 1; }
on int catch (e, s) { return 1; }
on String catch (e, s) { return 1; }
finally {}
''');
}
test_tryStatement_return_try_twoCatchesDoExit_noFinally() async {
_assertTrue('''
try { return 1; }
on int catch (e, s) { return 1; }
on String catch (e, s) { return 1; }
''');
}
test_tryStatement_return_try_twoCatchesDoNotExit() async {
_assertFalse('''
try { return 1; }
on int catch (e, s) {}
on String catch (e, s) {}
finally {}
''');
}
test_tryStatement_return_try_twoCatchesDoNotExit_noFinally() async {
_assertFalse('''
try { return 1; }
on int catch (e, s) {}
on String catch (e, s) {}
''');
}
test_tryStatement_return_try_twoCatchesMixed() async {
_assertFalse('''
try { return 1; }
on int catch (e, s) {}
on String catch (e, s) { return 1; }
finally {}
''');
}
test_tryStatement_return_try_twoCatchesMixed_noFinally() async {
_assertFalse('''
try { return 1; }
on int catch (e, s) {}
on String catch (e, s) { return 1; }
''');
}
test_variableDeclarationStatement_noInitializer() async {
_assertFalse('int i;');
}
test_variableDeclarationStatement_noThrow() async {
_assertFalse('int i = 0;');
}
test_variableDeclarationStatement_throw() async {
_assertTrue('int i = throw new Object();');
}
test_whileStatement_false_nonReturn() async {
_assertFalse("{ while (false) {} }");
}
test_whileStatement_throwCondition() async {
_assertTrue('{ while (throw 42) {} }');
}
test_whileStatement_true_break() async {
_assertFalse('{ while (true) { break; } }');
}
test_whileStatement_true_break_and_throw() async {
_assertFalse('{ while (true) { if (1==1) break; throw 42; } }');
}
test_whileStatement_true_continue() async {
_assertTrue('{ while (true) { continue; } }');
}
test_whileStatement_true_continueWithLabel() async {
_assertTrue('{ x: while (true) { continue x; } }');
}
test_whileStatement_true_doStatement_scopeRequired() async {
_assertTrue('{ while (true) { x: do { continue x; } while (true); } }');
}
test_whileStatement_true_if_return() async {
_assertTrue('{ while (true) { if (true) {return null;} } }');
}
test_whileStatement_true_noBreak() async {
_assertTrue('{ while (true) {} }');
}
test_whileStatement_true_return() async {
_assertTrue('{ while (true) { return null; } }');
}
test_whileStatement_true_throw() async {
_assertTrue('{ while (true) { throw 42; } }');
}
void _assertFalse(String code) {
_assertHasReturn(code, false);
}
void _assertHasReturn(String statementCode, bool expected) {
var path = convertPath('/test/lib/test.dart');
newFile(path, content: '''
void f() { // ref
$statementCode
}
''');
var parseResult = parseUnit(path);
expect(parseResult.errors, isEmpty);
var findNode = FindNode(parseResult.content, parseResult.unit);
var block = findNode.block('{ // ref');
var statement = block.statements.single;
var actual = ExitDetector.exits(statement);
expect(actual, expected);
}
void _assertTrue(String code) {
_assertHasReturn(code, true);
}
}
/// Tests for the [ExitDetector] that require that the AST be resolved.
///
/// See [ExitDetectorParsedStatementTest] for tests that do not require the AST to be resolved.
/// TODO(paulberry): migrate this test away from the task model.
/// See dartbug.com/35734.
@reflectiveTest
class ExitDetectorResolvedStatementTest extends DriverResolutionTest {
test_forStatement_implicitTrue_breakWithLabel() async {
await _assertNthStatementDoesNotExit(r'''
void f() {
x: for (;;) {
if (1 < 2) {
break x;
}
return;
}
}
''', 0);
}
test_switch_withEnum_false_noDefault() async {
await _assertNthStatementDoesNotExit(r'''
enum E { A, B }
String f(E e) {
var x;
switch (e) {
case A:
x = 'A';
case B:
x = 'B';
}
return x;
}
''', 1);
}
test_switch_withEnum_false_withDefault() async {
await _assertNthStatementDoesNotExit(r'''
enum E { A, B }
String f(E e) {
var x;
switch (e) {
case A:
x = 'A';
default:
x = '?';
}
return x;
}
''', 1);
}
test_switch_withEnum_true_noDefault() async {
await _assertNthStatementDoesNotExit(r'''
enum E { A, B }
String f(E e) {
switch (e) {
case A:
return 'A';
case B:
return 'B';
}
}
''', 0);
}
test_switch_withEnum_true_withExitingDefault() async {
await _assertNthStatementExits(r'''
enum E { A, B }
String f(E e) {
switch (e) {
case A:
return 'A';
default:
return '?';
}
}
''', 0);
}
test_switch_withEnum_true_withNonExitingDefault() async {
await _assertNthStatementDoesNotExit(r'''
enum E { A, B }
String f(E e) {
var x;
switch (e) {
case A:
return 'A';
default:
x = '?';
}
}
''', 1);
}
test_whileStatement_breakWithLabel() async {
await _assertNthStatementDoesNotExit(r'''
void f() {
x: while (true) {
if (1 < 2) {
break x;
}
return;
}
}
''', 0);
}
test_whileStatement_breakWithLabel_afterExiting() async {
await _assertNthStatementExits(r'''
void f() {
x: while (true) {
return;
if (1 < 2) {
break x;
}
}
}
''', 0);
}
test_whileStatement_switchWithBreakWithLabel() async {
await _assertNthStatementDoesNotExit(r'''
void f() {
x: while (true) {
switch (true) {
case false: break;
case true: break x;
}
}
}
''', 0);
}
test_yieldStatement_plain() async {
await _assertNthStatementDoesNotExit(r'''
void f() sync* {
yield 1;
}
''', 0);
}
test_yieldStatement_star_plain() async {
await _assertNthStatementDoesNotExit(r'''
void f() sync* {
yield* 1;
}
''', 0);
}
test_yieldStatement_star_throw() async {
await _assertNthStatementExits(r'''
void f() sync* {
yield* throw '';
}
''', 0);
}
test_yieldStatement_throw() async {
await _assertNthStatementExits(r'''
void f() sync* {
yield throw '';
}
''', 0);
}
Future<void> _assertHasReturn(String code, int n, bool expected) async {
var path = convertPath('/test/lib/test.dart');
newFile(path, content: code);
var session = driver.currentSession;
var resolvedResult = await session.getResolvedUnit(path);
var unit = resolvedResult.unit;
FunctionDeclaration function = unit.declarations.last;
BlockFunctionBody body = function.functionExpression.body;
Statement statement = body.block.statements[n];
expect(ExitDetector.exits(statement), expected);
}
/// Assert that the [n]th statement in the last function declaration of
/// [code] exits.
Future<void> _assertNthStatementDoesNotExit(String code, int n) async {
await _assertHasReturn(code, n, false);
}
/// Assert that the [n]th statement in the last function declaration of
/// [code] does not exit.
Future<void> _assertNthStatementExits(String code, int n) async {
await _assertHasReturn(code, n, true);
}
}