767d5ecbfd
This reverts commit 6907345fb9.
Review-Url: https://codereview.chromium.org/2907093002 .
215 lines
7.4 KiB
Dart
215 lines
7.4 KiB
Dart
// Copyright (c) 2012, 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.
|
|
|
|
// Check the validity of string literals.
|
|
|
|
library stringvalidator;
|
|
|
|
import 'dart:collection';
|
|
|
|
import 'common.dart';
|
|
import 'package:front_end/src/fasta/scanner.dart' show Token;
|
|
import 'tree/dartstring.dart' show DartString;
|
|
import 'tree/nodes.dart' show StringQuoting;
|
|
import 'package:front_end/src/fasta/scanner/characters.dart';
|
|
|
|
class StringValidator {
|
|
final DiagnosticReporter reporter;
|
|
|
|
StringValidator(this.reporter);
|
|
|
|
DartString validateInterpolationPart(Token token, StringQuoting quoting,
|
|
{bool isFirst: false, bool isLast: false}) {
|
|
String source = token.lexeme;
|
|
int leftQuote = 0;
|
|
int rightQuote = 0;
|
|
if (isFirst) leftQuote = quoting.leftQuoteLength;
|
|
if (isLast) rightQuote = quoting.rightQuoteLength;
|
|
String content = copyWithoutQuotes(source, leftQuote, rightQuote);
|
|
return validateString(
|
|
token, token.charOffset + leftQuote, content, quoting);
|
|
}
|
|
|
|
static StringQuoting quotingFromString(String sourceString) {
|
|
Iterator<int> source = sourceString.codeUnits.iterator;
|
|
bool raw = false;
|
|
int leftQuoteLength = 1;
|
|
source.moveNext();
|
|
int quoteChar = source.current;
|
|
if (quoteChar == $r) {
|
|
raw = true;
|
|
source.moveNext();
|
|
quoteChar = source.current;
|
|
}
|
|
assert(quoteChar == $SQ || quoteChar == $DQ);
|
|
// String has at least one quote. Check it if has three.
|
|
// If it only has two, the string must be an empty string literal,
|
|
// and end after the second quote.
|
|
if (source.moveNext() && source.current == quoteChar && source.moveNext()) {
|
|
int code = source.current;
|
|
assert(code == quoteChar); // If not, there is a bug in the parser.
|
|
leftQuoteLength = 3;
|
|
|
|
// Check if a multiline string starts with optional whitespace followed by
|
|
// a newline (CR, LF or CR+LF).
|
|
// We also accept if these characters are escaped by a backslash.
|
|
int newLineLength = 1;
|
|
while (true) {
|
|
// Due to string-interpolations we are not guaranteed to see the
|
|
// trailing quoting characters. The invocations to `moveNext()` may
|
|
// therefore return false and the `current`-getter return `null`. The
|
|
// code does not need to handle this specially (as it will not find the
|
|
// newline characters).
|
|
source.moveNext();
|
|
code = source.current;
|
|
if (code == $BACKSLASH) {
|
|
newLineLength++;
|
|
source.moveNext();
|
|
code = source.current;
|
|
}
|
|
if (code == $TAB || code == $SPACE) {
|
|
newLineLength++;
|
|
continue;
|
|
}
|
|
if (code == $CR) {
|
|
if (source.moveNext() && source.current == $LF) {
|
|
newLineLength++;
|
|
}
|
|
leftQuoteLength += newLineLength;
|
|
} else if (code == $LF) {
|
|
leftQuoteLength += newLineLength;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
return StringQuoting.getQuoting(quoteChar, raw, leftQuoteLength);
|
|
}
|
|
|
|
/**
|
|
* Return the string [string] witout its [initial] first and [terminal] last
|
|
* characters. This is intended to be used to remove quotes from string
|
|
* literals (including an initial 'r' for raw strings).
|
|
*/
|
|
String copyWithoutQuotes(String string, int initial, int terminal) {
|
|
assert(0 <= initial);
|
|
assert(0 <= terminal);
|
|
assert(initial + terminal <= string.length);
|
|
return string.substring(initial, string.length - terminal);
|
|
}
|
|
|
|
void stringParseError(String message, Token token, int offset) {
|
|
reporter.reportErrorMessage(reporter.spanFromToken(token),
|
|
MessageKind.GENERIC, {'text': "$message @ $offset"});
|
|
}
|
|
|
|
/**
|
|
* Validates the escape sequences and special characters of a string literal.
|
|
* Returns a DartString if valid, and null if not.
|
|
*/
|
|
DartString validateString(
|
|
Token token, int startOffset, String string, StringQuoting quoting) {
|
|
// We need to check for invalid x and u escapes, for line
|
|
// terminators in non-multiline strings, and for invalid Unicode
|
|
// code points (either directly or as u-escape values).
|
|
int length = 0;
|
|
int index = startOffset;
|
|
bool containsEscape = false;
|
|
var stringIter = string.codeUnits.iterator;
|
|
for (HasNextIterator<int> iter = new HasNextIterator(stringIter);
|
|
iter.hasNext;
|
|
length++) {
|
|
index++;
|
|
int code = iter.next();
|
|
if (code == $BACKSLASH) {
|
|
if (quoting.raw) continue;
|
|
containsEscape = true;
|
|
if (!iter.hasNext) {
|
|
stringParseError("Incomplete escape sequence", token, index);
|
|
return null;
|
|
}
|
|
index++;
|
|
code = iter.next();
|
|
if (code == $x) {
|
|
for (int i = 0; i < 2; i++) {
|
|
if (!iter.hasNext) {
|
|
stringParseError("Incomplete escape sequence", token, index);
|
|
return null;
|
|
}
|
|
index++;
|
|
code = iter.next();
|
|
if (!isHexDigit(code)) {
|
|
stringParseError(
|
|
"Invalid character in escape sequence", token, index);
|
|
return null;
|
|
}
|
|
}
|
|
// A two-byte hex escape can't generate an invalid value.
|
|
continue;
|
|
} else if (code == $u) {
|
|
index++;
|
|
code = iter.hasNext ? iter.next() : 0;
|
|
int value = 0;
|
|
if (code == $OPEN_CURLY_BRACKET) {
|
|
// expect 1-6 hex digits.
|
|
int count = 0;
|
|
while (iter.hasNext) {
|
|
code = iter.next();
|
|
index++;
|
|
if (code == $CLOSE_CURLY_BRACKET) {
|
|
break;
|
|
}
|
|
if (!isHexDigit(code)) {
|
|
stringParseError(
|
|
"Invalid character in escape sequence", token, index);
|
|
return null;
|
|
}
|
|
count++;
|
|
value = value * 16 + hexDigitValue(code);
|
|
}
|
|
if (code != $CLOSE_CURLY_BRACKET || count == 0 || count > 6) {
|
|
int errorPosition = index - count;
|
|
if (count > 6) errorPosition += 6;
|
|
stringParseError(
|
|
"Invalid character in escape sequence", token, errorPosition);
|
|
return null;
|
|
}
|
|
} else {
|
|
// Expect four hex digits, including the one just read.
|
|
for (int i = 0; i < 4; i++) {
|
|
if (i > 0) {
|
|
if (iter.hasNext) {
|
|
index++;
|
|
code = iter.next();
|
|
} else {
|
|
code = 0;
|
|
}
|
|
}
|
|
if (!isHexDigit(code)) {
|
|
stringParseError(
|
|
"Invalid character in escape sequence", token, index);
|
|
return null;
|
|
}
|
|
value = value * 16 + hexDigitValue(code);
|
|
}
|
|
}
|
|
code = value;
|
|
}
|
|
}
|
|
if (code >= 0x10000) {
|
|
length++;
|
|
if (code > 0x10FFFF) {
|
|
stringParseError("Invalid code point", token, index);
|
|
return null;
|
|
}
|
|
}
|
|
}
|
|
// String literal successfully validated.
|
|
if (quoting.raw || !containsEscape) {
|
|
// A string without escapes could just as well have been raw.
|
|
return new DartString.rawString(string, length);
|
|
}
|
|
return new DartString.escapedString(string, length);
|
|
}
|
|
}
|