Skip to content

Commit e656fec

Browse files
jensjohaCommit Queue
authored andcommitted
[CFE/Analyzer/parser] Fix crash upon double with separators and missing number after e
E.g. the user typing "1_234e" (and having yet to type a number) would crash the analyzer. Change-Id: Iff71c274a5e4719b3c8877ddbc9775d8d091c42f Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/437240 Reviewed-by: Johnni Winther <[email protected]> Reviewed-by: Lasse Nielsen <[email protected]> Commit-Queue: Jens Johansen <[email protected]> Reviewed-by: Samuel Rawlins <[email protected]>
1 parent d79ebc6 commit e656fec

File tree

11 files changed

+188
-2
lines changed

11 files changed

+188
-2
lines changed

pkg/_fe_analyzer_shared/lib/src/scanner/abstract_scanner.dart

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1696,7 +1696,9 @@ abstract class AbstractScanner implements Scanner {
16961696
} else {
16971697
if (!hasExponentDigits) {
16981698
appendSyntheticSubstringToken(
1699-
TokenType.DOUBLE,
1699+
hasSeparators
1700+
? TokenType.DOUBLE_WITH_SEPARATORS
1701+
: TokenType.DOUBLE,
17001702
start,
17011703
/* asciiOnly = */ true,
17021704
'0',
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
6+
import 'package:test/test.dart';
7+
import 'package:test_reflective_loader/test_reflective_loader.dart';
8+
9+
import '../dart/resolution/context_collection_resolution.dart';
10+
11+
main() {
12+
defineReflectiveSuite(() {
13+
defineReflectiveTests(NumberLiteralsWithSeparatorsTest);
14+
});
15+
}
16+
17+
@reflectiveTest
18+
class NumberLiteralsWithSeparatorsTest extends PubPackageResolutionTest {
19+
Future<void> assertHasErrors(String code) async {
20+
addTestFile(code);
21+
await resolveTestFile();
22+
expect(result.diagnostics, isNotEmpty);
23+
}
24+
25+
Future<void> test_double_with_separators_and_e() async {
26+
await assertNoErrorsInCode('double x = 1.234_567e2;');
27+
}
28+
29+
Future<void> test_missing_number_after_e_1() async {
30+
await assertErrorsInCode('dynamic x = 1_234_567e;', [
31+
error(ScannerErrorCode.MISSING_DIGIT, 21, 1),
32+
]);
33+
}
34+
35+
Future<void> test_missing_number_after_e_2() async {
36+
await assertErrorsInCode('dynamic x = 1.234_567e;', [
37+
error(ScannerErrorCode.MISSING_DIGIT, 21, 1),
38+
]);
39+
}
40+
41+
Future<void> test_other_erroneous_1() async {
42+
await assertHasErrors('dynamic x = 1_1e;');
43+
}
44+
45+
Future<void> test_other_erroneous_2() async {
46+
await assertHasErrors('dynamic x = 1_e;');
47+
}
48+
49+
Future<void> test_other_erroneous_3() async {
50+
await assertHasErrors('dynamic x = 1e_;');
51+
}
52+
53+
Future<void> test_other_erroneous_4() async {
54+
await assertHasErrors('dynamic x = 1e-_;');
55+
}
56+
57+
Future<void> test_other_erroneous_5() async {
58+
await assertHasErrors('dynamic x = 1e-_1;');
59+
}
60+
61+
Future<void> test_other_erroneous_6() async {
62+
await assertHasErrors('dynamic x = 1e+_;');
63+
}
64+
65+
Future<void> test_other_erroneous_7() async {
66+
await assertHasErrors('dynamic x = 1e+_1;');
67+
}
68+
69+
Future<void> test_simple_double_with_separators() async {
70+
await assertNoErrorsInCode('double x = 1.234_567;');
71+
}
72+
73+
Future<void> test_simple_int_with_separators() async {
74+
await assertNoErrorsInCode('int x = 1_234_567;');
75+
}
76+
}

pkg/analyzer/test/src/diagnostics/test_all.dart

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -692,6 +692,7 @@ import 'nullable_type_in_implements_clause_test.dart'
692692
as nullable_type_in_implements_clause;
693693
import 'nullable_type_in_on_clause_test.dart' as nullable_type_in_on_clause;
694694
import 'nullable_type_in_with_clause_test.dart' as nullable_type_in_with_clause;
695+
import 'number_literals_with_separators_test.dart' as number_literals_with_separators;
695696
import 'object_cannot_extend_another_class_test.dart'
696697
as object_cannot_extend_another_class;
697698
import 'obsolete_colon_for_default_value_test.dart'
@@ -1385,6 +1386,7 @@ main() {
13851386
nullable_type_in_implements_clause.main();
13861387
nullable_type_in_on_clause.main();
13871388
nullable_type_in_with_clause.main();
1389+
number_literals_with_separators.main();
13881390
object_cannot_extend_another_class.main();
13891391
obsolete_colon_for_default_value.main();
13901392
on_repeated.main();

pkg/front_end/test/precedence_info_test.dart

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,8 +398,14 @@ class PrecedenceInfoTest {
398398
}
399399

400400
void test_type() {
401-
void assertLexeme(String source, TokenType tt) {
401+
void assertLexeme(String source, TokenType tt,
402+
{bool skipErrorTokens = false}) {
402403
var token = scanString(source, includeComments: true).tokens;
404+
if (skipErrorTokens) {
405+
while (token is ErrorToken) {
406+
token = token.next!;
407+
}
408+
}
403409
expect(token.type, same(tt), reason: source);
404410
}
405411

@@ -413,5 +419,9 @@ class PrecedenceInfoTest {
413419
assertLexeme('#!/', TokenType.SCRIPT_TAG);
414420
assertLexeme('foo', TokenType.IDENTIFIER);
415421
assertLexeme('"foo"', TokenType.STRING);
422+
423+
// Invalid but recovers nicely.
424+
assertLexeme('1_e', TokenType.DOUBLE_WITH_SEPARATORS,
425+
skipErrorTokens: true);
416426
}
417427
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
void foo() {
6+
print(123_456e);
7+
print(1.234_567e);
8+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
library;
2+
//
3+
// Problems in library:
4+
//
5+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
6+
// Make sure there is an exponent, and remove any whitespace before it.
7+
// print(123_456e);
8+
// ^
9+
//
10+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
11+
// Make sure there is an exponent, and remove any whitespace before it.
12+
// print(1.234_567e);
13+
// ^
14+
//
15+
import self as self;
16+
import "dart:core" as core;
17+
18+
static method foo() → void {
19+
core::print(123456.0);
20+
core::print(1.234567);
21+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
library;
2+
//
3+
// Problems in library:
4+
//
5+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
6+
// Make sure there is an exponent, and remove any whitespace before it.
7+
// print(123_456e);
8+
// ^
9+
//
10+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
11+
// Make sure there is an exponent, and remove any whitespace before it.
12+
// print(1.234_567e);
13+
// ^
14+
//
15+
import self as self;
16+
import "dart:core" as core;
17+
18+
static method foo() → void {
19+
core::print(123456.0);
20+
core::print(1.234567);
21+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
library;
2+
//
3+
// Problems in library:
4+
//
5+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
6+
// Make sure there is an exponent, and remove any whitespace before it.
7+
// print(123_456e);
8+
// ^
9+
//
10+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
11+
// Make sure there is an exponent, and remove any whitespace before it.
12+
// print(1.234_567e);
13+
// ^
14+
//
15+
import self as self;
16+
17+
static method foo() → void
18+
;
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
library;
2+
//
3+
// Problems in library:
4+
//
5+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:6:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
6+
// Make sure there is an exponent, and remove any whitespace before it.
7+
// print(123_456e);
8+
// ^
9+
//
10+
// pkg/front_end/testcases/coverage/digit-separators/separators_errors_test.dart:7:9: Error: Numbers in exponential notation should always contain an exponent (an integer number with an optional sign).
11+
// Make sure there is an exponent, and remove any whitespace before it.
12+
// print(1.234_567e);
13+
// ^
14+
//
15+
import self as self;
16+
import "dart:core" as core;
17+
18+
static method foo() → void {
19+
core::print(123456.0);
20+
core::print(1.234567);
21+
}

pkg/front_end/testcases/textual_outline.status

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ regress/issue_31198: FormatterCrash
2828
super_parameters/var_before_super: FormatterCrash
2929
triple_shift/invalid_operator: FormatterCrash
3030

31+
coverage/digit-separators/separators_errors_test: EmptyOutput
3132
general/error_recovery/issue_38415.crash: EmptyOutput
3233
general/error_recovery/issue_39024.crash: EmptyOutput
3334
general/error_recovery/issue_39058.crash: EmptyOutput

0 commit comments

Comments
 (0)