From ec8b1b99b7fbefb2e3c753cdd55f9f4aa9e77cd2 Mon Sep 17 00:00:00 2001 From: codemasterover9000 Date: Wed, 13 Dec 2023 15:58:47 +0100 Subject: [PATCH 1/4] Remove serialization workarounds for ie6/7 and rhino (#9578) --- .../impl/ServerSerializationStreamWriter.java | 187 ++++-------------- .../ServerSerializationStreamWriterTest.java | 35 +--- 2 files changed, 45 insertions(+), 177 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java index 5e29935e368..1b0d6e66306 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java @@ -42,78 +42,6 @@ public final class ServerSerializationStreamWriter extends AbstractSerializationStreamWriter { - /** - * Builds a string that evaluates into an array containing the given elements. - * This class exists to work around a bug in IE6/7 that limits the size of - * array literals. - */ - public static class LengthConstrainedArray { - public static final int MAXIMUM_ARRAY_LENGTH = 1 << 15; - private static final String POSTLUDE = "])"; - private static final String PRELUDE = "].concat(["; - - private final StringBuffer buffer; - private int count = 0; - private boolean needsComma = false; - private int total = 0; - private boolean javascript = false; - - public LengthConstrainedArray() { - buffer = new StringBuffer(); - } - - public LengthConstrainedArray(int capacityGuess) { - buffer = new StringBuffer(capacityGuess); - } - - public void addToken(CharSequence token) { - total++; - if (count++ == MAXIMUM_ARRAY_LENGTH) { - if (total == MAXIMUM_ARRAY_LENGTH + 1) { - buffer.append(PRELUDE); - javascript = true; - } else { - buffer.append("],["); - } - count = 0; - needsComma = false; - } - - if (needsComma) { - buffer.append(","); - } else { - needsComma = true; - } - - buffer.append(token); - } - - public void addEscapedToken(String token) { - addToken(escapeString(token, true, this)); - } - - public void addToken(int i) { - addToken(String.valueOf(i)); - } - - public boolean isJavaScript() { - return javascript; - } - - public void setJavaScript(boolean javascript) { - this.javascript = javascript; - } - - @Override - public String toString() { - if (total > MAXIMUM_ARRAY_LENGTH) { - return "[" + buffer.toString() + POSTLUDE; - } else { - return "[" + buffer.toString() + "]"; - } - } - } - /** * Enumeration used to provided typed instance writers. */ @@ -337,14 +265,6 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance) private static final char NON_BREAKING_HYPHEN = '\u2011'; - /** - * Maximum length of a string node in RPC responses, not including surrounding - * quote characters (2 ^ 16 - 1) = 65535. - * This exists to work around a Rhino parser bug in the hosted mode client - * that limits string node lengths to 64KB. - */ - private static final int MAX_STRING_NODE_LENGTH = 0xFFFF; - static { /* * NOTE: The JS VM in IE6 & IE7 do not interpret \v correctly. They convert @@ -382,36 +302,15 @@ abstract void write(ServerSerializationStreamWriter stream, Object instance) CLASS_TO_VALUE_WRITER.put(String.class, ValueWriter.STRING); } - /** - * This method takes a string and outputs a JavaScript string literal. The - * data is surrounded with quotes, and any contained characters that need to - * be escaped are mapped onto their escape sequence. - * - * Assumptions: We are targeting a version of JavaScript that that is later - * than 1.3 that supports unicode strings. - */ - public static String escapeString(String toEscape) { - return escapeString(toEscape, false, null); - } - /** * This method takes a string and outputs a JavaScript string literal. The * data is surrounded with quotes, and any contained characters that need to * be escaped are mapped onto their escape sequence. * - * This splits strings into 64KB chunks to workaround an issue with the hosted mode client where - * the Rhino parser can't handle string nodes larger than 64KB, e.g. {@code "longstring"} is - * converted to {@code "long" + "string"}. - * * Assumptions: We are targeting a version of JavaScript that that is later * than 1.3 that supports unicode strings. */ - public static String escapeStringSplitNodes(String toEscape) { - return escapeString(toEscape, true, null); - } - - private static String escapeString(String toEscape, boolean splitNodes, - LengthConstrainedArray array) { + public static String escapeString(String toEscape) { // Since escaped characters will increase the output size, allocate extra room to start. int length = toEscape.length(); int capacityIncrement = Math.max(length, 16); @@ -422,13 +321,7 @@ private static String escapeString(String toEscape, boolean splitNodes, int i = 0; while (i < length) { - // Add one segment at a time, up to maxNodeLength characters. Note this always leave room - // for at least 6 characters at the end (maximum unicode escaped character size). - int maxSegmentVectorSize = splitNodes - ? (charVector.getSize() + MAX_STRING_NODE_LENGTH - 5) - : Integer.MAX_VALUE; - - while (i < length && charVector.getSize() < maxSegmentVectorSize) { + while (i < length) { char c = toEscape.charAt(i++); if (needsUnicodeEscape(c)) { unicodeEscape(c, charVector); @@ -436,16 +329,6 @@ private static String escapeString(String toEscape, boolean splitNodes, charVector.add(c); } } - - // If there's another segment left, insert a '+' operator. - if (splitNodes && i < length) { - charVector.add(JS_QUOTE_CHAR); - charVector.add('+'); - charVector.add(JS_QUOTE_CHAR); - if (array != null) { - array.setJavaScript(true); - } - } } charVector.add(JS_QUOTE_CHAR); @@ -502,7 +385,7 @@ private static Class getClassForSerialization(Object instance) { *
  • Total Characters Escaped: 2082
  • * * - * + * * @param ch character to check * @return true if the character requires the \\uXXXX unicode * character escape @@ -557,7 +440,7 @@ private static boolean needsUnicodeEscape(char ch) { * Writes a safe escape sequence for a character. Some characters have a short * form, such as \n for U+000D, while others are represented as \\xNN or * \\uNNNN. - * + * * @param ch character to unicode escape * @param charVector char vector to receive the unicode escaped representation */ @@ -610,7 +493,7 @@ public void serializeValue(Object value, Class type) /** * Build an array of JavaScript string literals that can be decoded by the * client via the eval function. - * + * * NOTE: We build the array in reverse so the client can simply use the pop * function to remove the next item from the list. */ @@ -620,14 +503,14 @@ public String toString() { // We take a guess at how big to make to buffer to avoid numerous resizes. // int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.size(); - LengthConstrainedArray stream = new LengthConstrainedArray(capacityGuess); - writePayload(stream); - writeStringTable(stream); - writeHeader(stream); + StringBuffer buffer = new StringBuffer(capacityGuess); + writePayload(buffer); + writeStringTable(buffer); + writeHeader(buffer); - return stream.toString(); + return "[" + buffer.toString() + "]"; } - + @Override public void writeLong(long value) { if (getVersion() == SERIALIZATION_STREAM_MIN_VERSION) { @@ -702,7 +585,7 @@ protected void serialize(Object instance, String typeSignature) * Serialize an instance that is an array. Will default to serializing the * instance as an Object vector if the instance is not a vector of primitives, * Strings or Object. - * + * * @param instanceClass * @param instance * @throws SerializationException @@ -735,14 +618,14 @@ private void serializeClass(Object instance, Class instanceClass) List serverFields = new ArrayList(); for (Field declField : serializableFields) { assert (declField != null); - + // Identify server-only fields if (!clientFieldNames.contains(declField.getName())) { serverFields.add(declField); continue; } } - + // Serialize the server-only fields into a byte array and encode as a String try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); @@ -765,7 +648,7 @@ private void serializeClass(Object instance, Class instanceClass) throw new SerializationException(e); } } - + // Write the client-visible field data for (Field declField : serializableFields) { if ((clientFieldNames != null) && !clientFieldNames.contains(declField.getName())) { @@ -861,29 +744,39 @@ private void serializeWithCustomSerializer(Class customSerializer, * Notice that the field are written in reverse order that the client can just * pop items out of the stream. */ - private void writeHeader(LengthConstrainedArray stream) { - stream.addToken(getFlags()); - if (stream.isJavaScript() && getVersion() >= SERIALIZATION_STREAM_JSON_VERSION) { - // Ensure we are not using the JSON supported version if stream is Javascript instead of JSON - stream.addToken(SERIALIZATION_STREAM_JSON_VERSION - 1); - } else { - stream.addToken(getVersion()); - } + private void writeHeader(StringBuffer buffer) { + addToken(buffer, getFlags()); + addToken(buffer, getVersion()); } - private void writePayload(LengthConstrainedArray stream) { + private void writePayload(StringBuffer buffer) { ListIterator tokenIterator = tokenList.listIterator(tokenList.size()); while (tokenIterator.hasPrevious()) { - stream.addToken(tokenIterator.previous()); + addToken(buffer, tokenIterator.previous()); } } - private void writeStringTable(LengthConstrainedArray stream) { - LengthConstrainedArray tableStream = new LengthConstrainedArray(); + private void writeStringTable(StringBuffer buffer) { + StringBuffer tableBuffer = new StringBuffer(); for (String s : getStringTable()) { - tableStream.addEscapedToken(s); + addEscapedToken(tableBuffer, s); } - stream.addToken(tableStream.toString()); - stream.setJavaScript(stream.isJavaScript() || tableStream.isJavaScript()); + addToken(buffer, "[" + tableBuffer + "]"); + } + + public void addToken(StringBuffer buffer, CharSequence token) { + if (buffer.length() > 0) { + buffer.append(","); + } + + buffer.append(token); + } + + public void addEscapedToken(StringBuffer buffer, String token) { + addToken(buffer, escapeString(token)); + } + + public void addToken(StringBuffer buffer, int i) { + addToken(buffer, String.valueOf(i)); } } diff --git a/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java b/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java index 1655966341d..cfbbc5f9818 100644 --- a/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java +++ b/user/test/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriterTest.java @@ -39,12 +39,12 @@ public void testEscapeString() { } public void testEscapeStringSplitNodes() { - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes("test"); + String escaped = ServerSerializationStreamWriter.escapeString("test"); assertEquals("\"test\"", escaped); } public void testEscapeStringSplitNodes_unicodeEscape() { - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( "测试" // Unicode characters + "\"" // JS quote char + "\\" // JS escape char @@ -75,11 +75,11 @@ public void testEscapeStringSplitNodes_over64KB() { secondNodeBuilder.append('2'); } - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( firstNodeBuilder.toString() + secondNodeBuilder.toString()); assertEquals( - "\"" + firstNodeBuilder.toString() + "\"+\"" + secondNodeBuilder.toString() + "\"", + "\"" + firstNodeBuilder.toString() + secondNodeBuilder.toString() + "\"", escaped); } @@ -104,12 +104,11 @@ public void testEscapeStringSplitNodes_over64KBEscaped() { } String secondNode = secondNodeBuilder.toString(); - String escaped = ServerSerializationStreamWriter.escapeStringSplitNodes( + String escaped = ServerSerializationStreamWriter.escapeString( firstNodeBuilder.toString() + secondNode); assertEquals( "\"" + firstNodeNoUnicode + "\\u2011" // first node (including escaped unicode character) - + "\"+\"" + secondNode + "\"", // second node escaped); } @@ -122,28 +121,4 @@ public void testWritingRpcVersion8Message() { assertEquals("[\"NaN\",\"Infinity\",\"-Infinity\",[],0,8]", writer.toString()); } - public void testVersion8Fallbacks() { - StringBuilder longString = new StringBuilder(66000); - for (int i = 0; i < 660000; i++) { - longString.append("a"); - } - - // Fallbacks to 7 if string gets concatenated - ServerSerializationStreamWriter writer = new ServerSerializationStreamWriter(null, 8); - writer.writeString(longString.toString()); - String encoded = writer.toString(); - assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]"))); - - // Fallbacks to 7 if array size reached maximum - int maxArrayLength = - ServerSerializationStreamWriter.LengthConstrainedArray.MAXIMUM_ARRAY_LENGTH + 100; - writer = new ServerSerializationStreamWriter(null, 8); - for (int i = 0; i < maxArrayLength; i++) { - writer.writeInt(i); - } - - encoded = writer.toString(); - assertEquals("7", encoded.substring(encoded.lastIndexOf(",") + 1, encoded.lastIndexOf("]"))); - } - } From 11e917e167eb9f14cb29a84ca5f0e3f4d9917c1f Mon Sep 17 00:00:00 2001 From: codemasterover9000 Date: Thu, 14 Dec 2023 09:36:47 +0100 Subject: [PATCH 2/4] Use StringBuilder instead of StringBuffer --- .../impl/ServerSerializationStreamWriter.java | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java index 1b0d6e66306..b331fe6d6c9 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java @@ -503,7 +503,7 @@ public String toString() { // We take a guess at how big to make to buffer to avoid numerous resizes. // int capacityGuess = 2 * tokenListCharCount + 2 * tokenList.size(); - StringBuffer buffer = new StringBuffer(capacityGuess); + StringBuilder buffer = new StringBuilder(capacityGuess); writePayload(buffer); writeStringTable(buffer); writeHeader(buffer); @@ -744,27 +744,27 @@ private void serializeWithCustomSerializer(Class customSerializer, * Notice that the field are written in reverse order that the client can just * pop items out of the stream. */ - private void writeHeader(StringBuffer buffer) { + private void writeHeader(StringBuilder buffer) { addToken(buffer, getFlags()); addToken(buffer, getVersion()); } - private void writePayload(StringBuffer buffer) { + private void writePayload(StringBuilder buffer) { ListIterator tokenIterator = tokenList.listIterator(tokenList.size()); while (tokenIterator.hasPrevious()) { addToken(buffer, tokenIterator.previous()); } } - private void writeStringTable(StringBuffer buffer) { - StringBuffer tableBuffer = new StringBuffer(); + private void writeStringTable(StringBuilder buffer) { + StringBuilder tableBuffer = new StringBuilder(); for (String s : getStringTable()) { addEscapedToken(tableBuffer, s); } addToken(buffer, "[" + tableBuffer + "]"); } - public void addToken(StringBuffer buffer, CharSequence token) { + public void addToken(StringBuilder buffer, CharSequence token) { if (buffer.length() > 0) { buffer.append(","); } @@ -772,11 +772,11 @@ public void addToken(StringBuffer buffer, CharSequence token) { buffer.append(token); } - public void addEscapedToken(StringBuffer buffer, String token) { + public void addEscapedToken(StringBuilder buffer, String token) { addToken(buffer, escapeString(token)); } - public void addToken(StringBuffer buffer, int i) { + public void addToken(StringBuilder buffer, int i) { addToken(buffer, String.valueOf(i)); } } From 68c238f05946b0161f638b545826b4b1f516157c Mon Sep 17 00:00:00 2001 From: codemasterover9000 Date: Thu, 14 Dec 2023 14:04:47 +0100 Subject: [PATCH 3/4] Use JSON as default serialization format --- .../gwt/user/client/rpc/impl/AbstractSerializationStream.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java index ba275dc5ed0..3aa55615a7f 100644 --- a/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java +++ b/user/src/com/google/gwt/user/client/rpc/impl/AbstractSerializationStream.java @@ -47,7 +47,7 @@ public abstract class AbstractSerializationStream { /** * The current RPC protocol version. */ - public static final int SERIALIZATION_STREAM_VERSION = 7; + public static final int SERIALIZATION_STREAM_VERSION = 8; /** * The oldest supported RPC protocol version. From f8e240196c8336e5876dbed5e61ca39e6420f8de Mon Sep 17 00:00:00 2001 From: codemasterover9000 Date: Fri, 15 Dec 2023 09:37:38 +0100 Subject: [PATCH 4/4] Remove redundant while loop --- .../rpc/impl/ServerSerializationStreamWriter.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java index b331fe6d6c9..c8ec5568dc5 100644 --- a/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java +++ b/user/src/com/google/gwt/user/server/rpc/impl/ServerSerializationStreamWriter.java @@ -321,13 +321,11 @@ public static String escapeString(String toEscape) { int i = 0; while (i < length) { - while (i < length) { - char c = toEscape.charAt(i++); - if (needsUnicodeEscape(c)) { - unicodeEscape(c, charVector); - } else { - charVector.add(c); - } + char c = toEscape.charAt(i++); + if (needsUnicodeEscape(c)) { + unicodeEscape(c, charVector); + } else { + charVector.add(c); } }