Fix external issue 4425 - '\0' chars in string literals were being escaped
incorrectly. This patch fixes that bug and also uses shorter octal escapes
rather than hexadecimal escapes where possible.
Review by: jat
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@7358 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
index 78dc37c..56b8200 100644
--- a/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
+++ b/dev/core/src/com/google/gwt/dev/js/JsToStringGenerationVisitor.java
@@ -1266,7 +1266,6 @@
* one is used less inside the string.
*/
private void printStringLiteral(String value) {
-
char[] chars = value.toCharArray();
final int n = chars.length;
int quoteCount = 0;
@@ -1298,9 +1297,6 @@
int escape = -1;
switch (c) {
- case 0:
- escape = '0';
- break;
case '\b':
escape = 'b';
break;
@@ -1332,20 +1328,39 @@
result.append('\\');
result.append((char) escape);
} else {
- int hexSize;
- if (c < 256) {
- // 2-digit hex
- result.append("\\x");
- hexSize = 2;
+ /*
+ * Emit characters from 0 to 31 that don't have a single character
+ * escape sequence in octal where possible. This saves one or two
+ * characters compared to the hexadecimal format '\xXX'.
+ *
+ * These short octal sequences may only be used at the end of the string
+ * or where the following character is a non-digit. Otherwise, the
+ * following character would be incorrectly interpreted as belonging to
+ * the sequence.
+ */
+ if (c < ' ' &&
+ (i == n - 1 || chars[i + 1] < '0' || chars[i + 1] > '9')) {
+ result.append('\\');
+ if (c > 0x7) {
+ result.append((char) ('0' + (0x7 & (c >> 3))));
+ }
+ result.append((char) ('0' + (0x7 & c)));
} else {
- // Unicode.
- result.append("\\u");
- hexSize = 4;
- }
- // append hexadecimal form of ch left-padded with 0
- for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
- int digit = 0xf & (c >> shift);
- result.append(HEX_DIGITS[digit]);
+ int hexSize;
+ if (c < 256) {
+ // 2-digit hex
+ result.append("\\x");
+ hexSize = 2;
+ } else {
+ // Unicode.
+ result.append("\\u");
+ hexSize = 4;
+ }
+ // append hexadecimal form of ch left-padded with 0
+ for (int shift = (hexSize - 1) * 4; shift >= 0; shift -= 4) {
+ int digit = 0xf & (c >> shift);
+ result.append(HEX_DIGITS[digit]);
+ }
}
}
}
diff --git a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
index 4eaed72..b1a2002 100644
--- a/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
+++ b/dev/core/test/com/google/gwt/dev/js/JsToStringGenerationVisitorAccuracyTest.java
@@ -16,8 +16,10 @@
package com.google.gwt.dev.js;
import com.google.gwt.dev.jjs.SourceOrigin;
+import com.google.gwt.dev.jjs.ast.JStringLiteral;
import com.google.gwt.dev.js.ast.JsProgram;
import com.google.gwt.dev.js.ast.JsStatement;
+import com.google.gwt.dev.js.ast.JsStringLiteral;
import com.google.gwt.dev.js.ast.JsVisitor;
import com.google.gwt.dev.util.DefaultTextOutput;
import com.google.gwt.dev.util.TextOutput;
@@ -140,6 +142,38 @@
// + prefix stripped when operand is literal number
doTest("var x = +42","var x = 42");
}
+
+ public void testEscapes() throws Exception {
+ // Use short octal escapes at the end of the string or when the next
+ // character is a non-digit
+ doTestEscapes("\u0000", "'\\0'");
+ doTestEscapes("a\u0000", "'a\\0'");
+ doTestEscapes("\u0000a", "'\\0a'");
+ doTestEscapes("a\u0000a", "'a\\0a'");
+ // Ensure hex escapes are used where octal is not possible due to a
+ // following digit
+ doTestEscapes("\u00006", "'\\x006'");
+ doTestEscapes("\u00006\u0000", "'\\x006\\0'");
+
+ // Single-digit octal escapes or special cases (\b,\t,\n\,f\,\r)
+ // for characters from 0 to 15
+ doTestEscapes("\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007",
+ "'\\0\\1\\2\\3\\4\\5\\6\\7'");
+ doTestEscapes("\u0008\u0009\n\u000b\u000c\r\u000e\u000f",
+ "'\\b\\t\\n\\13\\f\\r\\16\\17'");
+
+ // Use two-digit octal escapes for characters from 16 to 31
+ doTestEscapes("\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017",
+ "'\\20\\21\\22\\23\\24\\25\\26\\27'");
+ doTestEscapes("\u0018\u0019\u001a\u001b\u001c\u001d\u001e\u001f",
+ "'\\30\\31\\32\\33\\34\\35\\36\\37'");
+
+ // Use two-digit hex escapes for characters up to 0xff
+ doTestEscapes("\u007f\u00ab", "'\\x7F\\xAB'");
+
+ // Use four-digit unicode escapes for characters from 0x100 up
+ doTestEscapes("\u0100\u117f\u2345", "'\\u0100\\u117F\\u2345'");
+ }
private void doTest(String js) throws Exception {
List<JsStatement> expected = JsParser.parse(SourceOrigin.UNKNOWN,
@@ -158,6 +192,12 @@
new JsProgram().getScope(), new StringReader(expectedJs));
ComparingVisitor.exec(expected, actual);
}
+
+ private void doTestEscapes(String value, String expected) throws Exception {
+ String actual =
+ new JsProgram().getStringLiteral(SourceOrigin.UNKNOWN, value).toString();
+ assertEquals(expected, actual);
+ }
private List<JsStatement> parse(List<JsStatement> expected, boolean compact)
throws Exception {