Improve invalid string literal error recovery

Change-Id: Ic7d5d3f19e97a0004254377170bcc91efc7bfb08
Reviewed-on: https://dart-review.googlesource.com/60360
Reviewed-by: Brian Wilkerson <brianwilkerson@google.com>
Commit-Queue: Dan Rubel <danrubel@google.com>
This commit is contained in:
Dan Rubel 2018-06-14 22:53:34 +00:00 committed by commit-bot@chromium.org
parent 84f077842b
commit c015e8d303
11 changed files with 164 additions and 276 deletions

View file

@ -206,7 +206,7 @@ class AstBuilder extends StackListener {
if (interpolationCount == 0) {
Token token = pop();
String value = unescapeString(token.lexeme);
String value = unescapeString(token.lexeme, token, this);
push(ast.simpleStringLiteral(token, value));
} else {
List<Object> parts = popTypedList(1 + interpolationCount * 2);

View file

@ -7,6 +7,13 @@ import 'package:analyzer/dart/ast/token.dart' show Token;
import 'package:analyzer/src/dart/error/syntactic_errors.dart';
import 'package:front_end/src/api_prototype/compilation_message.dart';
import 'package:front_end/src/fasta/messages.dart' show Code, Message;
import 'package:front_end/src/fasta/quote.dart'
show
incompleteHexSequence,
incompleteUnicodeSequence,
invalidCodePoint,
invalidHexSequenceCharacter,
invalidUnicodeCharacter;
/// An error reporter that knows how to convert a Fasta error into an analyzer
/// error.
@ -183,6 +190,23 @@ class FastaErrorReporter {
offset,
length);
return;
case "ERROR_IN_STRING_LITERAL":
String stringError = arguments['string'];
if (stringError == invalidCodePoint) {
errorReporter?.reportErrorForOffset(
ParserErrorCode.INVALID_CODE_POINT, offset, length, ['\\u{...}']);
} else if (stringError == invalidHexSequenceCharacter ||
stringError == incompleteHexSequence) {
errorReporter?.reportErrorForOffset(
ParserErrorCode.INVALID_HEX_ESCAPE, offset, length);
} else if (stringError == invalidUnicodeCharacter ||
stringError == incompleteUnicodeSequence) {
errorReporter?.reportErrorForOffset(
ParserErrorCode.INVALID_UNICODE_ESCAPE, offset, length);
} else {
throw 'Unhandled error in string literal: ${arguments['string']}';
}
return;
case "EXPECTED_CLASS_MEMBER":
errorReporter?.reportErrorForOffset(
ParserErrorCode.EXPECTED_CLASS_MEMBER, offset, length);

View file

@ -125,249 +125,6 @@ class ErrorParserTest_Fasta extends FastaParserTestCase
}
}
@override
@failingTest
void test_invalidCodePoint() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 251:40 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// test/generated/parser_fasta_test.dart 3196:39 ParserProxy._run
super.test_invalidCodePoint();
}
@override
@failingTest
void test_invalidHexEscape_invalidDigit() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 221:47 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// test/generated/parser_fasta_test.dart 3196:39 ParserProxy._run
super.test_invalidHexEscape_invalidDigit();
}
@override
@failingTest
void test_invalidHexEscape_tooFewDigits() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 217:52 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// test/generated/parser_fasta_test.dart 3196:39 ParserProxy._run
super.test_invalidHexEscape_tooFewDigits();
}
@override
@failingTest
void test_invalidUnicodeEscape_incomplete_noDigits() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 232:54 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_incomplete_noDigits();
}
@override
@failingTest
void test_invalidUnicodeEscape_incomplete_someDigits() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 232:54 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_incomplete_someDigits();
}
@override
@failingTest
void test_invalidUnicodeEscape_invalidDigit() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 240:54 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_invalidDigit();
}
@override
@failingTest
void test_invalidUnicodeEscape_tooFewDigits_fixed() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 240:54 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_tooFewDigits_fixed();
}
@override
@failingTest
void test_invalidUnicodeEscape_tooFewDigits_variable() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 235:49 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_tooFewDigits_variable();
}
@override
@failingTest
void test_invalidUnicodeEscape_tooManyDigits_variable() {
// TODO(brianwilkerson) Does not recover.
// Internal problem: Compiler cannot run without a compiler context.
// Tip: Are calls to the compiler wrapped in CompilerContext.runInContext?
// package:front_end/src/fasta/compiler_context.dart 81:7 CompilerContext.current
// package:front_end/src/fasta/command_line_reporting.dart 112:30 shouldThrowOn
// package:front_end/src/fasta/deprecated_problems.dart 41:7 deprecated_inputError
// package:front_end/src/fasta/quote.dart 181:5 unescapeCodeUnits.error
// package:front_end/src/fasta/quote.dart 251:40 unescapeCodeUnits
// package:front_end/src/fasta/quote.dart 147:13 unescape
// package:front_end/src/fasta/quote.dart 135:10 unescapeString
// package:analyzer/src/fasta/ast_builder.dart 159:22 AstBuilder.endLiteralString
// test/generated/parser_fasta_listener.dart 896:14 ForwardingTestListener.endLiteralString
// package:front_end/src/fasta/parser/parser.dart 3497:14 Parser.parseSingleLiteralString
// package:front_end/src/fasta/parser/parser.dart 3434:13 Parser.parseLiteralString
// package:front_end/src/fasta/parser/parser.dart 3133:14 Parser.parsePrimary
// package:front_end/src/fasta/parser/parser.dart 3097:14 Parser.parseUnaryExpression
// package:front_end/src/fasta/parser/parser.dart 2968:13 Parser.parsePrecedenceExpression
// package:front_end/src/fasta/parser/parser.dart 2942:11 Parser.parseExpression
// package:front_end/src/fasta/parser/parser.dart 2862:13 Parser.parseExpressionStatement
// package:front_end/src/fasta/parser/parser.dart 2790:14 Parser.parseStatementX
// package:front_end/src/fasta/parser/parser.dart 2722:20 Parser.parseStatement
// test/generated/parser_fasta_test.dart 3287:39 ParserProxy._run
super.test_invalidUnicodeEscape_tooManyDigits_variable();
}
@override
void test_method_invalidTypeParameterComments() {
// Ignored: Fasta does not support the generic comment syntax.

View file

@ -3998,8 +3998,8 @@ class Wrong<T> {
}
void test_invalidCodePoint() {
StringLiteral literal = parseExpression("'\\u{110000}'",
errors: [expectedError(ParserErrorCode.INVALID_CODE_POINT, 0, 10)]);
StringLiteral literal = parseExpression("'begin \\u{110000}'",
errors: [expectedError(ParserErrorCode.INVALID_CODE_POINT, 7, 9)]);
expectNotNullIfNoErrors(literal);
}
@ -4065,8 +4065,8 @@ class Wrong<T> {
}
void test_invalidHexEscape_invalidDigit() {
StringLiteral literal = parseExpression("'\\x0 a'",
errors: [expectedError(ParserErrorCode.INVALID_HEX_ESCAPE, 1, 3)]);
StringLiteral literal = parseExpression("'not \\x0 a'",
errors: [expectedError(ParserErrorCode.INVALID_HEX_ESCAPE, 5, 3)]);
expectNotNullIfNoErrors(literal);
}
@ -4248,7 +4248,7 @@ class Wrong<T> {
}
void test_invalidUnicodeEscape_invalidDigit() {
Expression expression = parseStringLiteral("'\\u0 a'");
Expression expression = parseStringLiteral("'\\u0 and some more'");
expectNotNullIfNoErrors(expression);
listener.assertErrors(
[expectedError(ParserErrorCode.INVALID_UNICODE_ESCAPE, 1, 3)]);
@ -4271,10 +4271,12 @@ class Wrong<T> {
void test_invalidUnicodeEscape_tooManyDigits_variable() {
Expression expression = parseStringLiteral("'\\u{12345678}'");
expectNotNullIfNoErrors(expression);
listener.assertErrors([
expectedError(ParserErrorCode.INVALID_UNICODE_ESCAPE, 1, 12),
expectedError(ParserErrorCode.INVALID_CODE_POINT, 1, 12)
]);
listener.assertErrors(usingFastaParser
? [expectedError(ParserErrorCode.INVALID_CODE_POINT, 1, 9)]
: [
expectedError(ParserErrorCode.INVALID_UNICODE_ESCAPE, 1, 12),
expectedError(ParserErrorCode.INVALID_CODE_POINT, 1, 12)
]);
}
void test_libraryDirectiveNotFirst() {

View file

@ -5421,6 +5421,26 @@ const MessageCode messageStaticOperator = const MessageCode("StaticOperator",
message: r"""Operators can't be static.""",
tip: r"""Try removing the keyword 'static'.""");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Template<Message Function(String string, int count)>
templateStringLiteralError =
const Template<Message Function(String string, int count)>(
messageTemplate: r"""#string at offset #count""",
withArguments: _withArgumentsStringLiteralError);
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Message Function(String string, int count)> codeStringLiteralError =
const Code<Message Function(String string, int count)>(
"StringLiteralError", templateStringLiteralError,
analyzerCode: "ERROR_IN_STRING_LITERAL", dart2jsCode: "*fatal*");
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
Message _withArgumentsStringLiteralError(String string, int count) {
return new Message(codeStringLiteralError,
message: """${string} at offset ${count}""",
arguments: {'string': string, 'count': count});
}
// DO NOT EDIT. THIS FILE IS GENERATED. SEE TOP OF FILE.
const Code<Null> codeSuperAsExpression = messageSuperAsExpression;

View file

@ -1248,6 +1248,12 @@ class ForwardingListener implements Listener {
listener?.handleStringJuxtaposition(literalCount);
}
@override
void handleUnescapeError(
String error, Token location, int offset, int length) {
listener?.handleUnescapeError(error, location, offset, length);
}
@override
void handleStringPart(Token token) {
listener?.handleStringPart(token);

View file

@ -7,7 +7,12 @@ library fasta.parser.listener;
import '../../scanner/token.dart' show Token, TokenType;
import '../fasta_codes.dart'
show Message, messageNativeClauseShouldBeAnnotation;
show
Message,
messageNativeClauseShouldBeAnnotation,
templateStringLiteralError;
import '../quote.dart' show UnescapeErrorListener;
import 'assert.dart' show Assert;
@ -30,7 +35,7 @@ import 'parser_error.dart' show ParserError;
///
/// Events starting with `handle` are used when isn't possible to have a begin
/// event.
class Listener {
class Listener implements UnescapeErrorListener {
final List<ParserError> recoverableErrors = <ParserError>[];
Uri get uri => null;
@ -1232,6 +1237,15 @@ class Listener {
.add(new ParserError.fromTokens(startToken, endToken, message));
}
@override
void handleUnescapeError(
String error, Token location, int stringOffset, int length) {
handleRecoverableError(
templateStringLiteralError.withArguments(error, stringOffset),
location,
location);
}
/// Signals to the listener that the previous statement contained a semantic
/// error (described by the given [message]). This method can also be called
/// after [handleExpressionFunctionBody], in which case it signals that the

View file

@ -130,27 +130,31 @@ String unescapeLastStringPart(String last, Quote quote) {
last.substring(0, last.length - lastQuoteLength(quote)), quote);
}
String unescapeString(String string) {
String unescapeString(String string,
[Object token, UnescapeErrorListener listener]) {
Quote quote = analyzeQuote(string);
return unescape(
string.substring(firstQuoteLength(string, quote),
string.length - lastQuoteLength(quote)),
quote);
quote,
token,
listener);
}
String unescape(String string, Quote quote) {
String unescape(String string, Quote quote,
[Object token, UnescapeErrorListener listener]) {
switch (quote) {
case Quote.Single:
case Quote.Double:
return !string.contains("\\")
? string
: unescapeCodeUnits(string.codeUnits, false);
: unescapeCodeUnits(string.codeUnits, false, token, listener);
case Quote.MultiLineSingle:
case Quote.MultiLineDouble:
return !string.contains("\\") && !string.contains("\r")
? string
: unescapeCodeUnits(string.codeUnits, false);
: unescapeCodeUnits(string.codeUnits, false, token, listener);
case Quote.RawSingle:
case Quote.RawDouble:
@ -160,24 +164,38 @@ String unescape(String string, Quote quote) {
case Quote.RawMultiLineDouble:
return !string.contains("\r")
? string
: unescapeCodeUnits(string.codeUnits, true);
: unescapeCodeUnits(string.codeUnits, true, token, listener);
}
return unhandled("$quote", "unescape", -1, null);
}
const String incompleteSequence = "Incomplete escape sequence.";
const String incompleteHexSequence = "Incomplete hex escape sequence.";
const String invalidCharacter = "Invalid character in escape sequence.";
const String invalidHexSequenceCharacter =
"Invalid character in hex escape sequence.";
const String invalidCodePoint = "Invalid code point.";
const String incompleteUnicodeSequence = "Incomplete unicode escape sequence.";
const String invalidUnicodeCharacter =
"Invalid character in unicode escape sequence.";
// Note: based on
// [StringValidator.validateString](pkg/compiler/lib/src/string_validator.dart).
String unescapeCodeUnits(List<int> codeUnits, bool isRaw) {
String unescapeCodeUnits(List<int> codeUnits, bool isRaw,
// TODO(danrubel): Update remaining call sites
// and make these parameters required.
[Object location,
UnescapeErrorListener listener]) {
// Can't use Uint8List or Uint16List here, the code units may be larger.
List<int> result = new List<int>(codeUnits.length);
int resultOffset = 0;
error(int offset, String message) {
error(String message, int offset, int length) {
if (location != null && listener != null && length != null) {
listener.handleUnescapeError(message, location, offset, length);
return new String.fromCharCodes(codeUnits);
}
deprecated_inputError(null, null, message);
}
@ -189,7 +207,9 @@ String unescapeCodeUnits(List<int> codeUnits, bool isRaw) {
}
code = $LF;
} else if (!isRaw && code == $BACKSLASH) {
if (codeUnits.length == ++i) return error(i, incompleteSequence);
if (codeUnits.length == ++i) {
return error(incompleteUnicodeSequence, i, 1);
}
code = codeUnits[i];
/// `\n` for newline, equivalent to `\x0A`.
@ -214,43 +234,71 @@ String unescapeCodeUnits(List<int> codeUnits, bool isRaw) {
code = $VTAB;
} else if (code == $x) {
// Expect exactly 2 hex digits.
if (codeUnits.length <= i + 2) return error(i, incompleteSequence);
int begin = i;
if (codeUnits.length <= i + 2) {
return error(
incompleteHexSequence, begin, codeUnits.length + 1 - begin);
}
code = 0;
for (int j = 0; j < 2; j++) {
int digit = codeUnits[++i];
if (!isHexDigit(digit)) return error(i, invalidCharacter);
if (!isHexDigit(digit)) {
return error(invalidHexSequenceCharacter, begin, i + 1 - begin);
}
code = (code << 4) + hexDigitValue(digit);
}
} else if (code == $u) {
if (codeUnits.length == i + 1) return error(i, incompleteSequence);
int begin = i;
if (codeUnits.length == i + 1) {
return error(
incompleteUnicodeSequence, begin, codeUnits.length + 1 - begin);
}
code = codeUnits[i + 1];
if (code == $OPEN_CURLY_BRACKET) {
// Expect 1-6 hex digits followed by '}'.
if (codeUnits.length == ++i) return error(i, incompleteSequence);
if (codeUnits.length == ++i) {
return error(incompleteUnicodeSequence, begin, i + 1 - begin);
}
code = 0;
for (int j = 0; j < 7; j++) {
if (codeUnits.length == ++i) return error(i, incompleteSequence);
if (codeUnits.length == ++i) {
return error(incompleteUnicodeSequence, begin, i + 1 - begin);
}
int digit = codeUnits[i];
if (j != 0 && digit == $CLOSE_CURLY_BRACKET) break;
if (!isHexDigit(digit)) return error(i, invalidCharacter);
if (!isHexDigit(digit)) {
return error(invalidUnicodeCharacter, begin, i + 2 - begin);
}
code = (code << 4) + hexDigitValue(digit);
}
} else {
// Expect exactly 4 hex digits.
if (codeUnits.length <= i + 4) return error(i, incompleteSequence);
if (codeUnits.length <= i + 4) {
return error(
incompleteUnicodeSequence, begin, codeUnits.length + 1 - begin);
}
code = 0;
for (int j = 0; j < 4; j++) {
int digit = codeUnits[++i];
if (!isHexDigit(digit)) return error(i, invalidCharacter);
if (!isHexDigit(digit)) {
return error(invalidUnicodeCharacter, begin, i + 1 - begin);
}
code = (code << 4) + hexDigitValue(digit);
}
}
if (code > 0x10FFFF) {
return error(invalidCodePoint, begin, i + 1 - begin);
}
} else {
// Nothing, escaped character is passed through;
}
if (code > 0x10FFFF) return error(i, invalidCodePoint);
}
result[resultOffset++] = code;
}
return new String.fromCharCodes(result, 0, resultOffset);
}
abstract class UnescapeErrorListener {
void handleUnescapeError(
String error, covariant location, int offset, int length);
}

View file

@ -12,7 +12,8 @@ import '../fasta_codes.dart'
show
Message,
messageNativeClauseShouldBeAnnotation,
templateInternalProblemStackNotEmpty;
templateInternalProblemStackNotEmpty,
templateStringLiteralError;
import '../parser.dart' show Listener, MemberKind, Parser;
@ -356,6 +357,15 @@ abstract class StackListener extends Listener {
throw deprecated_inputError(uri, token.charOffset, message.message);
}
@override
void handleUnescapeError(
String error, Token token, int stringOffset, int length) {
addCompileTimeError(
templateStringLiteralError.withArguments(error, stringOffset),
token.charOffset + stringOffset,
length);
}
void addCompileTimeError(Message message, int charOffset, int length);
}

View file

@ -341,6 +341,7 @@ SourceOutlineSummary/analyzerCode: Fail
SourceOutlineSummary/example: Fail
StackOverflow/example: Fail
StaticAfterConst/script1: Fail
StringLiteralError/expression: Fail
SuperAsExpression/analyzerCode: Fail
SuperAsExpression/example: Fail
SuperAsIdentifier/analyzerCode: Fail

View file

@ -766,6 +766,12 @@ StackOverflow:
analyzerCode: STACK_OVERFLOW
dart2jsCode: GENERIC
StringLiteralError:
template: "#string at offset #count"
analyzerCode: ERROR_IN_STRING_LITERAL
dart2jsCode: "*fatal*"
expression: "'\\u{110000'"
UnexpectedDollarInString:
template: "A '$' has special meaning inside a string, and must be followed by an identifier or an expression in curly braces ({})."
tip: "Try adding a backslash (\\) to escape the '$'."