This commit addresses issues 1129, 1161, and 1163 and it includes some general 
cleanup.

Issue 1129:
The fix is that if we are in a manually serialized type that uses Object or Object[]
we will consider all serializable subtypes of Object.  Without being able to deal
with arrays dynamically and without giving additional information to CFS this is the
best that we can do.  We should be able to handle this correctly in RPC v2.

Issue 1161: 
We only checked for native methods if the class was automatically serializable. 
Since all of our JRE classes with native methods use custom field serializers we
never saw the native method warning.  When external developers added auto
serializable types to the JRE they did get warnings.  This patch will skip the check
for native methods if the type is part of our JRE emulation.  We assume that all 
types whose package names begin with "java." are part of our JRE.    This does not 
account for javax and org.* packages which are part of the real JRE.

Issue 1163:
There were a few problems with this code.  First, when generating a log message we
did not store whether or not it was a warning or an error.  If the type was seen
multiple times, it was possible that what was previously a warning would be treated
as an error.

Second, abstract classes that did not have any instantiable subtypes would be
considered an error.

General cleanup:
Removed several warnings about assignment to parameters and member 
visibility causing synthetic access methods to be generated.  Updated the test code 
to remove work arounds for TypeSerializer exceptions which were no longer necessary.

Based on scottb's review feedback, I renamed some of the test classes to clarify 
how they are used.  Fixed a couple of latent bugs where we were forcing abstract classes 
to be default instantiable.  I moved the async interface validation code into the 
ProxyCreator class since it is not really appropriate for STOB.  Lastly, updated 
the documentation for STOB to give more background on how it works.

Review by: scottb



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@1228 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
index 12e995f..8f6684e 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/CustomFieldSerializerValidator.java
@@ -67,7 +67,7 @@
           serializee.getQualifiedSourceName()}));
     }
 
-    if (!serializee.isDefaultInstantiable()) {
+    if (!serializee.isAbstract() && !serializee.isDefaultInstantiable()) {
       JMethod instantiate = serializer.findMethod("instantiate",
           new JType[] {streamReaderClass});
       if (!isValidCustomFieldSerializerMethod(instantiate, serializee)) {
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 e2f11c0..436d956 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/ProxyCreator.java
@@ -27,6 +27,7 @@
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JPrimitiveType;
 import com.google.gwt.core.ext.typeinfo.JType;
+import com.google.gwt.core.ext.typeinfo.TypeOracle;
 import com.google.gwt.dev.generator.NameFactory;
 import com.google.gwt.user.client.ResponseTextHandler;
 import com.google.gwt.user.client.rpc.AsyncCallback;
@@ -94,8 +95,18 @@
       return getProxyQualifiedName();
     }
 
+    TypeOracle typeOracle = context.getTypeOracle();
+    
+    // Make sure that the async and synchronous versions of the RemoteService
+    // agree with one another
+    //
+    RemoteServiceAsyncValidator rsav = new RemoteServiceAsyncValidator(logger,
+        typeOracle);
+    rsav.validateRemoteServiceAsync(logger, serviceIntf);
+
+    // Determine the set of serializable types
     SerializableTypeOracleBuilder stob = new SerializableTypeOracleBuilder(
-        logger, context.getTypeOracle());
+        logger, typeOracle);
     SerializableTypeOracle sto = stob.build(context.getPropertyOracle(),
         serviceIntf);
 
@@ -119,8 +130,8 @@
 
   /*
    * Given a type emit an expression for calling the correct
-   * SerializationStreamReader method which reads the corresponding 
-   * instance out of the stream.
+   * SerializationStreamReader method which reads the corresponding instance out
+   * of the stream.
    */
   protected final void generateDecodeCall(SourceWriter w, JType type) {
     w.print("streamReader.");
@@ -278,10 +289,12 @@
           w.println("}");
         }
         w.outdent();
-        w.println("} catch (" + SerializationException.class.getName() + " e) {");
+        w.println("} catch (" + SerializationException.class.getName()
+            + " e) {");
         w.indent();
         {
-          w.println("caught = new " + IncompatibleRemoteServiceException.class.getName() + "();");
+          w.println("caught = new "
+              + IncompatibleRemoteServiceException.class.getName() + "();");
         }
         w.outdent();
         w.println("} catch (Throwable e) {");
diff --git a/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java b/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
index bb46011..29ce7d3 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/RemoteServiceAsyncValidator.java
@@ -86,9 +86,15 @@
   private final JClassType asyncCallbackClass;
   private final TypeOracle typeOracle;
 
-  RemoteServiceAsyncValidator(TypeOracle typeOracle) throws NotFoundException {
+  RemoteServiceAsyncValidator(TreeLogger logger, TypeOracle typeOracle)
+      throws UnableToCompleteException {
     this.typeOracle = typeOracle;
-    asyncCallbackClass = typeOracle.getType(AsyncCallback.class.getName());
+    try {
+      asyncCallbackClass = typeOracle.getType(AsyncCallback.class.getName());
+    } catch (NotFoundException e) {
+      logger.log(TreeLogger.ERROR, null, e);
+      throw new UnableToCompleteException();
+    }
   }
 
   /**
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 fa01c3e..b6d404f 100644
--- a/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
+++ b/user/src/com/google/gwt/user/rebind/rpc/SerializableTypeOracleBuilder.java
@@ -19,10 +19,12 @@
 import com.google.gwt.core.ext.PropertyOracle;
 import com.google.gwt.core.ext.TreeLogger;
 import com.google.gwt.core.ext.UnableToCompleteException;
+import com.google.gwt.core.ext.TreeLogger.Type;
 import com.google.gwt.core.ext.typeinfo.JArrayType;
 import com.google.gwt.core.ext.typeinfo.JClassType;
 import com.google.gwt.core.ext.typeinfo.JField;
 import com.google.gwt.core.ext.typeinfo.JMethod;
+import com.google.gwt.core.ext.typeinfo.JPackage;
 import com.google.gwt.core.ext.typeinfo.JParameter;
 import com.google.gwt.core.ext.typeinfo.JParameterizedType;
 import com.google.gwt.core.ext.typeinfo.JType;
@@ -51,82 +53,110 @@
  * Builds a {@link SerializableTypeOracle} for a given
  * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
  * 
- * <p/> {@link java.lang.Object Object} is never serializable.
+ * <h4>Background</h4>
+ * </p>
+ * There are two goals for this builder. First, discover the set serializable
+ * types that can be exchanged between client and server code over a given
+ * {@link com.google.gwt.user.client.rpc.RemoteService RemoteService} interface.
+ * Second, to make sure that all types which qualify for serializability
+ * actually adhere to the constraints for the particular type of serializability
+ * which applies to them.
  * 
- * <p/>A type is serializable if either of the following apply:
+ * </p>
+ * This builder starts from the set of methods that are declared or inherited by
+ * the RemoteService interface. It then traverses the type hierarchy of each of
+ * the types in these method signatures in order to discover additional types
+ * which it might need to include. For the purposes of this explanation we
+ * define a root type to be any type which appears in the RemoteService method
+ * signatures or the type of any non-final, instance field which is part of a
+ * type that qualifies for serialization. The builder will fail if a root is not
+ * serializable and it has no subtypes that are.
+ * 
+ * </p>
+ * A type qualifies for serialization if it is automatically or manually
+ * serializable. Automatic serialization is selected if the type is assignable
+ * to {@link IsSerializable} or {@link Serializable} or if the type is a
+ * primitive type such as int, boolean, etc. Manual serialization is selected if
+ * there exists another type with the same fully qualified name concatenated
+ * with "_CustomFieldSerializer". If a type qualifies for both manual and
+ * automatic serialization, manual serialization is preferred.
+ * 
+ * </p>
+ * If any of the checks described in the following sections fail, the build
+ * process will fail.
+ * 
+ * <h4>Root Types</h4>
  * <ul>
- * <li>It is automatically serializable</li>
- * <li>It is manually serializable</li>
+ * <li>If not parameterized and it is assignable to Map or Collection, emit a
+ * warning and check all serializable subtypes of object.</li>
+ * <li>If parameterized check all subtypes of the type arguments.</li>
+ * <li>Check all subtypes of the raw type</li>
+ * <li>If not parameterized and not assignable to Map or Collection, check the
+ * root type and all of its subtypes</li>
  * </ul>
  * 
- * <p/> A class qualifies for automatic serialization if it:
+ * <h4>Classes</h4>
  * <ul>
- * <li>Is a primitive type</li>
- * <li>Is {@link java.lang.String String}</li>
- * <li>Is assignable to {@link IsSerializable} or
- * {@link java.io.Serializable Serializable}</li>
+ * <li>Does not qualify for serialization</li>
+ * <ul>
+ * <li>If asked to check subtypes, check all subclasses; must have one
+ * serializable subtype</li>
+ * </ul>
+ * <li>Qualifies for Auto Serialization
+ * <ul>
+ * <li>If superclass qualifies for serialization check it</li>
+ * <li>Check the type of every non-final instance field</li>
+ * <li>If class is local or nested and not static ignore it</li>
+ * <li>If class is not abstract and not default instantiable fail</li>
+ * <li>If class is not part of the JRE, warn if it uses native methods</li>
+ * <li>If asked to check subtypes, check all subclasses</li>
+ * </ul>
+ * </li>
+ * <li>Qualifies for Manual Serialization
+ * <ul>
+ * <li>If superclass qualifies for serialization check it</li>
+ * <li>Check the type of every non-final instance field</li>
+ * <li>Check that the CustomFieldSerializer meets the following criteria:
+ * <ul>
+ * <li>A deserialize method whose signature is: 'public static void
+ * deserialize({@link SerializationStreamReader}, &lt;T&gt; instance)'</li>
+ * <li>A serialize method whose signature is: 'public static void serialize({@link SerializationStreamWriter},
+ * &lt;T&gt; instance)'</li>
+ * <li>It the class is not abstract, not default instantiable the custom field
+ * serializer must implement an instantiate method whose signature is: 'public
+ * static &lt;T&gt; instantiate(SerializationStreamReader)'</li>
+ * </ul>
+ * </ul>
+ * </li>
  * </ul>
  * 
- * <p/> It is an error if any automatically serializable class:
+ * <h4>Arrays</h4>
  * <ul>
- * <li>Is not default instantiable</li>
- * <li>Has a non-static, non-transient, non-final field whose type is not
- * serializable</li>
- * </ul>
- * 
- * <p/> A class qualifies for manual serialization if:
- * <ul>
- * <li>There is another class whose name is: className +
- * "_CustomFieldSerializer"</li>
- * </ul>
- * 
- * <p/> It is an error if any manually serializable class:
- * <ul>
- * <li>It does not implement a deserialize method whose signature is: 'public
- * static void deserialize({@link SerializationStreamReader}, &lt;T&gt;
- * instance)'</li>
- * <li>It does not implement a serializer method whose signature is: 'public
- * static void serialize({@link SerializationStreamWriter}, &lt;T&gt;
- * instance)'</li>
- * <li>It is not default instantiable and the custom field serializer does not
- * implement an instantiate method whose signature is: 'public static &lt;T&gt;
- * instantiate(SerializationStreamReader)'</li>
- * </ul>
- * 
- * <p/> It is a warning if any serializable type:
- * <ul>
- * <li>Has final fields</li>
- * <li>Has native methods</li>
- * <li>Is assignable to {@link Collection} or {@link Map} but it is not a
- * parameterized type</li>
- * <li>Is automatically serializable and one of its subtypes is not;
- * this warning can be treated as an error if the
- * gwt.allowUnserializableSubtypesOfAutoSerializableTypes property is set to
- * <code>false</code></li>
+ * <li>Check the leaf type of the array; it must be serializable or have a
+ * subtype that is.</li>
+ * <li>All covariant array types are included.</li>
  * </ul>
  */
 public class SerializableTypeOracleBuilder {
   /**
-   * Represents the state of a type while we are determining the set of
-   * serializable types.
-   */
-  static final class TypeState {
-    private final String state;
-
-    protected TypeState(String state) {
-      this.state = state;
-    }
-
-    public String toString() {
-      return state;
-    }
-  }
-
-  /**
    * Represents additional information about a type with regards to its
    * serializability.
    */
-  private class MetaTypeInfo {
+  private static class MetaTypeInfo {
+    /**
+     * An issue that prevents a type from being serializable.
+     */
+    private static class SerializationIssue {
+      final String issueMessage;
+
+      final TreeLogger.Type issueType;
+
+      SerializationIssue(Type issueType, String issueMessage) {
+        this.issueType = issueType;
+        this.issueMessage = issueMessage;
+      }
+    }
+
     /**
      * <code>true</code> if the type is assignable to {@link IsSerializable}
      * or {@link java.io.Serializable Serializable}.
@@ -145,11 +175,6 @@
     private boolean checkedSubtypes;
 
     /**
-     * List of serialization failures.
-     */
-    private Set /* <String> */failures;
-
-    /**
      * Custom field serializer or <code>null</code> if there isn't one.
      */
     private JClassType manualSerializer;
@@ -161,6 +186,12 @@
     private boolean serializable;
 
     /**
+     * List of serialization warnings or errors that prevent this type from
+     * being serializable.
+     */
+    private Set /* <SerializationIssue> */serializationIssues;
+
+    /**
      * The state that this type is currently in.
      */
     private TypeState state = SerializableTypeOracleBuilder.NOT_CHECKED;
@@ -170,24 +201,26 @@
      */
     private final JType type;
 
-    public MetaTypeInfo(JType type) {
+    MetaTypeInfo(JType type, boolean autoSerializable,
+        JClassType manualSerializer) {
       this.type = type;
-
-      JClassType classOrInterface = type.isClassOrInterface();
-      if (classOrInterface != null) {
-        autoSerializable = classOrInterface.isAssignableTo(isSerializableClass)
-            || classOrInterface.isAssignableTo(serializableClass);
-        manualSerializer = findCustomFieldSerializer(typeOracle,
-            classOrInterface);
-      }
+      this.autoSerializable = autoSerializable;
+      this.manualSerializer = manualSerializer;
     }
 
-    public void addFailure(String message) {
-      if (failures == null) {
-        failures = new TreeSet/* <String> */();
+    public void addSerializationIssue(TreeLogger.Type issueType,
+        String issueMessage) {
+      if (serializationIssues == null) {
+        serializationIssues = new TreeSet/* <SerializationIssue> */();
       }
 
-      failures.add(message);
+      serializationIssues.add(new SerializationIssue(issueType, issueMessage));
+    }
+
+    public void clearSerializationIssues() {
+      if (serializationIssues != null) {
+        serializationIssues.clear();
+      }
     }
 
     public boolean getCheckedInManualContext() {
@@ -198,14 +231,14 @@
       return checkedSubtypes;
     }
 
-    public Set /* <String> */getFailures() {
-      return failures;
-    }
-
     public JClassType getManualSerializer() {
       return manualSerializer;
     }
 
+    public Set /* <SerializationIssue> */getSerializationIssues() {
+      return serializationIssues;
+    }
+
     public TypeState getState() {
       return state;
     }
@@ -221,7 +254,7 @@
         return true;
       }
 
-      return serializable && failures == null;
+      return serializable;
     }
 
     /**
@@ -294,26 +327,40 @@
   }
 
   /**
+   * Represents the state of a type while we are determining the set of
+   * serializable types.
+   */
+  private static final class TypeState {
+    private final String state;
+
+    protected TypeState(String state) {
+      this.state = state;
+    }
+
+    public String toString() {
+      return state;
+    }
+  }
+
+  /**
    * A serializability problem was discovered with the type.
    */
-  private static final TypeState CHECK_FAILED = new TypeState("Check failed");
+  static final TypeState CHECK_FAILED = new TypeState("Check failed");
 
   /**
    * The serializability of a type is being checked.
    */
-  private static final TypeState CHECK_IN_PROGRESS = new TypeState(
-      "Check in progress");
+  static final TypeState CHECK_IN_PROGRESS = new TypeState("Check in progress");
 
   /**
    * The serializability of a type has been determined and there were no errors.
    */
-  private static final TypeState CHECK_SUCCEEDED = new TypeState(
-      "Check succeeded");
+  static final TypeState CHECK_SUCCEEDED = new TypeState("Check succeeded");
 
   /**
    * The serializability of a type has not been checked.
    */
-  private static final TypeState NOT_CHECKED = new TypeState("Not checked");
+  static final TypeState NOT_CHECKED = new TypeState("Not checked");
 
   /**
    * Finds the custom field serializer for a given type.
@@ -347,20 +394,22 @@
     assert (rank > 0);
 
     JArrayType array = null;
+    JType currentComponent = component;
     for (int i = 0; i < rank; ++i) {
-      array = typeOracle.getArrayType(component);
-      component = array;
+      array = typeOracle.getArrayType(currentComponent);
+      currentComponent = array;
     }
 
     return array;
   }
 
   private static void logSerializableTypes(TreeLogger logger, JType[] types) {
-    logger = logger.branch(TreeLogger.DEBUG, "Identified " + types.length
-        + " serializable type" + ((types.length == 1) ? "" : "s"), null);
+    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG, "Identified "
+        + types.length + " serializable type"
+        + ((types.length == 1) ? "" : "s"), null);
 
     for (int i = 0; i < types.length; ++i) {
-      logger.branch(TreeLogger.DEBUG,
+      localLogger.branch(TreeLogger.DEBUG,
           types[i].getParameterizedQualifiedSourceName(), null);
     }
   }
@@ -377,11 +426,6 @@
   private final JClassType collectionClass;
 
   /**
-   * A stack of types whose fields we are currently checking.
-   */
-  private final Stack /* <JType> */contexts = new Stack();
-
-  /**
    * Cache of the {@link JClassType} for {@link IsSerializable}.
    */
   private final JClassType isSerializableClass;
@@ -391,8 +435,6 @@
    */
   private final JClassType mapClass;
 
-  private final RemoteServiceAsyncValidator remoteServiceAsyncValidator;
-
   private final TreeLogger rootLogger;
 
   /**
@@ -425,6 +467,11 @@
   private final TypeOracle typeOracle;
 
   /**
+   * A stack of types whose fields we are currently checking.
+   */
+  private final Stack /* <JType> */typesBeingAnalyzed = new Stack();
+
+  /**
    * Map of {@link JType} to {@link MetaTypeInfo}.
    */
   private final Map /* <JType, MetaTypeInfo> */typeToMetaTypeInfo = new HashMap();
@@ -464,8 +511,6 @@
       // IncompatibleRemoteServiceException is always serializable
       MetaTypeInfo incompatibleRemoteServiceExceptionMti = getMetaTypeInfo(typeOracle.getType(IncompatibleRemoteServiceException.class.getName()));
       incompatibleRemoteServiceExceptionMti.setSerializable(true);
-
-      remoteServiceAsyncValidator = new RemoteServiceAsyncValidator(typeOracle);
     } catch (NotFoundException e) {
       rootLogger.log(TreeLogger.ERROR, null, e);
       throw new UnableToCompleteException();
@@ -476,6 +521,14 @@
    * Builds a {@link SerializableTypeOracle} for a give
    * {@link com.google.gwt.user.client.rpc.RemoteService} interface.
    * 
+   * @param propertyOracle property oracle used for initializing properties
+   * @param remoteService
+   *          {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}
+   *          interface to build the oracle for
+   * @return a {@link SerializableTypeOracle} for the specified
+   *         {@link com.google.gwt.user.client.rpc.RemoteService RemoteService}
+   *         interface
+   * 
    * @throws UnableToCompleteException if the the remote service is considered
    *           invalid due to serialization problem or a missing or ill formed
    *           remote service asynchronous interface
@@ -485,9 +538,6 @@
 
     initializeProperties(rootLogger, propertyOracle);
 
-    remoteServiceAsyncValidator.validateRemoteServiceAsync(rootLogger,
-        remoteService);
-
     TreeLogger logger = rootLogger.branch(TreeLogger.DEBUG, "Analyzing '"
         + remoteService.getParameterizedQualifiedSourceName()
         + "' for serializable types", null);
@@ -504,6 +554,7 @@
     while (iterTypes.hasNext()) {
       MetaTypeInfo mti = (MetaTypeInfo) iterTypes.next();
       JType type = mti.getType();
+
       if (mti.isSerializable() && type.isInterface() == null) {
         serializableTypesList.add(type);
       }
@@ -526,6 +577,28 @@
   }
 
   /**
+   * Consider any subtype of java.lang.Object which qualifies for serialization.
+   * 
+   * @param localLogger
+   */
+  private void checkAllSubtypesOfObject(TreeLogger localLogger) {
+    /*
+     * This will pull in the world and the set of serializable types will be
+     * larger than it needs to be. We exclude types that do not qualify for
+     * serialization to avoid generating false errors due to types that do not
+     * qualify for serialization and have no serializable subtypes.
+     */
+    JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
+    for (int i = 0; i < allTypes.length; ++i) {
+      JClassType cls = allTypes[i];
+      MetaTypeInfo mti = getMetaTypeInfo(cls);
+      if (mti.qualifiesForSerialization()) {
+        checkType(localLogger, cls, true);
+      }
+    }
+  }
+
+  /**
    * The component type of an array must be serializable.
    */
   private void checkArray(TreeLogger logger, JArrayType array) {
@@ -546,57 +619,50 @@
       JClassType[] subtypes = classOrInterface.getSubtypes();
       for (int i = 0; i < subtypes.length; ++i) {
         JClassType component = subtypes[i];
-        JArrayType covariantArray = getArrayType(typeOracle, array.getRank(),
-            component);
-
         MetaTypeInfo cmti = getMetaTypeInfo(component);
         if (cmti.isSerializable()) {
+          JArrayType covariantArray = getArrayType(typeOracle, array.getRank(),
+              component);
+
           logger.branch(TreeLogger.DEBUG,
               covariantArray.getParameterizedQualifiedSourceName(), null);
+
           getMetaTypeInfo(covariantArray).setSerializable(true);
         }
       }
     }
   }
 
-  /**
-   * Case 1: Type is automatically serializable a) All fields must be
-   * serializable b) All subtypes must be serializable unless we allow subtypes
-   * that are not serializable c) If inherited automatic serialization
-   * superclass must be serializable
-   * 
-   * Case 2: Type is manually serializable a) CSF must be valid b) All
-   * automatically and manually serializable fields must be serializable c) Any
-   * field that is not manually or automatically serializable is okay
-   * 
-   * Case 3: Type is neither automatically or manually serializable a) If type
-   * has at least one automatically or manually serializable subtype then we are
-   * okay. b) If type has no serializable subtypes then: i) context is
-   * automatically serializable => error ii) context is manually serializable =>
-   * warning iii) context is neither manually nor automatically serializable =>
-   * warning.
-   */
   private void checkClassOrInterface(TreeLogger logger, JClassType type,
-      boolean validateSubtypes) {
+      boolean checkSubtypes) {
     if (type == stringClass) {
       // we know that it is serializable
       return;
     }
 
+    MetaTypeInfo mti = getMetaTypeInfo(type);
     if (type == typeOracle.getJavaLangObject()) {
-      // Object is never serializable
-      setUnserializableAndLog(
-          logger,
-          inManualSerializationContext() ? TreeLogger.WARN : TreeLogger.ERROR,
-          "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
-          type);
+      if (inManualSerializationContext()) {
+        TreeLogger branch = logger.branch(
+            TreeLogger.WARN,
+            "Object was reached from a manually serializable type; all subtypes of Object which qualify for serialization will be considered",
+            null);
+        checkAllSubtypesOfObject(branch);
+      } else {
+        setUnserializableAndLog(
+            logger,
+            TreeLogger.ERROR,
+            "In order to produce smaller client-side code, 'Object' is not allowed; consider using a more specific type",
+            mti);
+      }
+
       return;
     }
 
     JClassType superclass = type.getSuperclass();
     if (superclass != null) {
-      MetaTypeInfo smti = getMetaTypeInfo(superclass);
-      if (smti.qualifiesForSerialization()) {
+      MetaTypeInfo superMti = getMetaTypeInfo(superclass);
+      if (superMti.qualifiesForSerialization()) {
         checkType(
             logger.branch(TreeLogger.DEBUG, "Analyzing superclass:", null),
             superclass, false);
@@ -610,60 +676,53 @@
       }
     }
 
-    MetaTypeInfo mti = getMetaTypeInfo(type);
-    if (mti.qualifiesForManualSerialization()) {
-      List failures = CustomFieldSerializerValidator.validate(
-          streamReaderClass, streamWriterClass, mti.getManualSerializer(), type);
-      if (!failures.isEmpty()) {
-        setUnserializableAndLog(logger, TreeLogger.ERROR, failures, type);
-        return;
+    if (mti.qualifiesForSerialization()) {
+      if (mti.qualifiesForManualSerialization()) {
+        List failures = CustomFieldSerializerValidator.validate(
+            streamReaderClass, streamWriterClass, mti.getManualSerializer(),
+            type);
+        if (!failures.isEmpty()) {
+          setUnserializableAndLog(logger, TreeLogger.ERROR, failures, mti);
+          return;
+        }
+      } else {
+        if (type.isLocalType()) {
+          setUnserializableAndLog(
+              logger,
+              TreeLogger.WARN,
+              "Is a local type, it will be excluded from the set of serializable types",
+              mti);
+          return;
+        }
+
+        if (type.isMemberType() && !type.isStatic()) {
+          setUnserializableAndLog(
+              logger,
+              TreeLogger.WARN,
+              "Is nested but not static, it will be excluded from the set of serializable types",
+              mti);
+          return;
+        }
+
+        if (type.isClass() != null && !type.isAbstract()
+            && !type.isDefaultInstantiable()) {
+          setUnserializableAndLog(
+              logger,
+              TreeLogger.ERROR,
+              "Was not default instantiable (it must have a zero-argument public constructor or no constructors at all)",
+              mti);
+          return;
+        }
       }
 
       mti.setSerializable(true);
 
       checkFields(logger, type);
 
-    } else if (mti.qualifiesForAutoSerialization()) {
-      if (type.isLocalType()) {
-        setUnserializableAndLog(
-            logger,
-            TreeLogger.WARN,
-            "Is a local type, it will be excluded from the set of serializable types",
-            type);
-        return;
-      }
-
-      if (type.isMemberType() && !type.isStatic()) {
-        setUnserializableAndLog(
-            logger,
-            TreeLogger.WARN,
-            "Is nested but not static, it will be excluded from the set of serializable types",
-            type);
-        return;
-      }
-
-      if (type.isClass() != null && !type.isDefaultInstantiable()) {
-        setUnserializableAndLog(
-            logger,
-            TreeLogger.ERROR,
-            "Was not default instantiable (it must have a zero-argument public constructor or no constructors at all)",
-            type);
-        return;
-      }
-
-      if (type.isAbstract() && type.getSubtypes().length == 0) {
-        // Just ignore pure, abstract classes that have no subtypes
-        return;
-      }
-
-      getMetaTypeInfo(type).setSerializable(true);
-
       checkMethods(logger, type);
-
-      checkFields(logger, type);
     }
 
-    if (validateSubtypes) {
+    if (checkSubtypes) {
       int nSubtypes = 0;
       int nSerializableSubtypes = 0;
 
@@ -710,7 +769,7 @@
                   ? TreeLogger.WARN : TreeLogger.ERROR,
               "Not all subtypes of the automatically serializable type '"
                   + type.getQualifiedSourceName()
-                  + "' are themselves automatically serializable", type);
+                  + "' are themselves automatically serializable", mti);
         }
       } else if (!mti.qualifiesForManualSerialization()
           && nSerializableSubtypes == 0) {
@@ -723,7 +782,7 @@
             "Type ''{0}'' is not assignable to IsSerializable or java.io.Serializable, it does not have a custom field serializer and it does not have any serializable subtypes",
             new String[] {type.getParameterizedQualifiedSourceName()});
         setUnserializableAndLog(logger, inManualSerializationContext()
-            ? TreeLogger.WARN : TreeLogger.ERROR, message, type);
+            ? TreeLogger.WARN : TreeLogger.ERROR, message, mti);
       }
     }
   }
@@ -735,7 +794,7 @@
       localLogger = localLogger.branch(TreeLogger.DEBUG, "Analyzing Fields:",
           null);
 
-      contexts.push(classOrInterface);
+      typesBeingAnalyzed.push(classOrInterface);
 
       for (int i = 0; i < fields.length; ++i) {
         JField field = fields[i];
@@ -759,7 +818,7 @@
         checkType(fieldLogger, fieldType, true);
       }
 
-      contexts.pop();
+      typesBeingAnalyzed.pop();
 
     } else {
       localLogger.branch(TreeLogger.DEBUG, "No fields to analyze", null);
@@ -782,33 +841,26 @@
                 + "' should be parameterized to help the compiler produce the smallest code size possible for your module. Since the gwt.typeArgs javadoc annotation is missing, all subtypes of Object will be analyzed for serializability even if they are not directly or indirectly used",
             null);
 
-        /*
-         * This will pull in the world and the set of serializable types will be
-         * larger than it needs to be. We exclude types that do not qualify for
-         * serialization to avoid generating false errors due to types that do
-         * not qualify for serialization and have no serializable subtypes.
-         */
-        JClassType[] allTypes = typeOracle.getJavaLangObject().getSubtypes();
-        for (int i = 0; i < allTypes.length; ++i) {
-          JClassType cls = allTypes[i];
-          MetaTypeInfo mti = getMetaTypeInfo(cls);
-          if (mti.qualifiesForSerialization()) {
-            checkType(localLogger, cls, true);
-          }
-        }
+        checkAllSubtypesOfObject(localLogger);
       }
     }
   }
 
   private void checkMethods(TreeLogger logger, JClassType classOrInterface) {
+    if (isDefinedInJREEmulation(classOrInterface)) {
+      // JRE emulation classes are never used on the server; skip the check
+      return;
+    }
+
     JMethod[] methods = classOrInterface.getMethods();
     for (int i = 0; i < methods.length; ++i) {
-      if (methods[i].isNative()) {
+      JMethod method = methods[i];
+      if (method.isNative()) {
         logger.branch(
             TreeLogger.WARN,
             MessageFormat.format(
                 "Method ''{0}'' is native, calling this method in server side code will result in an UnsatisfiedLinkError",
-                new String[] {methods[i].toString()}), null);
+                new String[] {method.toString()}), null);
       }
     }
   }
@@ -822,40 +874,45 @@
       return;
     }
 
-    logger = logger.branch(TreeLogger.DEBUG,
+    TreeLogger localLogger = logger.branch(TreeLogger.DEBUG,
         type.getParameterizedQualifiedSourceName(), null);
 
     MetaTypeInfo mti = getMetaTypeInfo(type);
     TypeState state = mti.getState();
 
     if (state == SerializableTypeOracleBuilder.CHECK_FAILED) {
-      logReasonsForUnserializability(logger, type);
+      logReasonsForUnserializability(localLogger, mti);
       return;
     } else if (state == SerializableTypeOracleBuilder.CHECK_IN_PROGRESS) {
-      logger.branch(TreeLogger.DEBUG, "'"
+      localLogger.branch(TreeLogger.DEBUG, "'"
           + type.getParameterizedQualifiedSourceName()
           + "' is being analyzed; skipping", null);
       return;
     } else if (state == SerializableTypeOracleBuilder.CHECK_SUCCEEDED) {
       if (!mti.needToRecheck(checkSubtypes, inManualSerializationContext())) {
-        logger.branch(TreeLogger.DEBUG, "Type has already been analyzed", null);
+        localLogger.branch(TreeLogger.DEBUG, "Type has already been analyzed",
+            null);
         return;
       }
+
+      mti.clearSerializationIssues();
     }
 
     mti.setState(SerializableTypeOracleBuilder.CHECK_IN_PROGRESS);
 
     if (type.isParameterized() != null) {
       JParameterizedType parameterized = type.isParameterized();
-      checkType(logger.branch(TreeLogger.DEBUG, "Analyzing raw type", null),
+      checkType(
+          localLogger.branch(TreeLogger.DEBUG, "Analyzing raw type", null),
           parameterized.getRawType(), true);
 
-      checkTypes(logger.branch(TreeLogger.DEBUG, "Analyzing type args", null),
-          parameterized.getTypeArgs());
+      checkTypes(localLogger.branch(TreeLogger.DEBUG, "Analyzing type args",
+          null), parameterized.getTypeArgs());
     } else if (type.isArray() != null) {
-      checkArray(logger, type.isArray());
+      checkArray(localLogger, type.isArray());
     } else if (type.isClassOrInterface() != null) {
-      checkClassOrInterface(logger, type.isClassOrInterface(), checkSubtypes);
+      checkClassOrInterface(localLogger, type.isClassOrInterface(),
+          checkSubtypes);
     }
 
     if (mti.getState() != SerializableTypeOracleBuilder.CHECK_FAILED) {
@@ -875,7 +932,17 @@
   private MetaTypeInfo getMetaTypeInfo(JType type) {
     MetaTypeInfo mti = (MetaTypeInfo) typeToMetaTypeInfo.get(type);
     if (mti == null) {
-      mti = new MetaTypeInfo(type);
+      boolean autoSerializable = false;
+      JClassType manualSerializer = null;
+      JClassType classOrInterface = type.isClassOrInterface();
+      if (classOrInterface != null) {
+        autoSerializable = classOrInterface.isAssignableTo(isSerializableClass)
+            || classOrInterface.isAssignableTo(serializableClass);
+        manualSerializer = findCustomFieldSerializer(typeOracle,
+            classOrInterface);
+      }
+
+      mti = new MetaTypeInfo(type, autoSerializable, manualSerializer);
       typeToMetaTypeInfo.put(type, mti);
     }
 
@@ -919,11 +986,11 @@
    *         current type uses manual serialization
    */
   private boolean inManualSerializationContext() {
-    if (contexts.isEmpty()) {
+    if (typesBeingAnalyzed.isEmpty()) {
       return false;
     }
 
-    JType parent = (JType) contexts.peek();
+    JType parent = (JType) typesBeingAnalyzed.peek();
     JClassType parentClass = parent.isClassOrInterface();
 
     if (parentClass != null) {
@@ -933,67 +1000,59 @@
     return false;
   }
 
-  private void logReasonsForUnserializability(TreeLogger logger, JType type) {
-    TreeLogger.Type logType;
-    MetaTypeInfo mti = getMetaTypeInfo(type);
-
-    boolean autoSerializable = mti.qualifiesForAutoSerialization();
-    if (inManualSerializationContext() && !autoSerializable) {
-      logType = TreeLogger.WARN;
-    } else {
-      logType = TreeLogger.ERROR;
-
-      if (autoSerializable) {
-        JClassType classOrInterface = type.isClassOrInterface();
-
-        if (classOrInterface.isLocalType()
-            || (classOrInterface.isMemberType() && !classOrInterface.isStatic())) {
-          logType = TreeLogger.WARN;
-        }
-      }
+  /**
+   * Returns <code>true</code> if the type is defined by the JRE.
+   */
+  private boolean isDefinedInJREEmulation(JClassType type) {
+    JPackage pkg = type.getPackage();
+    if (pkg != null) {
+      return pkg.getName().startsWith("java.");
     }
 
-    if (logType == TreeLogger.ERROR) {
-      validationFailed = true;
-    }
+    return false;
+  }
 
-    Set /* <String> */reasons = mti.getFailures();
-    Iterator iter = reasons.iterator();
+  private void logReasonsForUnserializability(TreeLogger logger,
+      MetaTypeInfo mti) {
+    Set /* <SerializationIssue> */serializationIssues = mti.getSerializationIssues();
+    Iterator iter = serializationIssues.iterator();
     while (iter.hasNext()) {
-      logger.branch(logType, (String) iter.next(), null);
+      MetaTypeInfo.SerializationIssue serializationIssue = (MetaTypeInfo.SerializationIssue) iter.next();
+      logger.branch(serializationIssue.issueType,
+          serializationIssue.issueMessage, null);
     }
   }
 
   private void setUnserializableAndLog(TreeLogger logger,
-      TreeLogger.Type logType, List failures, JClassType type) {
+      TreeLogger.Type logType, List failures, MetaTypeInfo mti) {
     Iterator iter = failures.iterator();
     while (iter.hasNext()) {
-      setUnserializableAndLog(logger, logType, (String) iter.next(), type);
+      setUnserializableAndLog(logger, logType, (String) iter.next(), mti);
     }
   }
 
   private void setUnserializableAndLog(TreeLogger logger,
-      TreeLogger.Type logType, String message, JType type) {
-    MetaTypeInfo mti = getMetaTypeInfo(type);
+      TreeLogger.Type logType, String logMessage, MetaTypeInfo mti) {
     mti.setState(SerializableTypeOracleBuilder.CHECK_FAILED);
     mti.setSerializable(false);
-    mti.addFailure(message);
+    mti.addSerializationIssue(logType, logMessage);
 
     if (logType == TreeLogger.ERROR) {
       validationFailed = true;
     }
 
-    logger.branch(logType, message, null);
+    logger.branch(logType, logMessage, null);
   }
 
   private void validateRemoteService(TreeLogger logger, JClassType remoteService) {
     JMethod[] methods = remoteService.getOverridableMethods();
 
-    logger = logger.branch(TreeLogger.DEBUG, "Analyzing methods:", null);
+    TreeLogger validationLogger = logger.branch(TreeLogger.DEBUG,
+        "Analyzing methods:", null);
 
     for (int i = 0; i < methods.length; ++i) {
       JMethod method = methods[i];
-      TreeLogger methodLogger = logger.branch(TreeLogger.DEBUG,
+      TreeLogger methodLogger = validationLogger.branch(TreeLogger.DEBUG,
           method.toString(), null);
       JType returnType = method.getReturnType();
       TreeLogger returnTypeLogger = methodLogger.branch(TreeLogger.DEBUG,
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTest.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTest.java
index 63aed74..26c1abe 100644
--- a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTest.java
@@ -16,14 +16,24 @@
 package com.google.gwt.user.client.rpc;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetFactory.SerializableSubclass;
 
 /**
- * TODO: document me.
+ * Tests the following scenarios:
+ * <ul>
+ * <li>Manually serializable types use their custom field serializer</li>
+ * <li>Subtypes of manually serializable types that are not auto-serializable
+ * fail to be serialized</li>
+ * <li>Automatically serializable subtypes of manually serialized types can be
+ * serialized</li>
+ * </ul>
  */
-public class CustomFieldSerializerTest extends TypeSerializerWorkAround {
+public class CustomFieldSerializerTest extends GWTTestCase {
   private static final int TEST_DELAY = 5000;
 
+  private CustomFieldSerializerTestServiceAsync customFieldSerializerTestService;
+
   public String getModuleName() {
     return "com.google.gwt.user.RPCSuite";
   }
@@ -35,22 +45,18 @@
   public void testCustomFieldSerializabilityInheritance() {
     delayTestFinish(TEST_DELAY);
 
-    try {
-      CustomFieldSerializerTestServiceAsync service = getServiceAsync();
-      service.echo(
-          CustomFieldSerializerTestSetFactory.createUnserializableSubclass(),
-          new AsyncCallback() {
-            public void onFailure(Throwable caught) {
-              finishTest();
-            }
+    CustomFieldSerializerTestServiceAsync service = getServiceAsync();
+    service.echo(
+        CustomFieldSerializerTestSetFactory.createUnserializableSubclass(),
+        new AsyncCallback() {
+          public void onFailure(Throwable caught) {
+            finishTest();
+          }
 
-            public void onSuccess(Object result) {
-              fail("Class UnserializableSubclass should not be serializable");
-            }
-          });
-    } catch (RuntimeException ex) {
-      workAroundTypeSerializerBug(ex);
-    }
+          public void onSuccess(Object result) {
+            fail("Class UnserializableSubclass should not be serializable");
+          }
+        });
   }
 
   /**
@@ -59,27 +65,23 @@
   public void testCustomFieldSerialization() {
     delayTestFinish(TEST_DELAY);
 
-    try {
-      CustomFieldSerializerTestServiceAsync service = getServiceAsync();
-      service.echo(
-          CustomFieldSerializerTestSetFactory.createUnserializableClass(),
-          new AsyncCallback() {
-            public void onFailure(Throwable caught) {
-              fail("Class UnserializableClass should be serializable because it has a custom field serializer");
-            }
+    CustomFieldSerializerTestServiceAsync service = getServiceAsync();
+    service.echo(
+        CustomFieldSerializerTestSetFactory.createUnserializableClass(),
+        new AsyncCallback() {
+          public void onFailure(Throwable caught) {
+            fail("Class UnserializableClass should be serializable because it has a custom field serializer");
+          }
 
-            public void onSuccess(Object result) {
-              assertNotNull(result);
-              assertTrue(CustomFieldSerializerTestSetValidator.isValid((UnserializableClass) result));
-              finishTest();
-            }
-          });
-    } catch (RuntimeException ex) {
-      workAroundTypeSerializerBug(ex);
-    }
+          public void onSuccess(Object result) {
+            assertNotNull(result);
+            assertTrue(CustomFieldSerializerTestSetValidator.isValid((ManuallySerializedClass) result));
+            finishTest();
+          }
+        });
   }
 
-  /*
+  /**
    * Test that serializable subclasses of classes that have custom field
    * serializers serialize and deserialize correctly
    */
@@ -110,6 +112,4 @@
     }
     return customFieldSerializerTestService;
   }
-
-  private CustomFieldSerializerTestServiceAsync customFieldSerializerTestService;
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestService.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestService.java
index c486868..e90d71e 100644
--- a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestService.java
@@ -18,10 +18,12 @@
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetFactory.SerializableSubclass;
 
 /**
- * TODO: document me.
+ * Service interface used by the
+ * {@link com.google.gwt.user.client.rpc.CustomFieldSerializerTest CustomFieldSerializerTest}
+ * unit test.
  */
 public interface CustomFieldSerializerTestService extends RemoteService {
-  SerializableSubclass echo(SerializableSubclass serializableClass);
+  ManuallySerializedClass echo(ManuallySerializedClass manuallySerializableClass);
 
-  UnserializableClass echo(UnserializableClass unserializableClass);
+  SerializableSubclass echo(SerializableSubclass serializableClass);
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestServiceAsync.java
index fa0b92d..9260623 100644
--- a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestServiceAsync.java
@@ -18,10 +18,13 @@
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetFactory.SerializableSubclass;
 
 /**
- * TODO: document me.
+ * Asynchronous service interface used by the
+ * {@link com.google.gwt.user.client.rpc.CustomFieldSerializerTest CustomFieldSerializerTest}
+ * unit test.
  */
 public interface CustomFieldSerializerTestServiceAsync {
-  void echo(SerializableSubclass serializableClass, AsyncCallback callback);
+  void echo(ManuallySerializedClass manuallySerializableClass,
+      AsyncCallback callback);
 
-  void echo(UnserializableClass unserializableClass, AsyncCallback callback);
+  void echo(SerializableSubclass serializableClass, AsyncCallback callback);
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetFactory.java
index 12613fe..a075066 100644
--- a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetFactory.java
@@ -16,15 +16,20 @@
 package com.google.gwt.user.client.rpc;
 
 /**
- * TODO: document me.
+ * Generated test data for the
+ * {@link com.google.gwt.user.client.rpc.CustomFieldSerializerTest CustomFieldSerializerTest}
+ * unit test.
  */
 public class CustomFieldSerializerTestSetFactory {
 
   /**
-   * TODO: document me.
+   * Used to test an automatically serializable subclass of a manually
+   * serializable subtype
    */
-  public static class SerializableSubclass extends UnserializableClass
+  public static class SerializableSubclass extends ManuallySerializedClass
       implements IsSerializable {
+    private int d = 4;
+
     public int getD() {
       return d;
     }
@@ -32,22 +37,21 @@
     public void setD(int d) {
       this.d = d;
     }
-
-    private int d = 4;
   }
 
   /**
-   * TODO: document me.
+   * Used to test a subclass of a manually serializable type that is not
+   * automatically or manually serializable.
    */
-  public static class UnserializableSubclass extends UnserializableClass {
+  public static class UnserializableSubclass extends ManuallySerializedClass {
   }
 
   public static SerializableSubclass createSerializableSubclass() {
     return new SerializableSubclass();
   }
 
-  public static UnserializableClass createUnserializableClass() {
-    return new UnserializableClass();
+  public static ManuallySerializedClass createUnserializableClass() {
+    return new ManuallySerializedClass();
   }
 
   public static UnserializableSubclass createUnserializableSubclass() {
diff --git a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetValidator.java b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetValidator.java
index 21b0c3f..0d16946 100644
--- a/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetValidator.java
+++ b/user/test/com/google/gwt/user/client/rpc/CustomFieldSerializerTestSetValidator.java
@@ -18,9 +18,22 @@
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetFactory.SerializableSubclass;
 
 /**
- * TODO: document me.
+ * Data validator used by the
+ * {@link com.google.gwt.user.client.rpc.CustomFieldSerializerTest CustomFieldSerializerTest}
+ * unit test.
  */
 public class CustomFieldSerializerTestSetValidator {
+  public static boolean isValid(ManuallySerializedClass manuallySerializedClass) {
+    if (manuallySerializedClass == null) {
+      return false;
+    }
+
+    return manuallySerializedClass.getA() == 4
+        && manuallySerializedClass.getB() == 5
+        && manuallySerializedClass.getC() == 6
+        && manuallySerializedClass.getObj().equals("bye");
+  }
+
   public static boolean isValid(SerializableSubclass serializableSubclass) {
     if (serializableSubclass == null) {
       return false;
@@ -30,16 +43,6 @@
       return false;
     }
 
-    return isValid((UnserializableClass) serializableSubclass);
-  }
-
-  public static boolean isValid(UnserializableClass unserializableClass) {
-    if (unserializableClass == null) {
-      return false;
-    }
-
-    return unserializableClass.getA() == 4 && unserializableClass.getB() == 5
-        && unserializableClass.getC() == 6
-        && unserializableClass.getObj().equals("bye");
+    return isValid((ManuallySerializedClass) serializableSubclass);
   }
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
index 8f180af..4a87a10 100644
--- a/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
+++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTest.java
@@ -16,6 +16,7 @@
 package com.google.gwt.user.client.rpc;
 
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.AnonymousClassInterface;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.Circle;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClass;
@@ -25,7 +26,7 @@
 /**
  * TODO: document me.
  */
-public class InheritanceTest extends TypeSerializerWorkAround {
+public class InheritanceTest extends GWTTestCase {
   // private static final int TEST_DELAY = Integer.MAX_VALUE;
   private static final int TEST_DELAY = 5000;
 
@@ -42,23 +43,19 @@
     delayTestFinish(TEST_DELAY);
 
     InheritanceTestServiceAsync service = getServiceAsync();
-    try {
-      service.echo(new AnonymousClassInterface() {
-        public void foo() {
-          // TODO Auto-generated method stub
-        }
-      }, new AsyncCallback() {
-        public void onFailure(Throwable caught) {
-          finishTest();
-        }
+    service.echo(new AnonymousClassInterface() {
+      public void foo() {
+        // purposely empty
+      }
+    }, new AsyncCallback() {
+      public void onFailure(Throwable caught) {
+        finishTest();
+      }
 
-        public void onSuccess(Object result) {
-          fail("Anonymous inner classes should not be serializable");
-        }
-      });
-    } catch (RuntimeException ex) {
-      workAroundTypeSerializerBug(ex);
-    }
+      public void onSuccess(Object result) {
+        fail("Anonymous inner classes should not be serializable");
+      }
+    });
   }
 
   /**
@@ -70,23 +67,18 @@
   public void testFieldShadowing() {
     delayTestFinish(TEST_DELAY);
 
-    try {
-      InheritanceTestServiceAsync service = getServiceAsync();
-      service.echo(InheritanceTestSetFactory.createCircle(),
-          new AsyncCallback() {
-            public void onFailure(Throwable caught) {
-              fail("Unexpected failure");
-            }
+    InheritanceTestServiceAsync service = getServiceAsync();
+    service.echo(InheritanceTestSetFactory.createCircle(), new AsyncCallback() {
+      public void onFailure(Throwable caught) {
+        fail("Unexpected failure");
+      }
 
-            public void onSuccess(Object result) {
-              Circle circle = (Circle) result;
-              assertNotNull(circle.getName());
-              finishTest();
-            }
-          });
-    } catch (RuntimeException ex) {
-      workAroundTypeSerializerBug(ex);
-    }
+      public void onSuccess(Object result) {
+        Circle circle = (Circle) result;
+        assertNotNull(circle.getName());
+        finishTest();
+      }
+    });
   }
 
   /**
@@ -116,20 +108,16 @@
     delayTestFinish(TEST_DELAY);
 
     InheritanceTestServiceAsync service = getServiceAsync();
-    try {
-      service.echo(InheritanceTestSetFactory.createNonStaticInnerClass(),
-          new AsyncCallback() {
-            public void onFailure(Throwable caught) {
-              finishTest();
-            }
+    service.echo(InheritanceTestSetFactory.createNonStaticInnerClass(),
+        new AsyncCallback() {
+          public void onFailure(Throwable caught) {
+            finishTest();
+          }
 
-            public void onSuccess(Object result) {
-              fail("Non-static inner classes should not be serializable");
-            }
-          });
-    } catch (RuntimeException ex) {
-      workAroundTypeSerializerBug(ex);
-    }
+          public void onSuccess(Object result) {
+            fail("Non-static inner classes should not be serializable");
+          }
+        });
   }
 
   public void testReturnOfUnserializableClassFromServer() {
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTestService.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTestService.java
index 8e4f309..dc401e1 100644
--- a/user/test/com/google/gwt/user/client/rpc/InheritanceTestService.java
+++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTestService.java
@@ -15,14 +15,18 @@
  */
 package com.google.gwt.user.client.rpc;
 
+import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.AbstractClass;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.AnonymousClassInterface;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.Circle;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.JavaSerializableClass;
+import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.MySerializableInterface;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClass;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClassWithTransientField;
 
 /**
- * TODO: document me.
+ * Service interface used by the
+ * {@link com.google.gwt.user.client.rpc.InheritanceTest InheritanceTest} unit
+ * test.
  */
 public interface InheritanceTestService extends RemoteService {
   AnonymousClassInterface echo(AnonymousClassInterface serializable);
@@ -30,11 +34,38 @@
   Circle echo(Circle circle);
 
   JavaSerializableClass echo(JavaSerializableClass javaSerializableClass);
-  
+
   SerializableClass echo(SerializableClass serializableClass);
 
   SerializableClassWithTransientField echo(
       SerializableClassWithTransientField serializableClass);
 
+  /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   * 
+   * @return
+   */
+  AbstractClass getAbstractClass();
+
+  /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   * 
+   * @return
+   */
+  MySerializableInterface getSerializableInterface1();
+
+  /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   * 
+   * @return
+   */
+  MySerializableInterface getSerializableInterface2();
+
   SerializableClass getUnserializableClass();
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTestServiceAsync.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTestServiceAsync.java
index f19010f..5630faa 100644
--- a/user/test/com/google/gwt/user/client/rpc/InheritanceTestServiceAsync.java
+++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTestServiceAsync.java
@@ -21,8 +21,11 @@
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClass;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClassWithTransientField;
 
+
 /**
- * TODO: document me.
+ * Async service interface used by the
+ * {@link com.google.gwt.user.client.rpc.InheritanceTest InheritanceTest} unit
+ * test.
  */
 public interface InheritanceTestServiceAsync {
   void echo(AnonymousClassInterface serializable, AsyncCallback callback);
@@ -36,5 +39,11 @@
   void echo(SerializableClassWithTransientField serializableClass,
       AsyncCallback callback);
 
+  void getAbstractClass(AsyncCallback callback);
+  
+  void getSerializableInterface1(AsyncCallback callback);
+  
+  void getSerializableInterface2(AsyncCallback callback);
+  
   void getUnserializableClass(AsyncCallback callback);
 }
diff --git a/user/test/com/google/gwt/user/client/rpc/InheritanceTestSetFactory.java b/user/test/com/google/gwt/user/client/rpc/InheritanceTestSetFactory.java
index 4c1b448..f775a69 100644
--- a/user/test/com/google/gwt/user/client/rpc/InheritanceTestSetFactory.java
+++ b/user/test/com/google/gwt/user/client/rpc/InheritanceTestSetFactory.java
@@ -18,11 +18,21 @@
 import java.io.Serializable;
 
 /**
- * TODO: document me.
+ * Test data factory used by the
+ * {@link com.google.gwt.user.client.rpc.InheritanceTest InheritanceTest} unit
+ * test.
  */
 public class InheritanceTestSetFactory {
 
   /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   */
+  public static class AbstractClass implements IsSerializable {
+  }
+
+  /**
    * TODO: document me.
    */
   public static interface AnonymousClassInterface extends IsSerializable {
@@ -42,7 +52,7 @@
 
   /**
    * TODO: document me.
-   */  
+   */
   public static class JavaSerializableBaseClass implements Serializable {
     private int field1 = -1;
 
@@ -78,6 +88,23 @@
   }
 
   /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   */
+  public static interface MySerializableInterface extends IsSerializable {
+  }
+
+  /**
+   * Used to test <a
+   * href="http://code.google.com/p/google-web-toolkit/issues/detail?id=1163">Issue
+   * 1163</a>.
+   */
+  public static interface MySerializableInterfaceSubtype extends
+      MySerializableInterface {
+  }
+
+  /**
    * TODO: document me.
    */
   public static class SerializableClass implements IsSerializable {
diff --git a/user/test/com/google/gwt/user/client/rpc/UnserializableClass.java b/user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass.java
similarity index 97%
rename from user/test/com/google/gwt/user/client/rpc/UnserializableClass.java
rename to user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass.java
index 69fef97..2fa58ce 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnserializableClass.java
+++ b/user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass.java
@@ -22,7 +22,15 @@
  * serializer. Once we fix this bug we can move this class into the test set
  * factory.
  */
-public class UnserializableClass {
+public class ManuallySerializedClass {
+  private int a = 1;
+
+  private int b = 2;
+
+  private int c = 3;
+
+  private Object obj = "hello";
+
   public int getA() {
     return a;
   }
@@ -54,12 +62,4 @@
   public void setObj(Object obj) {
     this.obj = obj;
   }
-
-  private int a = 1;
-
-  private int b = 2;
-
-  private int c = 3;
-
-  private Object obj = "hello";
 }
\ No newline at end of file
diff --git a/user/test/com/google/gwt/user/client/rpc/UnserializableClass_CustomFieldSerializer.java b/user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass_CustomFieldSerializer.java
similarity index 86%
rename from user/test/com/google/gwt/user/client/rpc/UnserializableClass_CustomFieldSerializer.java
rename to user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass_CustomFieldSerializer.java
index db24d49..1c9531d 100644
--- a/user/test/com/google/gwt/user/client/rpc/UnserializableClass_CustomFieldSerializer.java
+++ b/user/test/com/google/gwt/user/client/rpc/ManuallySerializedClass_CustomFieldSerializer.java
@@ -20,9 +20,9 @@
  * because of a bug where custom field serializers cannot be inner classes. Once
  * we fix this bug we can move this class into the test set factory.
  */
-public class UnserializableClass_CustomFieldSerializer {
+public class ManuallySerializedClass_CustomFieldSerializer {
   public static void deserialize(SerializationStreamReader streamReader,
-      UnserializableClass instance) throws SerializationException {
+      ManuallySerializedClass instance) throws SerializationException {
     instance.setA(streamReader.readInt());
     instance.setB(streamReader.readInt());
     instance.setC(streamReader.readInt());
@@ -30,7 +30,7 @@
   }
 
   public static void serialize(SerializationStreamWriter streamWriter,
-      UnserializableClass instance) throws SerializationException {
+      ManuallySerializedClass instance) throws SerializationException {
     streamWriter.writeInt(4);
     streamWriter.writeInt(5);
     streamWriter.writeInt(6);
diff --git a/user/test/com/google/gwt/user/client/rpc/TypeSerializerWorkAround.java b/user/test/com/google/gwt/user/client/rpc/TypeSerializerWorkAround.java
deleted file mode 100644
index bbd85dc..0000000
--- a/user/test/com/google/gwt/user/client/rpc/TypeSerializerWorkAround.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright 2007 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.client.rpc;
-
-import com.google.gwt.junit.client.GWTTestCase;
-
-/**
- * This class is the superclass for any test case that needs to work around a
- * bug where the generated, client-side type serializer throws the wrong type of
- * exception.
- */
-abstract class TypeSerializerWorkAround extends GWTTestCase {
-  void workAroundTypeSerializerBug(RuntimeException ex) {
-    if (ex.getCause() instanceof SerializationException) {
-      finishTest();
-    } else {
-      fail("Unexpected exception");
-    }
-  }
-}
diff --git a/user/test/com/google/gwt/user/server/rpc/CustomFieldSerializerTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/CustomFieldSerializerTestServiceImpl.java
index addfbf9..309702e 100644
--- a/user/test/com/google/gwt/user/server/rpc/CustomFieldSerializerTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/CustomFieldSerializerTestServiceImpl.java
@@ -17,15 +17,26 @@
 
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestService;
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetValidator;
-import com.google.gwt.user.client.rpc.UnserializableClass;
+import com.google.gwt.user.client.rpc.ManuallySerializedClass;
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTestSetFactory.SerializableSubclass;
 
 /**
- * TODO: document me.
+ * Servlet used by the
+ * {@link com.google.gwt.user.client.rpc.CustomFieldSerializerTest CustomFieldSerializerTest}
+ * unit test.
  */
 public class CustomFieldSerializerTestServiceImpl extends RemoteServiceServlet
     implements CustomFieldSerializerTestService {
 
+  public ManuallySerializedClass echo(
+      ManuallySerializedClass unserializableClass) {
+    if (!CustomFieldSerializerTestSetValidator.isValid(unserializableClass)) {
+      throw new RuntimeException();
+    }
+
+    return unserializableClass;
+  }
+
   public SerializableSubclass echo(SerializableSubclass serializableClass) {
     if (!CustomFieldSerializerTestSetValidator.isValid(serializableClass)) {
       throw new RuntimeException();
@@ -33,12 +44,4 @@
 
     return serializableClass;
   }
-
-  public UnserializableClass echo(UnserializableClass unserializableClass) {
-    if (!CustomFieldSerializerTestSetValidator.isValid(unserializableClass)) {
-      throw new RuntimeException();
-    }
-
-    return unserializableClass;
-  }
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/InheritanceTestServiceImpl.java b/user/test/com/google/gwt/user/server/rpc/InheritanceTestServiceImpl.java
index ec64347..77545da 100644
--- a/user/test/com/google/gwt/user/server/rpc/InheritanceTestServiceImpl.java
+++ b/user/test/com/google/gwt/user/server/rpc/InheritanceTestServiceImpl.java
@@ -18,6 +18,8 @@
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory;
 import com.google.gwt.user.client.rpc.InheritanceTestSetValidator;
 import com.google.gwt.user.client.rpc.InheritanceTestServiceSubtype;
+import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.AbstractClass;
+import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.MySerializableInterface;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.AnonymousClassInterface;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.Circle;
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.JavaSerializableClass;
@@ -25,7 +27,9 @@
 import com.google.gwt.user.client.rpc.InheritanceTestSetFactory.SerializableClassWithTransientField;
 
 /**
- * TODO: document me.
+ * Servlet used by the
+ * {@link com.google.gwt.user.client.rpc.InheritanceTest InheritanceTest} unit
+ * test.
  */
 public class InheritanceTestServiceImpl extends RemoteServiceServlet implements
     InheritanceTestServiceSubtype {
@@ -66,6 +70,21 @@
   public void foo() {
   }
 
+  public AbstractClass getAbstractClass() {
+    // never actually called, used in testing the RPC generator
+    return null;
+  }
+
+  public MySerializableInterface getSerializableInterface1() {
+    // never actually called, used in testing the RPC generator
+    return null;
+  }
+
+  public MySerializableInterface getSerializableInterface2() {
+    // never actually called, used in testing the RPC generator
+    return null;
+  }
+
   public SerializableClass getUnserializableClass() {
     return InheritanceTestSetFactory.createNonStaticInnerClass();
   }