Exposed a method to iterate over the immediate subclasses of a class. When looking at subtypes don't reexaming fields that have already been look at during the subtype scan. Doing so can lessen the precision when raw supertypes are involved. Patch by: mmendez, spoon (pair prog) git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@2892 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 d99d873..c0dccfe 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java +++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -792,9 +792,6 @@ isSpeculative = false; } - boolean isInstantiable = checkTypeInstantiableNoSubtypes(localLogger, - baseType, isSpeculative, path); - JClassType[] typeArgs = NO_JCLASSES; JParameterizedType isParameterized = originalType.isParameterized(); JGenericType baseAsGenericType = baseType.isGenericType(); @@ -837,90 +834,16 @@ } } - isInstantiable &= parametersOkay; + boolean isInstantiable = parametersOkay + && qualifiesForSerialization(localLogger, baseType, isSpeculative, path) + && checkFields(localLogger, baseType, isSpeculative, path); boolean anySubtypes = false; - if (parametersOkay) { - // Speculatively check all subtypes. - JClassType[] subtypes = baseType.getSubtypes(); - if (subtypes.length > 0) { - TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG, - "Analyzing subclasses:", null); - - for (JClassType subtype : subtypes) { - TreeLogger subtypeLogger = subtypesLogger.branch(TreeLogger.DEBUG, - subtype.getParameterizedQualifiedSourceName(), null); - Path subtypePath = createSubtypePath(path, subtype, originalType); - boolean subInstantiable = checkTypeInstantiableNoSubtypes( - subtypeLogger, subtype, true, subtypePath); - if (!subInstantiable) { - // If the subtype is not instantiable do not check the arguments. - continue; - } - - JGenericType genericSub = subtype.isGenericType(); - if (genericSub != null) { - TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG, - "Checking parameters of '" - + genericSub.getParameterizedQualifiedSourceName() + "'"); - Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy( - baseType, genericSub); - for (int i = 0; i < genericSub.getTypeParameters().length; i++) { - JTypeParameter param = genericSub.getTypeParameters()[i]; - TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG, - "Checking param '" - + param.getParameterizedQualifiedSourceName() + "'"); - Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param); - if (constBy == null) { - subInstantiable &= checkTypeArgument(paramLogger, genericSub, - i, param.getFirstBound(), true, path); - } else { - boolean paramOK = false; - for (JTypeParameter constrained : constBy) { - paramOK |= checkTypeArgument(paramLogger, genericSub, i, - typeArgs[constrained.getOrdinal()], true, path); - } - subInstantiable &= paramOK; - } - } - } else { - /* - * The subtype is not generic so it must be a concrete - * parameterization of the query type. If the query type is also a - * concrete parameterization then we should be able to exclude the - * subtype based on an assignability check. If the query type does - * contain type parameters then we assume that the subtype should be - * included. In order to be certain, we would need to perform a full - * unification between the query type and subtype. - */ - if (isParameterized != null) { - HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>(); - recordTypeParametersIn(isParameterized, typeParamsInQueryType); - - if (typeParamsInQueryType.isEmpty()) { - if (!isParameterized.isAssignableFrom(subtype)) { - subtypeLogger.log(TreeLogger.DEBUG, "Excluding type '" - + subtype.getParameterizedQualifiedSourceName() - + "' because it is not assignable to '" - + isParameterized.getParameterizedQualifiedSourceName() - + "'"); - subInstantiable = false; - } - } - } - } - - if (subInstantiable) { - if (instSubtypes != null) { - instSubtypes.add(subtype); - } - - // TODO: This is suspect. - getTypeInfoComputed(subtype, path).setInstantiable(true); - anySubtypes = true; - } - } - } + if (parametersOkay && baseType.getSubtypes().length > 0) { + TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG, + "Analyzing subclasses:", null); + anySubtypes = checkSubtypes(subtypesLogger, originalType, baseType, + instSubtypes, typeArgs, path, baseType, false, isInstantiable); } anySubtypes |= isInstantiable; @@ -939,16 +862,6 @@ return tic.hasInstantiableSubtypes(); } - final boolean checkTypeInstantiableNoSubtypes(TreeLogger logger, - JClassType type, boolean isSpeculative, Path path) { - if (qualifiesForSerialization(logger, type, isSpeculative, path)) { - return type.isEnum() != null - || checkFields(logger, type, isSpeculative, path); - } - - return false; - } - int getTypeParameterExposure(JGenericType type, int index) { return getFlowInfo(type, index).getExposure(); } @@ -1127,31 +1040,22 @@ return succeeded; } - private boolean checkFields(TreeLogger logger, JClassType classOrInterface, - boolean isSpeculative, Path parent) { - TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent); + /** + * Returns <code>true</code> if the declared fields of this type are all + * instantiable. As a side-effect it fills in {@link TypeInfoComputed} for all + * necessary types. + */ + private boolean checkDeclaredFields(TreeLogger logger, + TypeInfoComputed typeInfo, boolean isSpeculative, Path parent) { - // Check all super type fields first (recursively). - JClassType superType = classOrInterface.getSuperclass(); - if (superType != null - && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) { - boolean superTypeOk = checkFields(logger, superType, isSpeculative, - parent); - /* - * If my super type did not check out, then I am not instantiable and we - * should error out... UNLESS I am *directly* serializable myself, in - * which case it's ok for me to be the root of a new instantiable - * hierarchy. - */ - if (!superTypeOk && !typeInfo.isDirectlySerializable()) { - return false; - } + JClassType classOrInterface = typeInfo.getType(); + if (classOrInterface.isEnum() != null) { + // The fields of an enum are never serialized; they are always okay. + return true; } - if (typeInfo.isManuallySerializable()) { - // All fields on a manual serializable are considered speculative. - isSpeculative = true; - } + // All fields on a manual serializable are considered speculative. + isSpeculative |= typeInfo.isManuallySerializable(); boolean allSucceeded = true; JField[] fields = classOrInterface.getFields(); @@ -1187,9 +1091,158 @@ if (succeeded) { getTypeInfoComputed(classOrInterface, parent).setFieldSerializable(); } + return succeeded; } + private boolean checkFields(TreeLogger logger, JClassType classOrInterface, + boolean isSpeculative, Path parent) { + TypeInfoComputed typeInfo = getTypeInfoComputed(classOrInterface, parent); + + // Check all super type fields first (recursively). + JClassType superType = classOrInterface.getSuperclass(); + if (superType != null + && getTypeInfoComputed(superType, parent).isDeclaredSerializable()) { + boolean superTypeOk = checkFields(logger, superType, isSpeculative, + parent); + /* + * If my super type did not check out, then I am not instantiable and we + * should error out... UNLESS I am *directly* serializable myself, in + * which case it's ok for me to be the root of a new instantiable + * hierarchy. + */ + if (!superTypeOk && !typeInfo.isDirectlySerializable()) { + return false; + } + } + + return checkDeclaredFields(logger, typeInfo, isSpeculative, parent); + } + + /** + * Returns <code>true</code> if this type or one of its subtypes is + * instantiable relative to a known base type. + */ + private boolean checkSubtypes(TreeLogger localLogger, + JClassType originalType, JRealClassType baseType, + Set<JClassType> instSubtypes, JClassType[] typeArgs, final Path path, + JClassType currentSubtype, boolean checkSuperFields, + boolean superIsFieldSerializable) { + assert (currentSubtype instanceof JRealClassType || currentSubtype.isGenericType() != null); + + Path subtypePath = createSubtypePath(path, currentSubtype, originalType); + TypeInfoComputed typeInfo = getTypeInfoComputed(currentSubtype, subtypePath); + + TreeLogger subtypeLogger = localLogger.branch(TreeLogger.DEBUG, + currentSubtype.getParameterizedQualifiedSourceName(), null); + + /* + * If super does not qualify and I am not directly serializable then I can't + * be serializable. + */ + boolean subInstantiable = qualifiesForSerialization(subtypeLogger, + currentSubtype, true, subtypePath); + if (!superIsFieldSerializable) { + subInstantiable &= typeInfo.isDirectlySerializable(); + } + + if (subInstantiable) { + if (checkSuperFields) { + subInstantiable &= checkFields(subtypeLogger, typeInfo.getType(), true, + subtypePath); + } else { + subInstantiable &= checkDeclaredFields(subtypeLogger, typeInfo, true, + subtypePath); + } + } + + boolean anySubtypes = false; + List<JClassType> immediateSubtypes = TypeHierarchyUtils.getImmediateSubtypes(currentSubtype); + for (JClassType immediateSubtype : immediateSubtypes) { + if (immediateSubtype.isRawType() != null) { + immediateSubtype = immediateSubtype.isRawType().getBaseType(); + } + + checkSuperFields = currentSubtype.isInterface() != null + && immediateSubtype.isClass() != null; + anySubtypes |= checkSubtypes(localLogger, originalType, baseType, + instSubtypes, typeArgs, path, immediateSubtype, checkSuperFields, + subInstantiable); + } + + if (!subInstantiable) { + // If the subtype is not instantiable do not check the arguments. + return anySubtypes; + } + + if (currentSubtype == baseType) { + return anySubtypes; + } + + JGenericType genericSub = currentSubtype.isGenericType(); + if (genericSub != null) { + TreeLogger paramsLogger = subtypeLogger.branch(TreeLogger.DEBUG, + "Checking parameters of '" + + genericSub.getParameterizedQualifiedSourceName() + "'"); + Map<JTypeParameter, Set<JTypeParameter>> subParamsConstrainedBy = subParamsConstrainedBy( + baseType, genericSub); + for (int i = 0; i < genericSub.getTypeParameters().length; i++) { + JTypeParameter param = genericSub.getTypeParameters()[i]; + TreeLogger paramLogger = paramsLogger.branch(TreeLogger.DEBUG, + "Checking param '" + param.getParameterizedQualifiedSourceName() + + "'"); + Set<JTypeParameter> constBy = subParamsConstrainedBy.get(param); + if (constBy == null) { + subInstantiable &= checkTypeArgument(paramLogger, genericSub, i, + param.getFirstBound(), true, path); + } else { + boolean paramOK = false; + for (JTypeParameter constrained : constBy) { + paramOK |= checkTypeArgument(paramLogger, genericSub, i, + typeArgs[constrained.getOrdinal()], true, path); + } + subInstantiable &= paramOK; + } + } + } else { + JParameterizedType isParameterized = originalType.isParameterized(); + + /* + * The subtype is not generic so it must be a concrete parameterization of + * the query type. If the query type is also a concrete parameterization + * then we should be able to exclude the subtype based on an assignability + * check. If the query type does contain type parameters then we assume + * that the subtype should be included. In order to be certain, we would + * need to perform a full unification between the query type and subtype. + */ + if (isParameterized != null) { + HashSet<JTypeParameter> typeParamsInQueryType = new HashSet<JTypeParameter>(); + recordTypeParametersIn(isParameterized, typeParamsInQueryType); + + if (typeParamsInQueryType.isEmpty()) { + if (!isParameterized.isAssignableFrom(currentSubtype)) { + subtypeLogger.log(TreeLogger.DEBUG, "Excluding type '" + + currentSubtype.getParameterizedQualifiedSourceName() + + "' because it is not assignable to '" + + isParameterized.getParameterizedQualifiedSourceName() + "'"); + subInstantiable = false; + } + } + } + } + + if (subInstantiable) { + if (instSubtypes != null) { + instSubtypes.add(currentSubtype); + } + + typeInfo.setInstantiable(true); + anySubtypes = true; + } + + return anySubtypes; + } + /** * Check the argument to a parameterized type to see if it will make the type * it is applied to be serializable. As a side effect, populates
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java index 79feed8..a2fb1e8 100644 --- a/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java +++ b/user/src/com/google/gwt/user/rebind/rpc/TypeHierarchyUtils.java
@@ -68,6 +68,22 @@ return Arrays.asList(types.toArray(new JClassType[0])); } + /** + * Returns the immediate subtypes of the erased class argument. + */ + public static List<JClassType> getImmediateSubtypes(JClassType clazz) { + List<JClassType> immediateSubtypes = new ArrayList<JClassType>(); + clazz = clazz.getErasedType(); + for (JClassType subclass : clazz.getSubtypes()) { + if (subclass.getSuperclass() == clazz || clazz.isInterface() != null + && directlyImplementsInterface(subclass, clazz)) { + immediateSubtypes.add(subclass); + } + } + + return immediateSubtypes; + } + private static void addEdge(Map<JClassType, List<JClassType>> adjList, JClassType subclass, JClassType clazz) { List<JClassType> edges = adjList.get(subclass); @@ -119,21 +135,6 @@ } /** - * Returns the immediate subtypes of a given type. - */ - private static List<JClassType> getImmediateSubtypes(JClassType clazz) { - List<JClassType> immediateSubtypes = new ArrayList<JClassType>(); - for (JClassType subclass : clazz.getSubtypes()) { - if (subclass.getSuperclass() == clazz || clazz.isInterface() != null - && directlyImplementsInterface(subclass, clazz)) { - immediateSubtypes.add(subclass); - } - } - - return immediateSubtypes; - } - - /** * Given a root type return an adjacency list that is the inverted type * hierarchy. */