Fixes a Linux hosted mode crash when an RPC interface has a large number of
instantiable serializable types.
Issue: 2935
Patch by: jat
Review by: fabbott, spoon
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@3695 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
index 206ed81..88f668d 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java
@@ -19,6 +19,7 @@
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.ext.GeneratorContext;
import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.UnableToCompleteException;
import com.google.gwt.core.ext.typeinfo.JClassType;
import com.google.gwt.core.ext.typeinfo.JMethod;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
@@ -32,6 +33,8 @@
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
/**
* This class generates a class with name 'typeSerializerClassName' that is able
@@ -52,6 +55,44 @@
+ "SerializationStreamWriter streamWriter, Object instance, String typeSignature)"
+ " throws SerializationException";
+ /**
+ * Default number of types to split createMethodMap entries into. Zero means
+ * no sharding occurs. Stored as a string since it is used as a default
+ * property value.
+ *
+ * Note that the inliner will likely reassemble the shards if it is used
+ * in web mode, but it isn't needed there anyway.
+ *
+ * TODO: remove this (and related code) when it is no longer needed.
+ */
+ private static final String DEFAULT_CREATEMETHODMAP_SHARD_SIZE = "0";
+
+ /**
+ * Java system property name to override the above.
+ */
+ private static final String GWT_CREATEMETHODMAP_SHARD_SIZE = "gwt.typecreator.shard.size";
+
+ private static int shardSize = -1;
+
+ private static void computeShardSize(TreeLogger logger)
+ throws UnableToCompleteException {
+ String shardSizeProperty = System.getProperty(
+ GWT_CREATEMETHODMAP_SHARD_SIZE, DEFAULT_CREATEMETHODMAP_SHARD_SIZE);
+ try {
+ shardSize = Integer.valueOf(shardSizeProperty);
+ if (shardSize < 0) {
+ logger.log(TreeLogger.ERROR, GWT_CREATEMETHODMAP_SHARD_SIZE
+ + " must be non-negative: " + shardSizeProperty);
+ throw new UnableToCompleteException();
+ }
+ } catch (NumberFormatException e) {
+ logger.log(TreeLogger.ERROR, "Property "
+ + GWT_CREATEMETHODMAP_SHARD_SIZE + " not a number: "
+ + shardSizeProperty, e);
+ throw new UnableToCompleteException();
+ }
+ }
+
private final GeneratorContext context;
private final JType[] serializableTypes;
@@ -66,7 +107,7 @@
public TypeSerializerCreator(TreeLogger logger,
SerializableTypeOracle serializationOracle, GeneratorContext context,
- String typeSerializerClassName) {
+ String typeSerializerClassName) throws UnableToCompleteException {
this.context = context;
this.typeSerializerClassName = typeSerializerClassName;
this.serializationOracle = serializationOracle;
@@ -75,9 +116,14 @@
serializableTypes = serializationOracle.getSerializableTypes();
srcWriter = getSourceWriter(logger, context);
+ if (shardSize < 0) {
+ computeShardSize(logger);
+ }
+ logger.log(TreeLogger.TRACE, "Using a shard size of " + shardSize
+ + " for TypeSerializerCreator createMethodMap");
}
- public String realize(TreeLogger logger) {
+ public String realize(TreeLogger logger) throws UnableToCompleteException {
logger = logger.branch(TreeLogger.DEBUG,
"Generating TypeSerializer for service interface '"
+ getTypeSerializerClassName() + "'", null);
@@ -92,7 +138,7 @@
writeCreateMethods();
- writeCreateMethodMapMethod();
+ writeCreateMethodMapMethod(logger);
writeCreateSignatureMapMethod();
@@ -173,13 +219,13 @@
packageName = className.substring(0, index);
className = className.substring(index + 1, className.length());
}
- return new String[]{packageName, className};
+ return new String[] {packageName, className};
}
-
+
private JType[] getSerializableTypes() {
return serializableTypes;
}
-
+
private SourceWriter getSourceWriter(TreeLogger logger, GeneratorContext ctx) {
String name[] = getPackageAndClassName(getTypeSerializerClassName());
String packageName = name[0];
@@ -207,6 +253,16 @@
}
/**
+ * @param type
+ * @return
+ */
+ private String getTypeString(JType type) {
+ String typeString = serializationOracle.getSerializedTypeName(type) + "/"
+ + serializationOracle.getSerializationSignature(type);
+ return typeString;
+ }
+
+ /**
* Return <code>true</code> if this type is concrete and has a custom field
* serializer that does not declare an instantiate method.
*
@@ -239,98 +295,30 @@
return true;
}
- private void writeCreateMethodMapMethod() {
- srcWriter.println("@SuppressWarnings(\"restriction\")");
- srcWriter.println("private static native JavaScriptObject createMethodMap() /*-" + '{');
- {
- srcWriter.indent();
- srcWriter.println("return {");
- JType[] types = getSerializableTypes();
- boolean needComma = false;
- for (int index = 0; index < types.length; ++index) {
- JType type = types[index];
- if (!serializationOracle.maybeInstantiated(type)) {
- continue;
- }
-
- if (needComma) {
- srcWriter.println(",");
- } else {
- needComma = true;
- }
-
- String typeString = serializationOracle.getSerializedTypeName(type)
- + "/" + serializationOracle.getSerializationSignature(type);
-
- srcWriter.print("\"" + typeString + "\":");
-
- // Make a JSON array
- srcWriter.println("[");
- {
- srcWriter.indent();
- String serializerName = serializationOracle.getFieldSerializerName(type);
-
- // First the initialization method
- {
- srcWriter.print("@");
- if (needsCreateMethod(type)) {
- srcWriter.print(getTypeSerializerClassName());
- srcWriter.print("::");
- srcWriter.print(getCreateMethodName(type));
- } else {
- srcWriter.print(serializerName);
- srcWriter.print("::instantiate");
- }
- srcWriter.print("(L"
- + SerializationStreamReader.class.getName().replace('.', '/')
- + ";)");
- srcWriter.println(",");
- }
-
- JClassType customSerializer = serializationOracle.hasCustomFieldSerializer(type);
-
- // Now the deserialization method
- {
- // Assume param type is the concrete type of the serialized type.
- JType paramType = type;
- if (customSerializer != null) {
- // But a custom serializer may specify a looser type.
- JMethod deserializationMethod = CustomFieldSerializerValidator.getDeserializationMethod(
- customSerializer, (JClassType) type);
- paramType = deserializationMethod.getParameters()[1].getType();
- }
- srcWriter.print("@" + serializerName);
- srcWriter.print("::deserialize(L"
- + SerializationStreamReader.class.getName().replace('.', '/')
- + ";" + paramType.getJNISignature() + ")");
- srcWriter.println(",");
- }
-
- // Now the serialization method
- {
- // Assume param type is the concrete type of the serialized type.
- JType paramType = type;
- if (customSerializer != null) {
- // But a custom serializer may specify a looser type.
- JMethod serializationMethod = CustomFieldSerializerValidator.getSerializationMethod(
- customSerializer, (JClassType) type);
- paramType = serializationMethod.getParameters()[1].getType();
- }
- srcWriter.print("@" + serializerName);
- srcWriter.print("::serialize(L"
- + SerializationStreamWriter.class.getName().replace('.', '/')
- + ";" + paramType.getJNISignature() + ")");
- srcWriter.println();
- }
- srcWriter.outdent();
- }
- srcWriter.print("]");
+ /**
+ * Generate the createMethodMap function, possibly splitting it into smaller
+ * pieces if necessary to avoid an old Mozilla crash when dealing with
+ * excessively large JS functions.
+ *
+ * @param logger TreeLogger instance
+ * @throws UnableToCompleteException if an error is logged
+ */
+ private void writeCreateMethodMapMethod(TreeLogger logger)
+ throws UnableToCompleteException {
+ ArrayList<JType> filteredTypes = new ArrayList<JType>();
+ JType[] types = getSerializableTypes();
+ int n = types.length;
+ for (int index = 0; index < n; ++index) {
+ JType type = types[index];
+ if (serializationOracle.maybeInstantiated(type)) {
+ filteredTypes.add(type);
}
- srcWriter.println();
- srcWriter.println("};");
- srcWriter.outdent();
}
- srcWriter.println("}-*/;");
+ if (shardSize > 0 && filteredTypes.size() > shardSize) {
+ writeShardedCreateMethodMapMethod(filteredTypes, shardSize);
+ } else {
+ writeSingleCreateMethodMapMethod(filteredTypes);
+ }
srcWriter.println();
}
@@ -468,9 +456,136 @@
srcWriter.println();
}
+ /**
+ * Create a createMethodMap method which is sharded into smaller methods. This
+ * avoids a crash in old Mozilla dealing with very large JS functions being
+ * evaluated.
+ *
+ * @param types list of types to include
+ * @param shardSize batch size for sharding
+ */
+ private void writeShardedCreateMethodMapMethod(List<JType> types,
+ int shardSize) {
+ srcWriter.println("private static JavaScriptObject createMethodMap() {");
+ int n = types.size();
+ srcWriter.indent();
+ srcWriter.println("JavaScriptObject map = JavaScriptObject.createObject();");
+ for (int i = 0; i < n; i += shardSize) {
+ srcWriter.println("createMethodMap_" + i + "(map);");
+ }
+ srcWriter.println("return map;");
+ srcWriter.outdent();
+ srcWriter.println("}");
+ srcWriter.println();
+ for (int outerIndex = 0; outerIndex < n; outerIndex += shardSize) {
+ srcWriter.println("@SuppressWarnings(\"restriction\")");
+ srcWriter.println("private static native void createMethodMap_"
+ + outerIndex + "(JavaScriptObject map) /*-" + '{');
+ srcWriter.indent();
+ int last = outerIndex + shardSize;
+ if (last > n) {
+ last = n;
+ }
+ for (int i = outerIndex; i < last; ++i) {
+ JType type = types.get(outerIndex);
+ String typeString = getTypeString(type);
+ srcWriter.print("map[\"" + typeString + "\"]=[");
+ writeTypeMethods(type);
+ srcWriter.println("];");
+ }
+ srcWriter.outdent();
+ srcWriter.println("}-*/;");
+ srcWriter.println();
+ }
+ }
+
+ private void writeSingleCreateMethodMapMethod(List<JType> types) {
+ srcWriter.println("@SuppressWarnings(\"restriction\")");
+ srcWriter.println("private static native JavaScriptObject createMethodMap() /*-" + '{');
+ srcWriter.indent();
+ srcWriter.println("return {");
+ int n = types.size();
+ for (int i = 0; i < n; ++i) {
+ if (i > 0) {
+ srcWriter.println(",");
+ }
+ JType type = types.get(i);
+ String typeString = getTypeString(type);
+ srcWriter.print("\"" + typeString + "\":[");
+ writeTypeMethods(type);
+ srcWriter.print("]");
+ }
+ srcWriter.println("};");
+ srcWriter.outdent();
+ srcWriter.println("}-*/;");
+ }
+
private void writeStaticFields() {
srcWriter.println("private static final JavaScriptObject methodMap = createMethodMap();");
srcWriter.println("private static final JavaScriptObject signatureMap = createSignatureMap();");
srcWriter.println();
}
-}
\ No newline at end of file
+
+ /**
+ * Write an entry in the createMethodMap method for one type.
+ *
+ * @param type type to generate entry for
+ */
+ private void writeTypeMethods(JType type) {
+ srcWriter.indent();
+ String serializerName = serializationOracle.getFieldSerializerName(type);
+
+ // First the initialization method
+ {
+ srcWriter.print("@");
+ if (needsCreateMethod(type)) {
+ srcWriter.print(getTypeSerializerClassName());
+ srcWriter.print("::");
+ srcWriter.print(getCreateMethodName(type));
+ } else {
+ srcWriter.print(serializerName);
+ srcWriter.print("::instantiate");
+ }
+ srcWriter.print("(L"
+ + SerializationStreamReader.class.getName().replace('.', '/') + ";)");
+ srcWriter.println(",");
+ }
+
+ JClassType customSerializer = serializationOracle.hasCustomFieldSerializer(type);
+
+ // Now the deserialization method
+ {
+ // Assume param type is the concrete type of the serialized type.
+ JType paramType = type;
+ if (customSerializer != null) {
+ // But a custom serializer may specify a looser type.
+ JMethod deserializationMethod = CustomFieldSerializerValidator.getDeserializationMethod(
+ customSerializer, (JClassType) type);
+ paramType = deserializationMethod.getParameters()[1].getType();
+ }
+ srcWriter.print("@" + serializerName);
+ srcWriter.print("::deserialize(L"
+ + SerializationStreamReader.class.getName().replace('.', '/') + ";"
+ + paramType.getJNISignature() + ")");
+ srcWriter.println(",");
+ }
+
+ // Now the serialization method
+ {
+ // Assume param type is the concrete type of the serialized type.
+ JType paramType = type;
+ if (customSerializer != null) {
+ // But a custom serializer may specify a looser type.
+ JMethod serializationMethod = CustomFieldSerializerValidator.getSerializationMethod(
+ customSerializer, (JClassType) type);
+ paramType = serializationMethod.getParameters()[1].getType();
+ }
+ srcWriter.print("@" + serializerName);
+ srcWriter.print("::serialize(L"
+ + SerializationStreamWriter.class.getName().replace('.', '/') + ";"
+ + paramType.getJNISignature() + ")");
+ srcWriter.println();
+ }
+ srcWriter.outdent();
+ }
+}