diff --git a/user/src/com/google/gwt/user/RemoteService.gwt.xml b/user/src/com/google/gwt/user/RemoteService.gwt.xml index 8a13d8686e7..d397ff023f4 100644 --- a/user/src/com/google/gwt/user/RemoteService.gwt.xml +++ b/user/src/com/google/gwt/user/RemoteService.gwt.xml @@ -90,6 +90,22 @@ --> + + + + diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java index 5611b7131a9..9f95945dabf 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java +++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java @@ -92,6 +92,8 @@ public class ProxyCreator { */ public static final String CACHED_PROPERTY_INFO_KEY = "cached-property-info"; public static final String CACHED_TYPE_INFO_KEY = "cached-type-info"; + static final String CACHED_ENHANCED_CLASSES_PROPERTY_INFO_KEY = + "cached-enhanced-classes-property-info"; /** * The directory within which RPC manifests are placed for individual @@ -103,7 +105,10 @@ public class ProxyCreator { * Properties which need to be checked to determine cache reusability. */ private static final Collection configPropsToCheck = Arrays.asList( - TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC, Shared.RPC_ENHANCED_CLASSES); + TypeSerializerCreator.GWT_ELIDE_TYPE_NAMES_FROM_RPC, Shared.RPC_ENHANCED_CLASSES, + Shared.RPC_ENHANCED_CLASSES_ENABLED); + private static final Collection enhancedClassesConfigPropsToCheck = + Arrays.asList(Shared.RPC_ENHANCED_CLASSES, Shared.RPC_ENHANCED_CLASSES_ENABLED); private static final Collection selectionPropsToCheck = Arrays .asList(Shared.RPC_PROP_SUPPRESS_NON_STATIC_FINAL_FIELD_WARNINGS); @@ -396,8 +401,12 @@ public RebindResult create(TreeLogger logger, GeneratorContext context) CachedPropertyInformation cpi = new CachedPropertyInformation(logger, context.getPropertyOracle(), selectionPropsToCheck, configPropsToCheck); + CachedPropertyInformation enhancedClassesCpi = + new CachedPropertyInformation(logger, context.getPropertyOracle(), null, + enhancedClassesConfigPropsToCheck); result.putClientData(CACHED_TYPE_INFO_KEY, cti); result.putClientData(CACHED_PROPERTY_INFO_KEY, cpi); + result.putClientData(CACHED_ENHANCED_CLASSES_PROPERTY_INFO_KEY, enhancedClassesCpi); return result; } else { diff --git a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java index 34f28fea57a..28d5448ee4d 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -98,6 +98,14 @@ * * *

+ * Enhanced class handling can be disabled entirely, including the automatic detection of JDO and + * JPA annotations, by setting the following configuration property: + * + *

+ * 
+ * 
+ * + *

* Enhanced classes are checked for the presence of additional serializable * fields on the server that were not defined in client code as seen by the GWT * compiler. If it is possible for an instance of such a class to be transmitted @@ -694,6 +702,8 @@ private static void logSerializableTypes(TreeLogger logger, Set fiel private final GeneratorContext context; + private final boolean enhancedClassesEnabled; + private Set enhancedClasses = null; private PrintWriter logOutputWriter; @@ -751,6 +761,8 @@ public SerializableTypeOracleBuilder(TreeLogger logger, GeneratorContext context } enhancedClasses = Shared.getEnhancedTypes(context.getPropertyOracle()); + enhancedClassesEnabled = + Shared.shouldEnableEnhancedClasses(logger, context.getPropertyOracle()); } public void addRootType(TreeLogger logger, JType type) { @@ -867,8 +879,10 @@ public SerializableTypeOracle build(TreeLogger logger) throws UnableToCompleteEx fieldSerializableTypes.add(type); } - if (tic.maybeEnhanced() - || (enhancedClasses != null && enhancedClasses.contains(type.getQualifiedSourceName()))) { + if (enhancedClassesEnabled + && (tic.maybeEnhanced() + || (enhancedClasses != null + && enhancedClasses.contains(type.getQualifiedSourceName())))) { logger.log(TreeLogger.WARN, "The class " + type.getQualifiedSourceName() + " has " + "JPA/JDO annotations or is explicitly configured as an enhanced class using the " + "configuration property rpc.enhancedClasses. This makes the server vulnerable " + diff --git a/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java b/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java index a1495e8cb1a..503dd5ad10e 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java +++ b/user/src/com/google/gwt/user/rebind/rpc/ServiceInterfaceProxyGenerator.java @@ -36,7 +36,7 @@ public class ServiceInterfaceProxyGenerator extends IncrementalGenerator { * generator results will be invalidated automatically if they were generated * by a version of this generator with a different version id. */ - private static final long GENERATOR_VERSION_ID = 1L; + private static final long GENERATOR_VERSION_ID = 2L; @Override public RebindResult generateIncrementally(TreeLogger logger, GeneratorContext ctx, diff --git a/user/src/com/google/gwt/user/rebind/rpc/Shared.java b/user/src/com/google/gwt/user/rebind/rpc/Shared.java index 8d4e5ac8f5f..262ddf32ab7 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/Shared.java +++ b/user/src/com/google/gwt/user/rebind/rpc/Shared.java @@ -53,6 +53,12 @@ class Shared { */ public static final String RPC_ENHANCED_CLASSES = "rpc.enhancedClasses"; + /** + * Single-valued configuration property used to disable all enhanced class handling at compile + * time. + */ + public static final String RPC_ENHANCED_CLASSES_ENABLED = "rpc.enhancedClasses.enabled"; + /** * Capitalizes a name. * @@ -80,6 +86,35 @@ static Set getEnhancedTypes(PropertyOracle propertyOracle) { } } + /** + * Returns whether RPC should generate support for server-enhanced classes. + * + * @param propertyOracle the property oracle used to access the relevant configuration property + * @return whether enhanced class handling is enabled + */ + static boolean shouldEnableEnhancedClasses(TreeLogger logger, PropertyOracle propertyOracle) { + try { + ConfigurationProperty prop = + propertyOracle.getConfigurationProperty(RPC_ENHANCED_CLASSES_ENABLED); + if (prop.getValues().size() == 1) { + String value = prop.getValues().get(0); + if ("true".equalsIgnoreCase(value)) { + return true; + } + if ("false".equalsIgnoreCase(value)) { + return false; + } + } + } catch (BadPropertyValueException e) { + // Warn below and retain the backwards-compatible behavior. + } + + logger.log(TreeLogger.WARN, "The configuration property " + RPC_ENHANCED_CLASSES_ENABLED + + " was missing or did not have exactly one 'true' or 'false' value. Is " + + "RemoteService.gwt.xml inherited? Enhanced class support will remain enabled."); + return true; + } + static String getStreamReadMethodNameFor(JType type) { return "read" + getCallSuffix(type); } 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 d4a77086aaf..dcf0de5b533 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java +++ b/user/src/com/google/gwt/user/rebind/rpc/TypeSerializerCreator.java @@ -21,6 +21,7 @@ import com.google.gwt.core.client.JsArrayString; import com.google.gwt.core.ext.BadPropertyValueException; import com.google.gwt.core.ext.CachedGeneratorResult; +import com.google.gwt.core.ext.CachedPropertyInformation; import com.google.gwt.core.ext.ConfigurationProperty; import com.google.gwt.core.ext.GeneratorContext; import com.google.gwt.core.ext.TreeLogger; @@ -114,6 +115,8 @@ private static void computeShardSize(TreeLogger logger) throws UnableToCompleteE private final boolean elideTypeNames; + private final boolean canReuseCachedFieldSerializers; + private final JType[] serializableTypes; private final SerializableTypeOracle serializationOracle; @@ -170,9 +173,12 @@ public TypeSerializerCreator(TreeLogger logger, SerializableTypeOracle serializa } if (context.isGeneratorResultCachingEnabled()) { + canReuseCachedFieldSerializers = + cachedEnhancedClassesConfigurationMatches(logger, context); typesNotUsingCustomFieldSerializers = new HashSet(); customFieldSerializersUsed = new HashSet(); } else { + canReuseCachedFieldSerializers = false; typesNotUsingCustomFieldSerializers = null; customFieldSerializersUsed = null; } @@ -303,6 +309,10 @@ private void createFieldSerializers(TreeLogger logger, GeneratorContext ctx) { private boolean findReusableCachedFieldSerializerIfAvailable(TreeLogger logger, GeneratorContext ctx, JType type, JType customFieldSerializer) { + if (!canReuseCachedFieldSerializers) { + return false; + } + CachedGeneratorResult lastResult = ctx.getCachedGeneratorResult(); if (lastResult == null || !ctx.isGeneratorResultCachingEnabled()) { return false; @@ -352,6 +362,20 @@ private boolean findReusableCachedFieldSerializerIfAvailable(TreeLogger logger, return foundMatch; } + static boolean cachedEnhancedClassesConfigurationMatches(TreeLogger logger, + GeneratorContext context) { + CachedGeneratorResult lastResult = context.getCachedGeneratorResult(); + if (lastResult == null || !context.isGeneratorResultCachingEnabled()) { + return false; + } + + CachedPropertyInformation cpi = + (CachedPropertyInformation) lastResult.getClientData( + ProxyCreator.CACHED_ENHANCED_CLASSES_PROPERTY_INFO_KEY); + return cpi != null + && cpi.checkPropertiesWithPropertyOracle(logger, context.getPropertyOracle()); + } + private String[] getPackageAndClassName(String fullClassName) { String className = fullClassName; String packageName = ""; @@ -849,4 +873,3 @@ private void writeTypeMethodsNative(JType type) { srcWriter.outdent(); } } - diff --git a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java index aef3e8b1302..17dfa598d23 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -15,6 +15,8 @@ */ package com.google.gwt.user.rebind.rpc; +import com.google.gwt.core.ext.CachedGeneratorResult; +import com.google.gwt.core.ext.CachedPropertyInformation; import com.google.gwt.core.ext.PropertyOracle; import com.google.gwt.core.ext.StubGeneratorContext; import com.google.gwt.core.ext.TreeLogger; @@ -41,6 +43,7 @@ import com.google.gwt.dev.javac.testing.impl.MockJavaResource; import com.google.gwt.dev.javac.testing.impl.StaticJavaResource; import com.google.gwt.dev.resource.Resource; +import com.google.gwt.dev.util.UnitTestTreeLogger; import com.google.gwt.dev.util.log.PrintWriterTreeLogger; import com.google.gwt.user.rebind.rpc.testcases.client.AbstractSerializableTypes; import com.google.gwt.user.rebind.rpc.testcases.client.ClassWithTypeParameterThatErasesToObject; @@ -75,6 +78,7 @@ public class SerializableTypeOracleBuilderTest extends TestCase { * {@code SerializableTypeOracleBuilder}. */ static class MockContext extends StubGeneratorContext { + private CachedGeneratorResult cachedGeneratorResult; private TypeOracle typeOracle; private PropertyOracle propertyOracle; @@ -87,11 +91,27 @@ static class MockContext extends StubGeneratorContext { this.propertyOracle = propertyOracle; } + MockContext(TypeOracle typeOracle, PropertyOracle propertyOracle, + CachedGeneratorResult cachedGeneratorResult) { + this(typeOracle, propertyOracle); + this.cachedGeneratorResult = cachedGeneratorResult; + } + + @Override + public CachedGeneratorResult getCachedGeneratorResult() { + return cachedGeneratorResult; + } + @Override public TypeOracle getTypeOracle() { return typeOracle; } + @Override + public boolean isGeneratorResultCachingEnabled() { + return cachedGeneratorResult != null; + } + @Override public PropertyOracle getPropertyOracle() { return propertyOracle; @@ -233,6 +253,47 @@ private static SerializableTypeOracleBuilder createSerializableTypeOracleBuilder return new SerializableTypeOracleBuilder(logger, new MockContext(to, props)); } + private static SerializableTypeOracleBuilder createSerializableTypeOracleBuilder( + TreeLogger logger, TypeOracle to, Map> configurationProperties) + throws UnableToCompleteException { + return new SerializableTypeOracleBuilder( + logger, new MockContext(to, createPropertyOracle(configurationProperties))); + } + + private static PropertyOracle createPropertyOracle( + Map> configurationProperties) { + return new BindingProperties(new BindingProperty[0], new String[0], + new ConfigurationProperties(configurationProperties)).toPropertyOracle(); + } + + private static CachedGeneratorResult createCachedGeneratorResult( + final CachedPropertyInformation cachedPropertyInformation) { + return new CachedGeneratorResult() { + @Override + public Object getClientData(String key) { + if (ProxyCreator.CACHED_ENHANCED_CLASSES_PROPERTY_INFO_KEY.equals(key)) { + return cachedPropertyInformation; + } + return null; + } + + @Override + public String getResultTypeName() { + return null; + } + + @Override + public long getTimeGenerated() { + return 0; + } + + @Override + public boolean isTypeCached(String typeName) { + return false; + } + }; + } + private static TypeInfo[] getActualTypeInfo(SerializableTypeOracle sto) { JType[] types = sto.getSerializableTypes(); TypeInfo[] actual = new TypeInfo[types.length]; @@ -969,6 +1030,107 @@ public void testCovariantArrays() throws UnableToCompleteException, NotFoundExce assertInstantiable(sto, supArray); } + /** + * Tests that enhanced class support can be disabled for an entire compilation. + */ + public void testEnhancedClassesCanBeDisabled() throws UnableToCompleteException, + NotFoundException { + Set resources = new HashSet(); + addStandardClasses(resources); + + StringBuilder code = new StringBuilder(); + code.append("import java.io.Serializable;\n"); + code.append("public class Enhanced implements Serializable {\n"); + code.append("}\n"); + resources.add(new StaticJavaResource("Enhanced", code)); + + TreeLogger logger = createLogger(); + TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, resources); + JClassType enhanced = to.getType("Enhanced"); + + Map> properties = new HashMap>(); + properties.put(Shared.RPC_ENHANCED_CLASSES, Collections.singletonList("Enhanced")); + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("false")); + + SerializableTypeOracleBuilder sob = + createSerializableTypeOracleBuilder(logger, to, properties); + sob.addRootType(logger, enhanced); + sob.build(logger); + assertFalse(enhanced.isEnhanced()); + + // The module defaults the property to true so existing applications retain their behavior. + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("true")); + sob = createSerializableTypeOracleBuilder(logger, to, properties); + sob.addRootType(logger, enhanced); + sob.build(logger); + assertTrue(enhanced.isEnhanced()); + } + + /** + * Tests parsing and backwards-compatible fallback for the enhanced class switch. + */ + public void testEnhancedClassesEnabledPropertyParsing() { + Map> properties = new HashMap>(); + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("TRUE")); + assertTrue(Shared.shouldEnableEnhancedClasses(TreeLogger.NULL, + createPropertyOracle(properties))); + + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("false")); + assertFalse(Shared.shouldEnableEnhancedClasses(TreeLogger.NULL, + createPropertyOracle(properties))); + + String warning = "The configuration property " + Shared.RPC_ENHANCED_CLASSES_ENABLED + + " was missing or did not have exactly one 'true' or 'false' value. Is " + + "RemoteService.gwt.xml inherited? Enhanced class support will remain enabled."; + UnitTestTreeLogger.Builder loggerBuilder = new UnitTestTreeLogger.Builder(); + loggerBuilder.setLowestLogLevel(TreeLogger.WARN); + loggerBuilder.expectWarn(warning, null); + loggerBuilder.expectWarn(warning, null); + loggerBuilder.expectWarn(warning, null); + UnitTestTreeLogger logger = loggerBuilder.createLogger(); + + properties.clear(); + assertTrue(Shared.shouldEnableEnhancedClasses(logger, createPropertyOracle(properties))); + + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("invalid")); + assertTrue(Shared.shouldEnableEnhancedClasses(logger, createPropertyOracle(properties))); + + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Arrays.asList("true", "false")); + assertTrue(Shared.shouldEnableEnhancedClasses(logger, createPropertyOracle(properties))); + logger.assertCorrectLogEntries(); + } + + /** + * Tests that changing the enhanced class property invalidates cached field serializers. + */ + public void testEnhancedClassesPropertyInvalidatesCachedFieldSerializers() { + TreeLogger logger = createLogger(); + Map> properties = new HashMap>(); + properties.put(Shared.RPC_ENHANCED_CLASSES, Collections.singletonList("Enhanced")); + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("true")); + PropertyOracle enabledProperties = createPropertyOracle(properties); + CachedPropertyInformation cachedPropertyInformation = + new CachedPropertyInformation(logger, enabledProperties, null, + Arrays.asList( + Shared.RPC_ENHANCED_CLASSES, Shared.RPC_ENHANCED_CLASSES_ENABLED)); + CachedGeneratorResult cachedGeneratorResult = + createCachedGeneratorResult(cachedPropertyInformation); + + MockContext context = new MockContext(null, enabledProperties, cachedGeneratorResult); + assertTrue(TypeSerializerCreator.cachedEnhancedClassesConfigurationMatches(logger, context)); + + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("false")); + PropertyOracle disabledProperties = createPropertyOracle(properties); + context = new MockContext(null, disabledProperties, cachedGeneratorResult); + assertFalse(TypeSerializerCreator.cachedEnhancedClassesConfigurationMatches(logger, context)); + + properties.put(Shared.RPC_ENHANCED_CLASSES_ENABLED, Collections.singletonList("true")); + properties.put(Shared.RPC_ENHANCED_CLASSES, Collections.singletonList("OtherEnhanced")); + PropertyOracle changedEnhancedClasses = createPropertyOracle(properties); + context = new MockContext(null, changedEnhancedClasses, cachedGeneratorResult); + assertFalse(TypeSerializerCreator.cachedEnhancedClassesConfigurationMatches(logger, context)); + } + /** * If the query type extends a raw type, be sure to pick up the parameters of * the raw subertype.