Changing an enum constant now changes the GWT-RPC serialization policy.
This avoids a subtle bug when a GWT app is deployed after reordering
enum constants but nothing else has changed that affects the serialization
policy. If a browser still has code cached from before the change, the enum
that it transmits or receives would silently be deserialized as the wrong
value. With this change, the RPC call will fail with an error until the
app is reloaded.
This should hopefully fix issue 7836.
Contributed by: t.broyer@gmail.com
Change-Id: I2d7eb618021f0a6d08a2e0ec6fc686e3fd5fc2c1
Review-Link: https://gwt-review.googlesource.com/#/c/1590/
Review by: mdempsky@google.com
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@11452 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializationUtils.java b/user/src/com/google/gwt/user/rebind/rpc/SerializationUtils.java
index 17cc6a0..0d6f826 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializationUtils.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializationUtils.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.ext.TreeLogger;
import com.google.gwt.core.ext.typeinfo.JArrayType;
import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.JEnumConstant;
import com.google.gwt.core.ext.typeinfo.JField;
import com.google.gwt.core.ext.typeinfo.JParameterizedType;
import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
@@ -27,6 +28,7 @@
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
@@ -268,6 +270,26 @@
} else if (type.isArray() != null) {
JArrayType isArray = type.isArray();
generateSerializationSignature(typeOracle, isArray.getComponentType(), crc);
+ } else if (type.isEnum() != null) {
+ List<JEnumConstant> constants = Arrays.asList(type.isEnum().getEnumConstants());
+ // Make sure the list is sorted; the getEnumConstants contract doesn't guarantees it.
+ Collections.sort(constants, new Comparator<JEnumConstant>() {
+ @Override
+ public int compare(JEnumConstant o1, JEnumConstant o2) {
+ int i1 = o1.getOrdinal();
+ int i2 = o2.getOrdinal();
+ if (i1 < i2) {
+ return -1;
+ } else if (i1 > i2) {
+ return 1;
+ } else {
+ return 0;
+ }
+ }
+ });
+ for (JEnumConstant constant : constants) {
+ crc.update(constant.getName().getBytes(Util.DEFAULT_ENCODING));
+ }
} else if (type.isClassOrInterface() != null) {
JClassType isClassOrInterface = type.isClassOrInterface();
JField[] fields = getSerializableFields(typeOracle, isClassOrInterface);
diff --git a/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java b/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
index 2dd668d..7eb246b 100644
--- a/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
+++ b/user/src/com/google/gwt/user/server/rpc/impl/SerializabilityUtil.java
@@ -870,6 +870,14 @@
generateSerializationSignature(customSerializer, crc, policy);
} else if (instanceType.isArray()) {
generateSerializationSignature(instanceType.getComponentType(), crc, policy);
+ } else if (Enum.class.isAssignableFrom(instanceType) && !Enum.class.equals(instanceType)) {
+ if (!instanceType.isEnum()) {
+ instanceType = instanceType.getSuperclass();
+ }
+ Enum<?>[] constants = instanceType.asSubclass(Enum.class).getEnumConstants();
+ for (Enum<?> constant : constants) {
+ crc.update(constant.name().getBytes(RPCServletUtils.CHARSET_UTF8));
+ }
} else if (!instanceType.isPrimitive()) {
Field[] fields = applyFieldSerializationPolicy(instanceType);
Set<String> clientFieldNames = policy.getClientFieldNamesForEnhancedClass(instanceType);
diff --git a/user/test/com/google/gwt/user/RpcSuiteNoBrowser.java b/user/test/com/google/gwt/user/RpcSuiteNoBrowser.java
index d70792d..a048816 100644
--- a/user/test/com/google/gwt/user/RpcSuiteNoBrowser.java
+++ b/user/test/com/google/gwt/user/RpcSuiteNoBrowser.java
@@ -18,6 +18,7 @@
import com.google.gwt.dev.BootStrapPlatform;
import com.google.gwt.user.client.rpc.impl.ClientSerializationStreamReaderTest;
import com.google.gwt.user.rebind.rpc.BlacklistTypeFilterTest;
+import com.google.gwt.user.rebind.rpc.SerializationUtilsTest;
import com.google.gwt.user.rebind.rpc.SerializableTypeOracleBuilderTest;
import com.google.gwt.user.rebind.rpc.TypeHierarchyUtilsTest;
import com.google.gwt.user.server.Base64Test;
@@ -52,6 +53,7 @@
public static Test suite() {
TestSuite suite = new TestSuite("Non-browser tests for com.google.gwt.user.client.rpc");
suite.addTestSuite(BlacklistTypeFilterTest.class);
+ suite.addTestSuite(SerializationUtilsTest.class);
suite.addTestSuite(SerializableTypeOracleBuilderTest.class);
suite.addTestSuite(TypeHierarchyUtilsTest.class);
suite.addTestSuite(RPCTest.class);
diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializationUtilsTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializationUtilsTest.java
new file mode 100644
index 0000000..3c8a5cc
--- /dev/null
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializationUtilsTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.rebind.rpc;
+
+import com.google.gwt.core.ext.TreeLogger;
+import com.google.gwt.core.ext.typeinfo.JClassType;
+import com.google.gwt.core.ext.typeinfo.NotFoundException;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
+import com.google.gwt.dev.javac.TypeOracleTestingUtils;
+import com.google.gwt.dev.javac.testing.impl.StaticJavaResource;
+
+import junit.framework.TestCase;
+
+/**
+ * Tests for {@link SerializationUtils}.
+ */
+public class SerializationUtilsTest extends TestCase {
+
+ public void testGetSerializationSignatureUseEnumConstants() throws Throwable {
+ assertEquals("Identical enums have different signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ"),
+ getEnumSerializationSignature("FOO, BAR, BAZ"));
+
+ assertFalse("Enums w/ renamed constant have same signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ").equals(
+ getEnumSerializationSignature("FOO, BAZ, BAR")));
+ // reordering is equivalent to renaming, but let's test it anyway
+ assertFalse("Enums w/ reordered constants have same signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ").equals(
+ getEnumSerializationSignature("FOO, BAZ, BAR")));
+
+ assertFalse("Enums w/ added constant have same signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ").equals(
+ getEnumSerializationSignature("FOO, BAR, BAZ, QUUX")));
+ assertFalse("Enums w/ removed constant have same signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ").equals(
+ getEnumSerializationSignature("FOO, BAR")));
+
+ assertEquals("Enums w/ changed implementation have different signature",
+ getEnumSerializationSignature("FOO, BAR, BAZ"),
+ getEnumSerializationSignature("FOO, BAR { @Override public String toString() { return \"QUUX\"; } }, BAZ"));
+ }
+
+ protected String getEnumSerializationSignature(String constants) throws NotFoundException {
+ TypeOracle to = TypeOracleTestingUtils.buildStandardTypeOracleWith(TreeLogger.NULL,
+ new StaticJavaResource("TestEnum", "public enum TestEnum { " + constants + " }"));
+ JClassType enumType = to.getType("TestEnum");
+ return SerializationUtils.getSerializationSignature(to, enumType);
+ }
+}