-
Notifications
You must be signed in to change notification settings - Fork 378
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Remove serialization workarounds for ie 6/7 and rhino (#9578) #9876
base: main
Are you sure you want to change the base?
Changes from 1 commit
ec8b1b9
11e917e
68c238f
f8e2401
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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,30 +321,14 @@ 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); | ||
} else { | ||
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) { | |
* <li>Total Characters Escaped: 2082</li></li> | ||
* </ul> </li> | ||
* </ol> | ||
* | ||
* | ||
* @param ch character to check | ||
* @return <code>true</code> 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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As long as we're updating this, consider replacing StringBuffer with StringBuilder for better single-threaded performance? From https://docs.oracle.com/en/java/javase/17/docs/api/java.base/java/lang/StringBuffer.html
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated to use StringBuilder instead of StringBuffer and changed the default serialization format to JSON. I'm not sure how best to implement the rest of your suggestions though. But they seem logical. |
||
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<Field> serverFields = new ArrayList<Field>(); | ||
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<String> 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)); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be removed. The same loop definition is in the line above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed the extra loop