Reworked error logging in STOB, coincidentally gaining a 22% compile-time improvement in DynaTable sample by improving short-circuiting of already-examined types and avoiding "speculative" STOB processing.  Basic idea is that errors are accumulated in a ProblemReport until we later know the overall result and can decide whether and how to emit them.

Issues: 3461
Review by: spoon


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@5256 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java b/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java
new file mode 100644
index 0000000..56b000b
--- /dev/null
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProblemReport.java
@@ -0,0 +1,223 @@
+/*

+ * Copyright 2008 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.TreeLogger.Type;

+import com.google.gwt.core.ext.typeinfo.JClassType;

+

+import java.util.ArrayList;

+import java.util.Comparator;

+import java.util.List;

+import java.util.Map;

+import java.util.TreeMap;

+

+/**

+ * A collection of reported problems; these are accumulated during the

+ * SerializableTypeOracleBuilder's isSerializable analysis, and what to do about

+ * the problems is decided only later.

+ */

+public class ProblemReport {

+

+  /**

+   * Priority of problems.  {@link #FATAL}  problems will fail a build that

+   * would otherwise have succeeded, for example because of a bad custom

+   * serializer used only as a subclass of a superclass with other viable

+   * subtypes.  {@link #DEFAULT} problems might or might not be fatal,

+   * depending on overall results accumulated later.  {@link #AUXILIARY}

+   * problems are not fatal, and often not even problems by themselves, but

+   * diagnostics related to default problems (e.g. type filtration, which

+   * might suppress an intended-to-serialize class).

+   */

+  public enum Priority { FATAL, DEFAULT, AUXILIARY}

+

+  private Map<JClassType, List<String>> allProblems;

+  private Map<JClassType, List<String>> auxiliaries;

+  private Map<JClassType, List<String>> fatalProblems;

+  private JClassType contextType;

+

+  /**

+   * Creates a new, empty, context-less ProblemReport.

+   */

+  public ProblemReport() {

+    Comparator<JClassType> comparator = new Comparator<JClassType>() {

+        public int compare(JClassType o1, JClassType o2) {

+          assert o1 != null;

+          assert o2 != null;

+          return o1.getParameterizedQualifiedSourceName().compareTo(

+              o2.getParameterizedQualifiedSourceName());

+        }

+      };

+    allProblems = new TreeMap<JClassType, List<String>>(comparator);

+    auxiliaries = new TreeMap<JClassType, List<String>>(comparator);

+    fatalProblems = new TreeMap<JClassType, List<String>>(comparator);

+    contextType = null;

+  }

+

+  /**

+   * Adds a problem for a given type.  This also sorts the problems into

+   * collections by priority.

+   *

+   * @param type the problematic type

+   * @param message the description of the problem

+   * @param priority priority of the problem.

+   * @param extraLines additional continuation lines for the message, usually

+   *    for additional explanations.

+   */

+  public void add(JClassType type, String message, Priority priority, 

+      String... extraLines) {

+    String contextString = "";

+    if (contextType != null) {

+      contextString = " (reached via " +

+          contextType.getParameterizedQualifiedSourceName() + ")";

+    }

+    message = message + contextString;

+    for (String line : extraLines) {

+      message = message + "\n   " + line;

+    }

+    if (priority == Priority.AUXILIARY) {

+      addToMap(type, message, auxiliaries);

+      return;

+    }

+

+    // both FATAL and DEFAULT problems go in allProblems...

+    addToMap(type, message, allProblems);

+

+    // only FATAL problems go in fatalProblems...

+    if (priority == Priority.FATAL) {

+      addToMap(type, message, fatalProblems);

+    }

+  }

+

+  /**

+   * Returns list of auxiliary "problems" logged against a given type.

+   *

+   * @param type type to fetch problems for

+   * @return {@code null} if no auxiliaries were logged.  Otherwise, a list

+   *   of strings describing messages, including the context in which the

+   *   problem was found.

+   */

+  public List<String> getAuxiliaryMessagesForType(JClassType type) {

+    List<String> list = auxiliaries.get(type);

+    if (list == null) {

+      list = new ArrayList<String>(0);

+    }

+    return list;

+  }

+

+  /**

+   * Returns list of problems logged against a given type.

+   *

+   * @param type type to fetch problems for

+   * @return {@code null} if no problems were logged.  Otherwise, a list

+   *   of strings describing problems, including the context in which the

+   *   problem was found.

+   */

+  public List<String> getProblemsForType(JClassType type) {

+    List<String> list = allProblems.get(type);

+    if (list == null) {

+      list = new ArrayList<String>(0);

+    }

+    return list;

+  }

+

+  /**

+   * Returns the number of types against which problems were reported.

+   *

+   * @return number of problematic types

+   */

+  public int getProblemTypeCount() {

+    return allProblems.size();

+  }

+

+  /**

+   * Were any problems reported as "fatal"?

+   */

+  public boolean hasFatalProblems() {

+    return !fatalProblems.isEmpty();

+  }

+

+  /**

+   * Reports all problems to the logger supplied, at the log level supplied.

+   * The problems are assured of being reported in lexographic order of

+   * type names.

+   *

+   * @param logger logger to receive problem reports

+   * @param problemLevel severity level at which to report problems.

+   * @param auxLevel severity level at which to report any auxiliary messages.

+   */

+  public void report(TreeLogger logger, TreeLogger.Type problemLevel,

+      TreeLogger.Type auxLevel) {

+    doReport(logger, auxLevel, auxiliaries);

+    doReport(logger, problemLevel, allProblems);

+  }

+

+  /**

+   * Reports only urgent problems to the logger supplied, at the log level

+   * supplied.  The problems are assured of being reported in lexographic

+   * order of type names.

+   *

+   * @param logger logger to receive problem reports

+   * @param level severity level at which to report problems.

+   */

+  public void reportFatalProblems(TreeLogger logger, TreeLogger.Type level) {

+    doReport(logger, level, fatalProblems);

+  }

+

+  /**

+   * Sets the context type currently being analyzed.  Problems found will

+   * include reference to this context, until reset with another call to this

+   * method.  Context may be cancelled with a {@code null} value here.

+   *

+   * @param newContext the type under analysis

+   */

+  public void setContextType(JClassType newContext) {

+    contextType = newContext;

+  }

+

+  /**

+   * Adds an entry to one of the problem maps.

+   *

+   * @param type the type to add

+   * @param message the message to add for {@code type}

+   * @param map the map to add to

+   */

+  private void addToMap(JClassType type, String message,

+      Map<JClassType, List<String>> map) {

+    List<String> entry = map.get(type);

+    if (entry == null) {

+      entry = new ArrayList<String>();

+      map.put(type, entry);

+    }

+    entry.add(message);

+  }

+

+  /**

+   * Logs all of the problems from one of the problem maps.

+   *

+   * @param logger the logger to log to

+   * @param level the level for messages

+   * @param problems the problems to log

+   */

+  private void doReport(TreeLogger logger, Type level,

+      Map<JClassType, List<String>> problems) {

+    for (JClassType type : problems.keySet()) {

+      for (String message : problems.get(type)) {

+        logger.log(level, message);

+      }

+    }

+  }

+}

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 fa8eb9e..616367a 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -33,6 +33,7 @@
 import com.google.gwt.dev.util.log.PrintWriterTreeLogger;
 import com.google.gwt.user.client.rpc.GwtTransient;
 import com.google.gwt.user.client.rpc.IsSerializable;
+import com.google.gwt.user.rebind.rpc.ProblemReport.Priority;
 import com.google.gwt.user.rebind.rpc.TypeParameterExposureComputer.TypeParameterFlowInfo;
 import com.google.gwt.user.rebind.rpc.TypePaths.TypePath;
 
@@ -56,13 +57,13 @@
 
 /**
  * Builds a {@link SerializableTypeOracle} for a given set of root types.
- * 
+ *
  * <p>
  * There are two goals for this builder. First, discover the set of serializable
  * types that can be serialized if you serialize one of the root types. Second,
  * to make sure that all root types can actually be serialized by GWT.
  * </p>
- * 
+ *
  * <p>
  * To find the serializable types, it includes the root types, and then it
  * iteratively traverses the type hierarchy and the fields of any type already
@@ -71,7 +72,7 @@
  * parameterized type, these exposure values are used to determine how to treat
  * the arguments.
  * </p>
- * 
+ *
  * <p>
  * A type qualifies for serialization if it or one of its subtypes is
  * automatically or manually serializable. Automatic serialization is selected
@@ -204,7 +205,7 @@
       state = TypeState.CHECK_DONE;
     }
 
-    public void setInstantiableSubytpes(boolean instantiableSubtypes) {
+    public void setInstantiableSubtypes(boolean instantiableSubtypes) {
       this.instantiableSubtypes = instantiableSubtypes;
     }
 
@@ -256,13 +257,12 @@
       return "Default";
     }
 
-    public boolean isAllowed(JClassType type) {
+    public boolean isAllowed(@SuppressWarnings("unused") JClassType type) {
       return true;
     }
   };
 
-  static boolean canBeInstantiated(TreeLogger logger, JClassType type,
-      TreeLogger.Type logLevel) {
+  static boolean canBeInstantiated(JClassType type, ProblemReport problems) {
     if (type.isEnum() == null) {
       if (type.isAbstract()) {
         // Abstract types will be picked up if there is an instantiable
@@ -272,11 +272,10 @@
 
       if (!type.isDefaultInstantiable() && !isManuallySerializable(type)) {
         // Warn and return false.
-        logger.log(
-            logLevel,
-            type.getParameterizedQualifiedSourceName()
-                + " was not default instantiable (it must have a zero-argument constructor or no constructors at all)",
-            null);
+        problems.add(type, type.getParameterizedQualifiedSourceName() +
+            " is not default instantiable (it must have a zero-argument "
+            + "constructor or no constructors at all) and has no custom "
+            + "serializer.", Priority.DEFAULT);
         return false;
       }
     } else {
@@ -291,7 +290,7 @@
 
   /**
    * Finds the custom field serializer for a given type.
-   * 
+   *
    * @param typeOracle
    * @param type
    * @return the custom field serializer for a type or <code>null</code> if
@@ -384,18 +383,17 @@
    * serialization. If it returns <code>false</code> then none of the fields of
    * this class should be serialized.
    */
-  static boolean shouldConsiderFieldsForSerialization(TreeLogger logger,
-      JClassType type, boolean isSpeculative, TypeFilter filter) {
-    if (!isAllowedByFilter(logger, filter, type, isSpeculative)) {
+  static boolean shouldConsiderFieldsForSerialization(JClassType type,
+       TypeFilter filter, ProblemReport problems) {
+    if (!isAllowedByFilter(filter, type, problems)) {
       return false;
     }
 
     if (!isDeclaredSerializable(type)) {
-      logger.branch(TreeLogger.DEBUG, "Type '"
-          + type.getParameterizedQualifiedSourceName()
-          + "' is not assignable to '" + IsSerializable.class.getName()
+      problems.add(type, type.getParameterizedQualifiedSourceName()
+          + " is not assignable to '" + IsSerializable.class.getName()
           + "' or '" + Serializable.class.getName()
-          + "' nor does it have a custom field serializer", null);
+          + "' nor does it have a custom field serializer", Priority.DEFAULT);
       return false;
     }
 
@@ -404,41 +402,32 @@
           type);
       assert (manualSerializer != null);
 
-      List<String> problems = CustomFieldSerializerValidator.validate(
+      List<String> fieldProblems = CustomFieldSerializerValidator.validate(
           manualSerializer, type);
-      if (!problems.isEmpty()) {
-        for (String problem : problems) {
-          logger.branch(getLogLevel(isSpeculative), problem, null);
+      if (!fieldProblems.isEmpty()) {
+        for (String problem : fieldProblems) {
+          problems.add(type, problem, Priority.FATAL);
         }
         return false;
       }
     } else {
       assert (isAutoSerializable(type));
 
-      /*
-       * Speculative paths log at DEBUG level, non-speculative ones log at WARN
-       * level.
-       */
-      TreeLogger.Type logLevel = isSpeculative ? TreeLogger.DEBUG
-          : TreeLogger.WARN;
-
       if (!isAccessibleToSerializer(type)) {
         // Class is not visible to a serializer class in the same package
-        logger.branch(
-            logLevel,
-            type.getParameterizedQualifiedSourceName()
-                + " is not accessible from a class in its same package; it will be excluded from the set of serializable types",
-            null);
+        problems.add(type, type.getParameterizedQualifiedSourceName()
+                + " is not accessible from a class in its same package; it "
+                + "will be excluded from the set of serializable types",
+            Priority.DEFAULT);
         return false;
       }
 
       if (type.isMemberType() && !type.isStatic()) {
         // Non-static member types cannot be serialized
-        logger.branch(
-            logLevel,
-            type.getParameterizedQualifiedSourceName()
-                + " is nested but not static; it will be excluded from the set of serializable types",
-            null);
+        problems.add(type,
+            type.getParameterizedQualifiedSourceName() + " is nested but " +
+            "not static; it will be excluded from the set of serializable " +
+            "types", Priority.DEFAULT);
         return false;
       }
     }
@@ -461,8 +450,18 @@
     }
 
     if (field.isFinal()) {
+      Type logLevel;
+      if (isManuallySerializable(field.getEnclosingType())) {
+        /*
+         * If the type has a custom serializer, assume the programmer knows
+         * best.
+         */
+        logLevel = TreeLogger.DEBUG;
+      } else {
+        logLevel = TreeLogger.WARN;
+      }
       logger.branch(suppressNonStaticFinalFieldWarnings ? TreeLogger.DEBUG
-          : TreeLogger.WARN, "Field '" + field.toString()
+          : logLevel, "Field '" + field.toString()
           + "' will not be serialized because it is final", null);
       return false;
     }
@@ -500,10 +499,6 @@
     return type.getOracle().getType(IsSerializable.class.getName());
   }
 
-  private static Type getLogLevel(boolean isSpeculative) {
-    return isSpeculative ? TreeLogger.WARN : TreeLogger.ERROR;
-  }
-
   private static JClassType getSerializableMarkerInterface(JClassType type)
       throws NotFoundException {
     return type.getOracle().getType(Serializable.class.getName());
@@ -530,10 +525,12 @@
     return true;
   }
 
-  private static boolean isAllowedByFilter(TreeLogger logger,
-      TypeFilter filter, JClassType classType, boolean isSpeculative) {
+  private static boolean isAllowedByFilter(TypeFilter filter,
+        JClassType classType, ProblemReport problems) {
     if (!filter.isAllowed(classType)) {
-      logger.log(getLogLevel(isSpeculative), "Excluded by type filter ");
+      problems.add(classType,
+          classType.getParameterizedQualifiedSourceName()
+             + " is excluded by type filter ", Priority.AUXILIARY);
       return false;
     }
 
@@ -610,11 +607,11 @@
 
   /**
    * Constructs a builder.
-   * 
+   *
    * @param logger
    * @param propertyOracle
    * @param typeOracle
-   * 
+   *
    * @throws UnableToCompleteException if we fail to find one of our special
    *           types
    */
@@ -654,12 +651,12 @@
 
   /**
    * Builds a {@link SerializableTypeOracle} for a given set of root types.
-   * 
+   *
    * @param logger
-   * 
+   *
    * @return a {@link SerializableTypeOracle} for the specified set of root
    *         types
-   * 
+   *
    * @throws UnableToCompleteException if there was not at least one
    *           instantiable type assignable to each of the specified root types
    */
@@ -668,13 +665,31 @@
     alreadyCheckedObject = false;
 
     boolean allSucceeded = true;
+
     for (Entry<JClassType, TreeLogger> entry : rootTypes.entrySet()) {
-      allSucceeded &= checkTypeInstantiable(entry.getValue(), entry.getKey(),
-          false, TypePaths.createRootPath(entry.getKey()));
+      ProblemReport problems = new ProblemReport();
+      problems.setContextType(entry.getKey());
+      boolean entrySucceeded = checkTypeInstantiable(entry.getValue(),
+          entry.getKey(), TypePaths.createRootPath(entry.getKey()), problems);
+      if (!entrySucceeded) {
+        problems.report(logger, TreeLogger.ERROR, TreeLogger.INFO);
+      } else {
+        if (problems.hasFatalProblems()) {
+          entrySucceeded = false;
+          problems.reportFatalProblems(logger, TreeLogger.ERROR);
+        }
+        // if entrySucceeded, there may still be "warning" problems, but too
+        // often they're expected (e.g. non-instantiable subtypes of List), so
+        // we log them at DEBUG.
+        // TODO(fabbott): we could blacklist or graylist those types here, so
+        // instantiation during code generation would flag them for us.
+        problems.report(logger, TreeLogger.DEBUG, TreeLogger.DEBUG);
+      }
+
+      allSucceeded &= entrySucceeded;
     }
 
     if (!allSucceeded) {
-      // the validation code has already logged why
       throw new UnableToCompleteException();
     }
 
@@ -733,19 +748,21 @@
    * This method determines whether a type can be serialized by GWT. To do so,
    * it must traverse all subtypes as well as all field types of those types,
    * transitively.
-   * 
+   *
    * It returns a boolean indicating whether this type or any of its subtypes
    * are instantiable.
-   * 
+   *
    * As a side effect, all types needed--plus some--to serialize this type are
-   * accumulated in {@link #typeToTypeInfoComputed}.
-   * 
+   * accumulated in {@link #typeToTypeInfoComputed}.  In particular, there
+   * will be an entry for any type that has been validated by this method, as
+   * a shortcircuit to avoid recomputation.
+   *
    * The method is exposed using default access to enable testing.
    */
   final boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative, TypePath path) {
-    return checkTypeInstantiable(logger, type, isSpeculative, path,
-        new HashSet<JClassType>());
+      TypePath path, ProblemReport problems) {
+    return checkTypeInstantiable(logger, type, path, new HashSet<JClassType>(),
+        problems);
   }
 
   /**
@@ -754,7 +771,7 @@
    * , except that returns the set of instantiable subtypes.
    */
   boolean checkTypeInstantiable(TreeLogger logger, JType type,
-      boolean isSpeculative, TypePath path, Set<JClassType> instSubtypes) {
+      TypePath path, Set<JClassType> instSubtypes, ProblemReport problems) {
     assert (type != null);
     if (type.isPrimitive() != null) {
       return true;
@@ -764,6 +781,12 @@
 
     JClassType classType = (JClassType) type;
 
+    TypeInfoComputed tic = getTypeInfoComputed(classType, path, false);
+    if (tic != null && tic.isDone()) {
+      // we have an answer already; use it.
+      return tic.hasInstantiableSubtypes();
+    }
+
     TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
         classType.getParameterizedQualifiedSourceName(), null);
 
@@ -771,16 +794,19 @@
     if (isTypeParameter != null) {
       if (typeParametersInRootTypes.contains(isTypeParameter)) {
         return checkTypeInstantiable(localLogger,
-            isTypeParameter.getFirstBound(), isSpeculative,
+            isTypeParameter.getFirstBound(),
             TypePaths.createTypeParameterInRootPath(path, isTypeParameter),
-            instSubtypes);
+            instSubtypes, problems);
       }
 
       /*
        * This type parameter was not in a root type and therefore it is the
        * caller's responsibility to deal with it. We assume that it is
-       * instantiable here.
+       * indirectly instantiable here.
        */
+      tic = getTypeInfoComputed(classType, path, true);
+      tic.setInstantiableSubtypes(true);
+      tic.setInstantiable(false);
       return true;
     }
 
@@ -788,31 +814,38 @@
     if (isWildcard != null) {
       boolean success = true;
       for (JClassType bound : isWildcard.getUpperBounds()) {
-        success &= checkTypeInstantiable(localLogger, bound, isSpeculative,
-            path);
+        success &= checkTypeInstantiable(localLogger, bound, path, problems);
       }
+      tic = getTypeInfoComputed(classType, path, true);
+      tic.setInstantiableSubtypes(success);
+      tic.setInstantiable(false);
       return success;
     }
 
     JArrayType isArray = classType.isArray();
     if (isArray != null) {
-      return checkArrayInstantiable(localLogger, isArray, isSpeculative, path);
+      boolean success = checkArrayInstantiable(localLogger, isArray, path,
+          problems);
+      assert getTypeInfoComputed(classType, path, false) != null;
+      return success;
     }
 
     if (classType == typeOracle.getJavaLangObject()) {
       /*
        * Report an error if the type is or erases to Object since this violates
-       * our restrictions on RPC.
+       * our restrictions on RPC.  Should be fatal, but I worry users may have
+       * Object-using code they can't readily get out of the class hierarchy.
        */
-      localLogger.branch(
-          getLogLevel(isSpeculative),
-          "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
-          null);
+      problems.add(classType,
+          "In order to produce smaller client-side code, 'Object' is not " +
+          "allowed; please use a more specific type", Priority.DEFAULT);
+      tic = getTypeInfoComputed(classType, path, true);
+      tic.setInstantiable(false);
       return false;
     }
 
     if (classType.isRawType() != null) {
-      localLogger.branch(
+      localLogger.log(
           TreeLogger.DEBUG,
           "Type '"
               + classType.getQualifiedSourceName()
@@ -821,24 +854,16 @@
     }
 
     JClassType originalType = (JClassType) type;
-    if (isSpeculative && isDirectlySerializable(originalType)) {
-      // TODO: Our speculative tracking is off.
-      isSpeculative = false;
-    }
-    //
+
     // TreeLogger subtypesLogger = localLogger.branch(TreeLogger.DEBUG,
     // "Analyzing subclasses:", null);
     boolean anySubtypes = checkSubtypes(localLogger, originalType,
-        instSubtypes, path);
-
-    if (!anySubtypes && !isSpeculative) {
-      // No instantiable types were found
-      localLogger.branch(getLogLevel(isSpeculative), "Type '"
-          + classType.getParameterizedQualifiedSourceName()
-          + "' was not serializable and has no concrete serializable subtypes",
-          null);
+        instSubtypes, path, problems);
+    tic = getTypeInfoComputed(classType, path, true);
+    if (!tic.isDone()) {
+      tic.setInstantiableSubtypes(anySubtypes);
+      tic.setInstantiable(false);
     }
-
     return anySubtypes;
   }
 
@@ -849,21 +874,22 @@
   /**
    * Returns <code>true</code> if the fields of the type should be considered
    * for serialization.
-   * 
+   *
    * Default access to allow for testing.
    */
-  boolean shouldConsiderFieldsForSerialization(TreeLogger logger,
-      JClassType type, boolean isSpeculative) {
-    return shouldConsiderFieldsForSerialization(logger, type, isSpeculative,
-        typeFilter);
+  boolean shouldConsiderFieldsForSerialization(JClassType type,
+      ProblemReport problems) {
+    return shouldConsiderFieldsForSerialization(type, typeFilter,
+        problems);
   }
 
   /**
    * Consider any subtype of java.lang.Object which qualifies for serialization.
-   * 
+   *
    * @param logger
    */
-  private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent) {
+  private void checkAllSubtypesOfObject(TreeLogger logger, TypePath parent,
+      ProblemReport problems) {
     if (alreadyCheckedObject) {
       return;
     }
@@ -880,37 +906,45 @@
     JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
     for (JClassType cls : allTypes) {
       if (isDeclaredSerializable(cls)) {
-        checkTypeInstantiable(localLogger, cls, true,
+        checkTypeInstantiable(localLogger, cls,
             TypePaths.createSubtypePath(parent, cls,
-                typeOracle.getJavaLangObject()));
+                typeOracle.getJavaLangObject()), problems);
       }
     }
   }
 
   private boolean checkArrayInstantiable(TreeLogger logger, JArrayType array,
-      boolean isSpeculative, TypePath path) {
+      TypePath path, ProblemReport problems) {
 
     JType leafType = array.getLeafType();
     JWildcardType leafWild = leafType.isWildcard();
     if (leafWild != null) {
       JArrayType arrayType = getArrayType(typeOracle, array.getRank(),
           leafWild.getUpperBound());
-      return checkArrayInstantiable(logger, arrayType, isSpeculative, path);
+      return checkArrayInstantiable(logger, arrayType, path, problems);
     }
 
     JClassType leafClass = leafType.isClassOrInterface();
     JTypeParameter isLeafTypeParameter = leafType.isTypeParameter();
     if (isLeafTypeParameter != null
         && !typeParametersInRootTypes.contains(isLeafTypeParameter)) {
-      // Don't deal with non root type parameters
+      // Don't deal with non root type parameters, but make a TIC entry to
+      // save time if it recurs.  We assume they're indirectly instantiable.
+      TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
+      tic.setInstantiableSubtypes(true);
+      tic.setInstantiable(false);
       return true;
     }
 
-    if (!isAllowedByFilter(logger, array, isSpeculative)) {
+    if (!isAllowedByFilter(array, problems)) {
+      // Don't deal with filtered out types either, but make a TIC entry to
+      // save time if it recurs.  We assume they're not instantiable.
+      TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
+      tic.setInstantiable(false);
       return false;
     }
 
-    TypeInfoComputed tic = getTypeInfoComputed(array, path);
+    TypeInfoComputed tic = getTypeInfoComputed(array, path, true);
     if (tic.isDone()) {
       return tic.hasInstantiableSubtypes();
     } else if (tic.isPendingInstantiable()) {
@@ -922,8 +956,9 @@
         "Analyzing component type:", null);
     Set<JClassType> instantiableTypes = new HashSet<JClassType>();
 
-    boolean succeeded = checkTypeInstantiable(branch, leafType, isSpeculative,
-        TypePaths.createArrayComponentPath(array, path), instantiableTypes);
+    boolean succeeded = checkTypeInstantiable(branch, leafType,
+        TypePaths.createArrayComponentPath(array, path), instantiableTypes,
+        problems);
     if (succeeded) {
       if (leafClass == null) {
         assert leafType.isPrimitive() != null;
@@ -961,7 +996,7 @@
    * necessary types.
    */
   private boolean checkDeclaredFields(TreeLogger logger,
-      TypeInfoComputed typeInfo, boolean isSpeculative, TypePath parent) {
+      TypeInfoComputed typeInfo, TypePath parent, ProblemReport problems) {
 
     JClassType classOrInterface = typeInfo.getType();
     if (classOrInterface.isEnum() != null) {
@@ -969,9 +1004,6 @@
       return true;
     }
 
-    // All fields on a manual serializable are considered speculative.
-    isSpeculative |= typeInfo.isManuallySerializable();
-
     JClassType baseType = getBaseType(classOrInterface);
 
     boolean allSucceeded = true;
@@ -999,10 +1031,10 @@
             && fieldType.getLeafType() == typeOracle.getJavaLangObject()) {
           checkAllSubtypesOfObject(fieldLogger.branch(TreeLogger.WARN,
               "Object was reached from a manually serializable type", null),
-              path);
+              path, problems);
         } else {
-          allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType,
-              isSpeculative, path);
+          allSucceeded &= checkTypeInstantiable(fieldLogger, fieldType, path,
+              problems);
         }
       }
     }
@@ -1016,7 +1048,7 @@
   }
 
   private boolean checkSubtype(TreeLogger logger, JClassType classOrInterface,
-      JClassType originalType, boolean isSpeculative, TypePath parent) {
+      JClassType originalType, TypePath parent, ProblemReport problems) {
     if (classOrInterface.isEnum() != null) {
       // The fields of an enum are never serialized; they are always okay.
       return true;
@@ -1029,7 +1061,7 @@
          * Backwards compatibility. Raw collections or maps force all object
          * subtypes to be considered.
          */
-        checkAllSubtypesOfObject(logger, parent);
+        checkAllSubtypesOfObject(logger, parent, problems);
       } else {
         TreeLogger paramsLogger = logger.branch(TreeLogger.DEBUG,
             "Checking parameters of '"
@@ -1038,7 +1070,8 @@
         for (JTypeParameter param : isParameterized.getBaseType().getTypeParameters()) {
           if (!checkTypeArgument(paramsLogger, isParameterized.getBaseType(),
               param.getOrdinal(),
-              isParameterized.getTypeArgs()[param.getOrdinal()], true, parent)) {
+              isParameterized.getTypeArgs()[param.getOrdinal()], parent,
+              problems)) {
             return false;
           }
         }
@@ -1060,8 +1093,8 @@
       boolean superTypeOk = false;
       if (superType != null) {
         superTypeOk = checkSubtype(logger, superType, originalType,
-            isSpeculative, TypePaths.createSupertypePath(parent, superType,
-                classOrInterface));
+            TypePaths.createSupertypePath(parent, superType, classOrInterface),
+            problems);
       }
 
       /*
@@ -1074,8 +1107,8 @@
       }
     }
 
-    TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent);
-    return checkDeclaredFields(logger, tic, isSpeculative, parent);
+    TypeInfoComputed tic = getTypeInfoComputed(classOrInterface, parent, true);
+    return checkDeclaredFields(logger, tic, parent, problems);
   }
 
   /**
@@ -1083,12 +1116,12 @@
    * instantiable relative to a known base type.
    */
   private boolean checkSubtypes(TreeLogger logger, JClassType originalType,
-      Set<JClassType> instSubtypes, TypePath path) {
+      Set<JClassType> instSubtypes, TypePath path, ProblemReport problems) {
     JClassType baseType = getBaseType(originalType);
     TreeLogger computationLogger = logger.branch(TreeLogger.DEBUG,
         "Finding possibly instantiable subtypes");
     List<JClassType> candidates = getPossiblyInstantiableSubtypes(
-        computationLogger, baseType);
+        computationLogger, baseType, problems);
     boolean anySubtypes = false;
 
     TreeLogger verificationLogger = logger.branch(TreeLogger.DEBUG,
@@ -1105,13 +1138,13 @@
         }
       }
 
-      if (!isAllowedByFilter(verificationLogger, candidate, true)) {
+      if (!isAllowedByFilter(candidate, problems)) {
         continue;
       }
 
       TypePath subtypePath = TypePaths.createSubtypePath(path, candidate,
           originalType);
-      TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath);
+      TypeInfoComputed tic = getTypeInfoComputed(candidate, subtypePath, true);
       if (tic.isDone()) {
         if (tic.isInstantiable()) {
           anySubtypes = true;
@@ -1128,7 +1161,7 @@
       TreeLogger subtypeLogger = verificationLogger.branch(TreeLogger.DEBUG,
           candidate.getParameterizedQualifiedSourceName());
       boolean instantiable = checkSubtype(subtypeLogger, candidate,
-          originalType, true, subtypePath);
+          originalType, subtypePath, problems);
       anySubtypes |= instantiable;
       tic.setInstantiable(instantiable);
 
@@ -1152,25 +1185,25 @@
    * it is applied to be serializable. As a side effect, populates
    * {@link #typeToTypeInfoComputed} in the same way as
    * {@link #checkTypeInstantiable(TreeLogger, JType, boolean)}.
-   * 
+   *
    * @param logger
    * @param baseType - The generic type the parameter is on
    * @param paramIndex - The index of the parameter in the generic type
    * @param typeArg - An upper bound on the actual argument being applied to the
    *          generic type
-   * @param isSpeculative
-   * 
+   *
    * @return Whether the a parameterized type can be serializable if
    *         <code>baseType</code> is the base type and the
    *         <code>paramIndex</code>th type argument is a subtype of
    *         <code>typeArg</code>.
    */
   private boolean checkTypeArgument(TreeLogger logger, JGenericType baseType,
-      int paramIndex, JClassType typeArg, boolean isSpeculative, TypePath parent) {
+      int paramIndex, JClassType typeArg, TypePath parent,
+      ProblemReport problems) {
     JWildcardType isWildcard = typeArg.isWildcard();
     if (isWildcard != null) {
       return checkTypeArgument(logger, baseType, paramIndex,
-          isWildcard.getUpperBound(), isSpeculative, parent);
+          isWildcard.getUpperBound(), parent, problems);
     }
 
     JArrayType typeArgAsArray = typeArg.isArray();
@@ -1185,13 +1218,13 @@
               paramIndex);
           if (otherFlowInfo.getExposure() >= 0
               && otherFlowInfo.isTransitivelyAffectedBy(flowInfoForArrayParam)) {
-            logger.branch(
-                getLogLevel(isSpeculative),
+            problems.add(baseType,
                 "Cannot serialize type '"
                     + baseType.getParameterizedQualifiedSourceName()
                     + "' when given an argument of type '"
                     + typeArg.getParameterizedQualifiedSourceName()
-                    + "' because it appears to require serializing arrays of unbounded dimension");
+                    + "' because it appears to require serializing arrays "
+                    + "of unbounded dimension", Priority.DEFAULT);
             return false;
           }
         }
@@ -1210,28 +1243,28 @@
                 + " of type '"
                 + baseType.getParameterizedQualifiedSourceName()
                 + "' because it is directly exposed in this type or in one of its subtypes");
-        return checkTypeInstantiable(branch, typeArg, true, path)
+        return checkTypeInstantiable(branch, typeArg, path, problems)
             || mightNotBeExposed(baseType, paramIndex);
       }
       case TypeParameterExposureComputer.EXPOSURE_NONE:
         // Ignore this argument
-        logger.branch(TreeLogger.DEBUG, "Ignoring type argument " + paramIndex
+        logger.log(Type.DEBUG, "Ignoring type argument " + paramIndex
             + " of type '" + baseType.getParameterizedQualifiedSourceName()
             + "' because it is not exposed in this or any subtype");
         return true;
 
       default: {
         assert (exposure >= TypeParameterExposureComputer.EXPOSURE_MIN_BOUNDED_ARRAY);
-        TreeLogger branch = logger.branch(
-            TreeLogger.DEBUG,
+        problems.add(getArrayType(typeOracle, exposure, typeArg),
             "Checking type argument "
                 + paramIndex
                 + " of type '"
                 + baseType.getParameterizedQualifiedSourceName()
                 + "' because it is exposed as an array with a maximum dimension of "
-                + exposure + " in this type or one of its subtypes");
-        return checkTypeInstantiable(branch, getArrayType(typeOracle, exposure,
-            typeArg), true, path)
+                + exposure + " in this type or one of its subtypes",
+            Priority.AUXILIARY);
+        return checkTypeInstantiable(logger, getArrayType(typeOracle, exposure,
+            typeArg), path, problems)
             || mightNotBeExposed(baseType, paramIndex);
       }
     }
@@ -1257,7 +1290,7 @@
    * Returns the subtypes of a given base type as parameterized by wildcards.
    */
   private List<JClassType> getPossiblyInstantiableSubtypes(TreeLogger logger,
-      JClassType baseType) {
+      JClassType baseType, ProblemReport problems) {
     assert (baseType == getBaseType(baseType));
 
     List<JClassType> possiblyInstantiableTypes = new ArrayList<JClassType>();
@@ -1267,11 +1300,12 @@
 
     List<JClassType> candidates = new ArrayList<JClassType>();
     candidates.add(baseType);
-    candidates.addAll(Arrays.asList(baseType.getSubtypes()));
+    List<JClassType> subtypes = Arrays.asList(baseType.getSubtypes());
+    candidates.addAll(subtypes);
 
     for (JClassType subtype : candidates) {
       JClassType subtypeBase = getBaseType(subtype);
-      if (maybeInstantiable(logger, subtypeBase)) {
+      if (maybeInstantiable(logger, subtypeBase, problems)) {
         /*
          * Convert the generic type into a parameterization that only includes
          * wildcards.
@@ -1287,21 +1321,51 @@
       }
     }
 
+    if (possiblyInstantiableTypes.size() == 0) {
+      String possibilities[] = new String[candidates.size()];
+      for (int i = 0; i < possibilities.length; i++) {
+        JClassType subtype = candidates.get(i); 
+        List<String> auxiliaries = problems.getAuxiliaryMessagesForType(subtype);
+        List<String> errors = problems.getProblemsForType(subtype);
+        if (errors.isEmpty()) {
+          if (auxiliaries.isEmpty()) {
+            possibilities[i] = "   subtype " + 
+              subtype.getParameterizedQualifiedSourceName() +
+              " is not instantiable";
+          } else {
+            // message with have the form "FQCN some-problem-description"
+            possibilities[i] = "   subtype " + auxiliaries.get(0);
+            if (auxiliaries.size() > 1) {
+              possibilities[i] = possibilities[i] + ", etc.";
+            }
+          }
+        } else {
+          possibilities[i] = "   subtype " + errors.get(0);
+          if (errors.size() > 1 || !auxiliaries.isEmpty()) {
+            possibilities[i] = possibilities[i] + ", etc.";
+          }
+        }
+      }
+      problems.add(baseType, baseType.getParameterizedQualifiedSourceName()
+          + " has no available instantiable subtypes.", Priority.DEFAULT,
+          possibilities);
+    }
     return possiblyInstantiableTypes;
   }
 
-  private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path) {
+  private TypeInfoComputed getTypeInfoComputed(JClassType type, TypePath path,
+      boolean createIfNeeded) {
     TypeInfoComputed tic = typeToTypeInfoComputed.get(type);
-    if (tic == null) {
+    if (tic == null && createIfNeeded) {
       tic = new TypeInfoComputed(type, path);
       typeToTypeInfoComputed.put(type, tic);
     }
     return tic;
   }
 
-  private boolean isAllowedByFilter(TreeLogger logger, JClassType classType,
-      boolean isSpeculative) {
-    return isAllowedByFilter(logger, typeFilter, classType, isSpeculative);
+  private boolean isAllowedByFilter(JClassType classType,
+      ProblemReport problems) {
+    return isAllowedByFilter(typeFilter, classType, problems);
   }
 
   /**
@@ -1385,16 +1449,17 @@
       JArrayType covariantArray = getArrayType(typeOracle, rank, leafType);
 
       TypeInfoComputed covariantArrayTic = getTypeInfoComputed(covariantArray,
-          path);
+          path, true);
       covariantArrayTic.setInstantiable(true);
     }
   }
 
-  private boolean maybeInstantiable(TreeLogger logger, JClassType type) {
-    boolean success = canBeInstantiated(logger, type, TreeLogger.DEBUG)
-        && shouldConsiderFieldsForSerialization(logger, type, true);
+  private boolean maybeInstantiable(TreeLogger logger, JClassType type,
+      ProblemReport problems) {
+    boolean success = canBeInstantiated(type, problems)
+        && shouldConsiderFieldsForSerialization(type, problems);
     if (success) {
-      logger.branch(TreeLogger.DEBUG,
+      logger.log(TreeLogger.DEBUG,
           type.getParameterizedQualifiedSourceName() + " might be instantiable");
     }
     return success;
@@ -1408,7 +1473,7 @@
   /**
    * Remove serializable types that were visited due to speculative paths but
    * are not really needed for serialization.
-   * 
+   *
    * NOTE: This is currently much more limited than it should be. For example, a
    * path sensitive prune could remove instantiable types also.
    */
diff --git a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
index aab0345..e9d2874 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/TypeParameterExposureComputer.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 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
@@ -82,8 +82,10 @@
       boolean didChange = false;
       JClassType type = baseType;
       while (type != null) {
+        // any problems should already have been captured by our caller, so we
+        // make a throw-away ProblemReport here.
         if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
-            TreeLogger.NULL, type, true, typeFilter)) {
+            type, typeFilter, new ProblemReport())) {
           JField[] fields = type.getFields();
           for (JField field : fields) {
             if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(
@@ -249,8 +251,10 @@
           continue;
         }
 
+        // any problems should already have been captured by our caller, so we
+        // make a throw-away ProblemReport here.
         if (!SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
-            TreeLogger.NULL, subtype, true, typeFilter)) {
+            subtype, typeFilter, new ProblemReport())) {
           continue;
         }
 
@@ -267,7 +271,7 @@
       JClassType type = baseType;
       while (type != null) {
         if (SerializableTypeOracleBuilder.shouldConsiderFieldsForSerialization(
-            TreeLogger.NULL, type, true, typeFilter)) {
+            type, typeFilter, new ProblemReport())) {
           JField[] fields = type.getFields();
           for (JField field : fields) {
             if (!SerializableTypeOracleBuilder.shouldConsiderForSerialization(
@@ -367,7 +371,7 @@
   /**
    * Computes flow information for the specified type parameter. If it has
    * already been computed just return the value of the previous computation.
-   * 
+   *
    * @param type the generic type whose type parameter flow we are interested in
    * @param index the index of the type parameter whose flow we want to compute
    */
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 a7285fb..2cd14c3 100644
--- a/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
+++ b/user/test/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilderTest.java
@@ -1,12 +1,12 @@
 /*
  * Copyright 2008 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
@@ -320,7 +320,7 @@
   /**
    * Test with a generic class whose type parameter is exposed only in certain
    * subclasses.
-   * 
+   *
    * NOTE: This test has been disabled because it requires a better pruner in
    * STOB. See SerializableTypeOracleBuilder.pruneUnreachableTypes().
    */
@@ -381,7 +381,7 @@
 
   /**
    * Tests abstract root types that are field serializable.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -682,7 +682,7 @@
 
   /**
    * Tests the rules that govern whether a type qualifies for serialization.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -803,65 +803,81 @@
     // Does not qualify because it is not declared to be auto or manually
     // serializable
     JClassType notSerializable = to.getType("NotSerializable");
-    assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
-        notSerializable, false));
+    ProblemReport problems = new ProblemReport();
+    assertFalse(sob.shouldConsiderFieldsForSerialization(notSerializable,
+        problems));
 
     // Local types should not qualify for serialization
     JClassType iFoo = to.getType("AutoSerializable.IFoo");
-    assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
-        iFoo.getSubtypes()[0], false));
+    problems = new ProblemReport();
+    assertFalse(sob.shouldConsiderFieldsForSerialization(
+        iFoo.getSubtypes()[0],  problems));
 
     // Static nested types qualify for serialization
     JClassType staticNested = to.getType("OuterClass.StaticNested");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger, staticNested,
-        false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(staticNested,
+        problems));
 
     // Non-static nested types do not qualify for serialization
     JClassType nonStaticNested = to.getType("OuterClass.NonStaticNested");
-    assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
-        nonStaticNested, false));
+    problems = new ProblemReport();
+    assertFalse(sob.shouldConsiderFieldsForSerialization(
+        nonStaticNested, problems));
 
     // Abstract classes that implement Serializable should not qualify
     JClassType abstractSerializableClass = to.getType("AbstractSerializableClass");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
-        abstractSerializableClass, false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(
+        abstractSerializableClass, problems));
+
+    problems = new ProblemReport();
     assertFalse(SerializableTypeOracleBuilder.canBeInstantiated(
-        TreeLogger.NULL, abstractSerializableClass, TreeLogger.DEBUG));
+        abstractSerializableClass, problems));
 
     // Non-default instantiable types should not qualify
     JClassType nonDefaultInstantiableSerializable = to.getType("NonDefaultInstantiableSerializable");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
-        nonDefaultInstantiableSerializable, false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(
+        nonDefaultInstantiableSerializable, problems));
+
+    problems = new ProblemReport();
     assertFalse(SerializableTypeOracleBuilder.canBeInstantiated(
-        TreeLogger.NULL, nonDefaultInstantiableSerializable, TreeLogger.DEBUG));
+        nonDefaultInstantiableSerializable, problems));
 
     // SPublicStaticInnerInner is not accessible to classes in its package
     JClassType publicStaticInnerInner = to.getType("PublicOuterClass.PrivateStaticInner.PublicStaticInnerInner");
-    assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
-        publicStaticInnerInner, false));
+    problems = new ProblemReport();
+    assertFalse(sob.shouldConsiderFieldsForSerialization(
+        publicStaticInnerInner, problems));
 
     // DefaultStaticInnerInner is visible to classes in its package
     JClassType defaultStaticInnerInner = to.getType("PublicOuterClass.DefaultStaticInner.DefaultStaticInnerInner");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
-        defaultStaticInnerInner, false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(
+        defaultStaticInnerInner, problems));
 
     // Enum with subclasses should qualify, but their subtypes should not
     JClassType enumWithSubclasses = to.getType("EnumWithSubclasses");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
-        enumWithSubclasses, false));
-    assertFalse(sob.shouldConsiderFieldsForSerialization(logger,
-        enumWithSubclasses.getSubtypes()[0], false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(
+        enumWithSubclasses, problems));
+
+    problems = new ProblemReport();
+    assertFalse(sob.shouldConsiderFieldsForSerialization(
+        enumWithSubclasses.getSubtypes()[0], problems));
 
     // Enum that are not default instantiable should qualify
     JClassType enumWithNonDefaultCtors = to.getType("EnumWithNonDefaultCtors");
-    assertTrue(sob.shouldConsiderFieldsForSerialization(logger,
-        enumWithNonDefaultCtors, false));
+    problems = new ProblemReport();
+    assertTrue(sob.shouldConsiderFieldsForSerialization(
+        enumWithNonDefaultCtors, problems));
   }
 
   /**
    * Tests that both the generic and raw forms of type that has a type parameter
    * that erases to object are not serializable.
-   * 
+   *
    * @throws NotFoundException
    */
   public void testClassWithTypeParameterThatErasesToObject()
@@ -886,7 +902,7 @@
   /**
    * Test the situation where an abstract class has an unconstrained type
    * parameter but all of its concrete subclasses add helpful constraints to it.
-   * 
+   *
    * @throws NotFoundException
    * @throws UnableToCompleteException
    */
@@ -1022,7 +1038,7 @@
   /**
    * If the query type extends a raw type, be sure to pick up the parameters of
    * the raw subertype.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1095,7 +1111,7 @@
    * If a subtype of a root type extends from the raw version of that root type,
    * then when visiting the fields of the raw version, take advantage of
    * information from the original root type.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1656,8 +1672,143 @@
   }
 
   /**
+   * Tests a hierarchy blending various serializable and non-serializable
+   * types.
+   */
+  public void testProblemReporting() throws UnableToCompleteException,
+      NotFoundException {
+    Set<CompilationUnit> units = new HashSet<CompilationUnit>();
+    addStandardClasses(units);
+
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public interface TopInterface {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("TopInterface", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public abstract class AbstractSerializable implements\n");
+      code.append("    Serializable, TopInterface {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("AbstractSerializable", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public interface PureAbstractSerializable extends \n");
+      code.append("    Serializable, TopInterface {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("PureAbstractSerializable", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public abstract class PureAbstractClass implements \n");
+      code.append("    PureAbstractSerializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("PureAbstractClass", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public abstract class PureAbstractClassTwo extends \n");
+      code.append("    PureAbstractClass {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("PureAbstractClassTwo", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class ConcreteNonSerializable implements\n");
+      code.append("    TopInterface {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("ConcreteNonSerializable", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class ConcreteSerializable implements\n");
+      code.append("    Serializable, TopInterface {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("ConcreteSerializable", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("import java.io.Serializable;\n");
+      code.append("public class SubSerializable extends\n");
+      code.append("    ConcreteNonSerializable implements Serializable {\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("SubSerializable", code));
+    }
+    {
+      StringBuilder code = new StringBuilder();
+      code.append("public class ConcreteBadCtor extends\n");
+      code.append("    AbstractSerializable {\n");
+      code.append("  public ConcreteBadCtor(int i) {\n");
+      code.append("  }\n");
+      code.append("}\n");
+      units.add(createMockCompilationUnit("ConcreteBadCtor", code));
+    }
+    TreeLogger logger = createLogger();
+    TypeOracle to = TypeOracleTestingUtils.buildTypeOracle(logger, units);
+
+    SerializableTypeOracleBuilder stob = createSerializableTypeOracleBuilder(
+        logger, to);
+    JClassType topInterface = to.getType("TopInterface");
+    stob.addRootType(logger, topInterface);
+
+    ProblemReport problems = new ProblemReport();
+    assertTrue("TopInterface should be (partially) serializable",
+        stob.checkTypeInstantiable(logger, topInterface, null,
+            problems));
+    assertTrue("TopInterface should be a serializable type",
+        problems.getProblemsForType(topInterface).isEmpty());
+    assertTrue("AbstractSerializable should not be reported on",
+        problems.getProblemsForType(
+            to.getType("AbstractSerializable")).isEmpty());
+    assertTrue("PureAbstractSerializable should not be reported on",
+        problems.getProblemsForType(
+            to.getType("PureAbstractSerializable")).isEmpty());
+    assertTrue("PureAbstractClass should not be reported on",
+        problems.getProblemsForType(
+            to.getType("PureAbstractClass")).isEmpty());
+    assertFalse("ConcreteBadCtor should not be a serializable type",
+        problems.getProblemsForType(to.getType("ConcreteBadCtor")).isEmpty());
+    assertFalse("ConcreteNonSerializable should not be a serializable type",
+        problems.getProblemsForType(
+            to.getType("ConcreteNonSerializable")).isEmpty());
+    assertTrue("ConcreteSerializable should be a serializable type",
+        problems.getProblemsForType(to.getType("ConcreteSerializable")).isEmpty());
+    assertTrue("SubSerializable should be a serializable type",
+        problems.getProblemsForType(to.getType("SubSerializable")).isEmpty());
+
+    problems = new ProblemReport();
+    assertFalse("PureAbstractClass should have no possible concrete implementation",
+        stob.checkTypeInstantiable(logger, to.getType("PureAbstractClass"), null,
+            problems));
+    assertTrue("PureAbstractClass should have a problem entry as the tested class",
+        null != problems.getProblemsForType(to.getType("PureAbstractClass")));
+
+    problems = new ProblemReport();
+    assertFalse("PureAbstractSerializable should have no possible concrete implementation",
+        stob.checkTypeInstantiable(logger, to.getType("PureAbstractSerializable"), null,
+            problems));
+    assertFalse("PureAbstractSerializable should have a problem entry",
+        problems.getProblemsForType(to.getType("PureAbstractSerializable")).isEmpty());
+    assertTrue("PureAbstractClassTwo should not have a problem entry as the middle class",
+        problems.getProblemsForType(to.getType("PureAbstractClassTwo")).isEmpty());
+    assertTrue("PureAbstractClassTwo should not have an auxiliary entry as the middle class",
+        problems.getAuxiliaryMessagesForType(to.getType("PureAbstractClassTwo")).
+        isEmpty());
+    assertTrue("PureAbstractClass should not have a problem entry as the child class",
+        problems.getProblemsForType(to.getType("PureAbstractClass")).isEmpty());
+    assertTrue("PureAbstractClass should not have an auxiliary entry as the child class",
+        problems.getAuxiliaryMessagesForType(to.getType("PureAbstractClass")).
+        isEmpty());
+  }
+
+  /**
    * Tests that adding a raw collection as a root type pulls in the world.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1715,7 +1866,7 @@
   /**
    * Tests that raw type with type parameters that are instantiable are
    * themselves instantiable.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1761,7 +1912,7 @@
   /**
    * Tests that a type paramter that occurs within its bounds will not result in
    * infinite recursion.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1868,7 +2019,7 @@
 
   /**
    * Tests subtypes that introduce new instantiable type parameters.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1921,7 +2072,7 @@
   /**
    * Tests subtypes that introduce new uninstantiable type parameters as
    * compared to an implemented interface, where the root type is the interface.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -1990,7 +2141,7 @@
 
   /**
    * Tests subtypes that introduce new uninstantiable type parameters.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -2085,7 +2236,7 @@
 
   /**
    * Miscellaneous direct tests of {@link TypeConstrainer}.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -2209,7 +2360,7 @@
 
   /**
    * Tests root types that have type parameters.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */
@@ -2262,7 +2413,7 @@
 
   /**
    * Tests root types that <em>are</em> type parameters.
-   * 
+   *
    * @throws UnableToCompleteException
    * @throws NotFoundException
    */