Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright (c) 2012, 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 // Checks a tree structure for whether the string literals are valid. | |
| 6 | |
| 7 class StringValidatorTask extends CompilerTask { | |
| 8 // Validator has no state except the compiler, so we just create one | |
| 9 // per task. | |
| 10 final StringValidator validator; | |
| 11 | |
| 12 StringValidatorTask(Compiler compiler) | |
| 13 : super(compiler), | |
| 14 validator = new StringValidator(compiler); | |
| 15 | |
| 16 void validate(Node tree) { | |
| 17 tree.accept(validator); | |
| 18 } | |
| 19 } | |
| 20 | |
| 21 class StringValidator extends AbstractVisitor { | |
| 22 final Compiler compiler; | |
| 23 | |
| 24 StringValidator(this.compiler); | |
| 25 | |
| 26 void visitNode(Node node) { | |
| 27 node.visitChildren(this); | |
| 28 } | |
| 29 | |
| 30 visitLiteralString(LiteralString node) { | |
| 31 // This is a string literal where the source contains both start and | |
| 32 // end quotes. | |
| 33 // String literals that are part of string interpolations are | |
| 34 // handled there to account for their varying degree of quotedness. | |
| 35 SourceString source = node.value; | |
| 36 StringQuoting quoting = quotingFromString(source); | |
| 37 int leftQuote = quoting.leftQuoteLength; | |
| 38 int rightQuote = quoting.rightQuoteLength; | |
| 39 SourceString content = source.copyWithoutQuotes(leftQuote, rightQuote); | |
| 40 validateString(node, node.token.charOffset + leftQuote, content, quoting); | |
| 41 } | |
| 42 | |
| 43 // String interpolation validates the immediate strings and traverses | |
| 44 // the sub-expressions. | |
| 45 visitStringInterpolation(StringInterpolation node) { | |
| 46 LiteralString literalString = node.string; | |
| 47 SourceString source = literalString.value; | |
| 48 StringQuoting quoting = quotingFromString(source); | |
| 49 int offset = literalString.token.charOffset + quoting.leftQuoteLength; | |
| 50 SourceString string = source.copyWithoutQuotes(quoting.leftQuoteLength, 0); | |
| 51 for (StringInterpolationPart part in node.parts) { | |
| 52 validateString(literalString, offset, string, quoting); | |
| 53 part.expression.accept(this); | |
| 54 literalString = part.string; | |
| 55 offset = literalString.token.charOffset; | |
| 56 string = literalString.value; | |
| 57 } | |
| 58 string = string.copyWithoutQuotes(0, quoting.rightQuoteLength); | |
| 59 validateString(literalString, offset, string, quoting); | |
| 60 } | |
| 61 | |
| 62 static StringQuoting quotingFromString(SourceString sourceString) { | |
| 63 Iterator<int> source = sourceString.iterator(); | |
| 64 bool raw = false; | |
| 65 int quoteChar = source.next(); | |
| 66 if (quoteChar == $AT) { | |
| 67 raw = true; | |
| 68 quoteChar = source.next(); | |
| 69 } | |
| 70 assert(quoteChar === $SQ || quoteChar === $DQ); | |
| 71 // String has one quote. Check it if has three. | |
|
karlklose
2012/01/25 08:47:26
one -> at least one
Lasse Reichstein Nielsen
2012/01/26 10:14:20
Done.
| |
| 72 // If it only have two, the string must be an empty string literal, | |
| 73 // and end after the second quote. | |
| 74 bool multiline = false; | |
| 75 if (source.hasNext() && source.next() == quoteChar && source.hasNext()) { | |
| 76 assert(source.next() == quoteChar); | |
| 77 multiline = true; | |
| 78 } | |
| 79 return StringQuoting.get(quoteChar, raw, multiline); | |
| 80 } | |
| 81 | |
| 82 void stringParseError(String message, LiteralString node, int offset) { | |
| 83 assert(node.quotedString === null); | |
| 84 compiler.cancel("$message @ $offset", node); | |
| 85 } | |
| 86 | |
| 87 /** | |
| 88 * Validates the escape sequences and special characters of a string literal. | |
| 89 * Returns the number of actual scalar values in the corresponding Dart | |
|
karlklose
2012/01/25 08:47:26
It returns whether the string is valid or not, doe
Lasse Reichstein Nielsen
2012/01/26 10:14:20
True. Old comment is old.
| |
| 90 * string, or a negative value if the string is invalid. | |
| 91 */ | |
| 92 bool validateString(LiteralString node, | |
| 93 int startOffset, | |
| 94 SourceString string, | |
| 95 StringQuoting quoting) { | |
| 96 // We only need to check for invalid x and u escapes, for line | |
| 97 // terminators in non-multiline strings, and for invalid Unicode | |
| 98 // scalar values (either directly or as u-escape values). | |
| 99 int length = 0; | |
| 100 int index = startOffset; | |
| 101 for(Iterator<int> iter = string.iterator(); iter.hasNext(); length++) { | |
| 102 index++; | |
| 103 int code = iter.next(); | |
| 104 if (code === $BACKSLASH) { | |
| 105 if (quoting.raw) continue; | |
| 106 if (!iter.hasNext()) { | |
| 107 stringParseError("Incomplete escape sequence", node, index); | |
| 108 return false; | |
| 109 } | |
| 110 index++; | |
| 111 code = iter.next(); | |
| 112 if (code === $x) { | |
| 113 for (int i = 0; i < 2; i++) { | |
| 114 if (!iter.hasNext()) { | |
| 115 stringParseError("Incomplete escape sequence", node, index); | |
| 116 return false; | |
| 117 } | |
| 118 index++; | |
| 119 code = iter.next(); | |
| 120 if (!isHexDigit(code)) { | |
| 121 stringParseError("Invalid character in escape sequence", | |
| 122 node, index); | |
| 123 return false; | |
| 124 } | |
| 125 } | |
| 126 continue; | |
| 127 } else if (code === $u) { | |
| 128 int escapeStart = index - 1; | |
| 129 index++; | |
| 130 code = iter.next(); | |
| 131 int value = 0; | |
| 132 if (code == $OPEN_CURLY_BRACKET) { | |
| 133 // expect 1-7 hex digits. | |
| 134 int count = 0; | |
| 135 index++; | |
| 136 code = iter.next(); | |
| 137 do { | |
| 138 if (!isHexDigit(code)) { | |
| 139 stringParseError("Invalid character in escape sequence", | |
| 140 node, index); | |
| 141 return false; | |
| 142 } | |
| 143 count++; | |
| 144 value = value * 16 + hexDigitValue(code); | |
| 145 index++; | |
| 146 code = iter.next(); | |
| 147 } while (code != $CLOSE_CURLY_BRACKET); | |
| 148 if (count > 7) { | |
| 149 stringParseError("Invalid character in escape sequence", | |
| 150 node, index - (count - 7)); | |
| 151 return false; | |
| 152 } | |
| 153 } else { | |
| 154 // Expect four hex digits, including the one just tread. | |
| 155 for (int i = 0; i < 4; i++) { | |
| 156 if (i > 0) { | |
| 157 index++; | |
| 158 code = iter.next(); | |
| 159 } | |
| 160 if (!isHexDigit(code)) { | |
| 161 stringParseError("Invalid character in escape sequence", | |
| 162 node, index); | |
| 163 return false; | |
| 164 } | |
| 165 value = value * 16 + hexDigitValue(code); | |
| 166 } | |
| 167 } | |
| 168 if (0xd800 <= value && ( value <= 0xdfff || value > 0x10ffff)) { | |
| 169 stringParseError( | |
| 170 "Invalid unicode scalar value U+${value.toRadixString(16)}", | |
| 171 node, index); | |
| 172 return false; | |
| 173 } | |
| 174 continue; | |
| 175 } | |
| 176 } | |
| 177 // This handles borth unescaped characters as well as those | |
| 178 // characters after a backslash that doesn't have a special | |
| 179 // meaning. | |
| 180 if (code >= 0xd800 && (code <= 0xdfff || code > 0x10ffff)) { | |
| 181 stringParseError( | |
| 182 "Invalid unicode scalar value U+${code.toRadixString(16)}", | |
| 183 node, index); | |
| 184 return false; | |
| 185 } | |
| 186 if (!quoting.multiline && (code === $LF || code === $CR)) { | |
| 187 stringParseError("Line terminator in single-line string", | |
| 188 node, index); | |
| 189 return false; | |
| 190 } | |
| 191 } | |
| 192 // String literal successfully validated. | |
| 193 node.quotedString = new QuotedString(string, quoting, length); | |
| 194 return true; | |
| 195 } | |
| 196 } | |
| OLD | NEW |