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..ad239a53f31 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java @@ -1003,7 +1003,9 @@ TypeInfoComputed computeTypeInstantiability(TreeLogger logger, JType type, TypeP Set instantiableTypes = new HashSet(); boolean anySubtypes = checkSubtypes(localLogger, originalType, instantiableTypes, path, problems); - if (!tic.isDone()) { + if (tic.isPendingInstantiable()) { + tic.setInstantiableSubtypes(anySubtypes); + } else if (!tic.isDone()) { tic.setInstantiableSubtypes(anySubtypes); tic.setInstantiable(false); } @@ -1147,9 +1149,14 @@ private boolean checkDeclaredFields(TreeLogger logger, TypeInfoComputed typeInfo checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN, "Object was reached from a manually serializable type", null), path, problems); } else { - allSucceeded &= - computeTypeInstantiability(fieldLogger, fieldType, path, problems) - .hasInstantiableSubtypes(); + boolean hasInstantiableSubtypes = computeTypeInstantiability(fieldLogger, fieldType, path, + problems).hasInstantiableSubtypes(); + allSucceeded &= hasInstantiableSubtypes; + if (!hasInstantiableSubtypes) { + fieldLogger.branch(TreeLogger.WARN, "Field type '" + + fieldType.getParameterizedQualifiedSourceName() + + "' has no instantiable subtypes"); + } } } } 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..84c3ec45534 100644 --- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java +++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java @@ -49,6 +49,8 @@ import com.google.gwt.user.rebind.rpc.testcases.client.NotAllSubtypesAreSerializable; import com.google.gwt.user.rebind.rpc.testcases.client.ParameterizedTypeInList; import com.google.gwt.user.rebind.rpc.testcases.client.RawTypeInList; +import com.google.gwt.user.rebind.rpc.testcases.client.RecursiveTypeGraphInstantiability; +import com.google.gwt.user.rebind.rpc.testcases.client.SimplifiedRecursiveTypeGraphInstantiability; import com.google.gwt.user.rebind.rpc.testcases.client.SubclassUsedInArray; import junit.framework.TestCase; @@ -1521,6 +1523,78 @@ public void testNotAllSubtypesAreSerializable() throws UnableToCompleteException validateSTO(sto, expected); } + public void testInstantiabilityDetectionOnRecursiveTypeGraphStartingAtA() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType a = typeOracle.getType(RecursiveTypeGraphInstantiability.A.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, a); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + + public void testInstantiabilityDetectionOnRecursiveTypeGraphStartingAtB() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType b = typeOracle.getType(RecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, b); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(RecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + + public void testInstantiabilityDetectionOnSimplifiedRecursiveTypeGraphStartingAtA() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType a = typeOracle.getType(SimplifiedRecursiveTypeGraphInstantiability.A.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, a); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + + public void testInstantiabilityDetectionOnSimplifiedRecursiveTypeGraphStartingAtB() + throws UnableToCompleteException, NotFoundException { + TreeLogger logger = createLogger(); + TypeOracle typeOracle = getTestTypeOracle(); + JClassType b = typeOracle.getType(SimplifiedRecursiveTypeGraphInstantiability.B.class.getCanonicalName()); + SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(logger, typeOracle); + stob.addRootType(logger, b); + SerializableTypeOracle sto = stob.build(logger); + assertTrue("Expected type info for B to be present and marked instantiable but found " + Arrays + .asList(getActualTypeInfo(sto)) + ".", Arrays.asList(getActualTypeInfo(sto)).contains( + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), + true))); + TypeInfo[] expected = new TypeInfo[] { + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.B.class.getName()), true), + new TypeInfo(makeSourceName(SimplifiedRecursiveTypeGraphInstantiability.C.class.getName()), true)}; + validateSTO(sto, expected); + } + /** * Tests that Object[] is not instantiable. */ diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java new file mode 100644 index 00000000000..2af520b093a --- /dev/null +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/RecursiveTypeGraphInstantiability.java @@ -0,0 +1,58 @@ +/* + * Copyright 2025 GWT Project Authors + * + * 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.testcases.client; + +import com.google.gwt.user.client.rpc.IsSerializable; + +public interface RecursiveTypeGraphInstantiability extends IsSerializable { + /** + * Not serializable; interface only. + */ + interface A extends IsSerializable { + } + + /** + * Not serializable; interface only, the second interface implemented by {@link B}. + */ + interface D extends IsSerializable { + } + + /** + * Default-serializable, but with back-reference to A for which the question of instantiable subtypes + * depends on the serializability of this class. The reference to {@link C} which + * has a back-reference to {@link B} and B being its only subtypes candidate + * helps reproduce issue 10181. + */ + class B implements A, D { + A a; + B b; + C c; + } + + /** + * Default-serializable with back-reference to B through D, a different interface, triggering another + * descent through checkSubtypes, finding an already "done" TIC for {@link B} with + * {@code instantiable==false}. Being "done", it is used to decide instantiability of the only + * subclass candidate of {@link D}, which is {@link B}, resulting in {@code false} for + * the instantiability of {@link D}, and thus of {@link C}, and thus of {@link B}, causing + * the test case to fail prior to a fix for issue 10181. + */ + class C implements IsSerializable { + D d; + } + + A getA(); +} diff --git a/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java new file mode 100644 index 00000000000..57211085fc5 --- /dev/null +++ b/user/test/com/google/gwt/user/rebind/rpc/testcases/client/SimplifiedRecursiveTypeGraphInstantiability.java @@ -0,0 +1,43 @@ +/* + * Copyright 2025 GWT Project Authors + * + * 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.testcases.client; + +import com.google.gwt.user.client.rpc.IsSerializable; + +public interface SimplifiedRecursiveTypeGraphInstantiability extends IsSerializable { + /** + * Not serializable; interface only. + */ + interface A extends IsSerializable { + } + + /** + * Default serializable, but with back-reference to B for which the question of instantiable + * subtypes depends on the serializability of this class. The reference to {@link C} which has a + * back-reference to {@link A} and B being its only subtypes candidate helps reproduce issue + * 10181. + */ + class B implements A { + B b; + C c; + } + + class C implements IsSerializable { + A A; + } + + A getA(); +}