Adds ObjectCodecRegistry to {Des|S}erializationContext.

* AutoCodec now delegates to the registry.
* Adds getSuperclass logic for resolving a codec.
* Small cleanups for classes that break the registry.

TODO after this change:
* Explicit CODEC definitions are no longer needed and existing ones should be cleaned up.
* POLYMORPHIC is no longer be needed and should be cleaned up.

PiperOrigin-RevId: 186351845
diff --git a/src/main/java/com/google/devtools/build/lib/actions/ArtifactOwner.java b/src/main/java/com/google/devtools/build/lib/actions/ArtifactOwner.java
index 02904d4..27b97a4 100644
--- a/src/main/java/com/google/devtools/build/lib/actions/ArtifactOwner.java
+++ b/src/main/java/com/google/devtools/build/lib/actions/ArtifactOwner.java
@@ -16,7 +16,6 @@
 import com.google.common.annotations.VisibleForTesting;
 import com.google.devtools.build.lib.cmdline.Label;
 import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
-import com.google.devtools.build.lib.skyframe.serialization.SingletonCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 
 /**
@@ -34,11 +33,10 @@
    * An {@link ArtifactOwner} that just returns null for its label. Only for use with resolved
    * source artifacts and tests.
    */
+  @AutoCodec(strategy = AutoCodec.Strategy.SINGLETON)
   class NullArtifactOwner implements ArtifactOwner {
     @VisibleForTesting public static final NullArtifactOwner INSTANCE = new NullArtifactOwner();
 
-    static final ObjectCodec<NullArtifactOwner> CODEC = SingletonCodec.of(INSTANCE, "NULL_OWNER");
-
     private NullArtifactOwner() {}
 
     @Override
diff --git a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodec.java b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodec.java
index ec61883..2554561 100644
--- a/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodec.java
+++ b/src/main/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodec.java
@@ -42,11 +42,6 @@
 public class NestedSetCodec<T> implements ObjectCodec<NestedSet<T>> {
 
   private static final EnumCodec<Order> orderCodec = new EnumCodec<>(Order.class);
-  private final ObjectCodec<T> objectCodec;
-
-  public NestedSetCodec(ObjectCodec<T> objectCodec) {
-    this.objectCodec = objectCodec;
-  }
 
   @SuppressWarnings("unchecked")
   @Override
@@ -76,7 +71,7 @@
     int nestedSetCount = codedIn.readInt32();
     Preconditions.checkState(
         nestedSetCount >= 1,
-        "Should have at least serialized one nested set, got: %d",
+        "Should have at least serialized one nested set, got: %s",
         nestedSetCount);
     Order order = orderCodec.deserialize(context, codedIn);
     Object children = null;
@@ -131,7 +126,7 @@
         childCodedOut.writeByteArrayNoTag(digest);
       } else {
         childCodedOut.writeBoolNoTag(false);
-        objectCodec.serialize(context, cast(child), childCodedOut);
+        context.serialize(cast(child), childCodedOut);
       }
     }
   }
@@ -141,7 +136,7 @@
       throws IOException, SerializationException {
     childCodedOut.writeInt32NoTag(1);
     T singleChild = cast(children);
-    objectCodec.serialize(context, singleChild, childCodedOut);
+    context.serialize(singleChild, childCodedOut);
   }
 
   private Object deserializeOneNestedSet(
@@ -157,7 +152,7 @@
     if (childCount > 1) {
       result = deserializeMultipleItemChildArray(context, digestToChild, childCodedIn, childCount);
     } else if (childCount == 1) {
-      result = objectCodec.deserialize(context, childCodedIn);
+      result = context.deserialize(childCodedIn);
     } else {
       result = NestedSet.EMPTY_CHILDREN;
     }
@@ -179,7 +174,7 @@
         children[i] =
             Preconditions.checkNotNull(digestToChild.get(digest), "Transitive nested set missing");
       } else {
-        children[i] = objectCodec.deserialize(context, childCodedIn);
+        children[i] = context.deserialize(childCodedIn);
       }
     }
     return children;
diff --git a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
index 46460b0..d455d01 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/android/DexArchiveAspect.java
@@ -65,7 +65,6 @@
 import com.google.devtools.build.lib.rules.proto.ProtoLangToolchainProvider;
 import com.google.devtools.build.lib.rules.proto.ProtoSourcesProvider;
 import com.google.devtools.build.lib.skyframe.ConfiguredTargetAndTarget;
-import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.List;
@@ -73,7 +72,6 @@
 import java.util.Set;
 
 /** Aspect to {@link DexArchiveProvider build .dex Archives} from Jars. */
-@AutoCodec
 public final class DexArchiveAspect extends NativeAspectClass implements ConfiguredAspectFactory {
   public static final String NAME = "DexArchiveAspect";
 
diff --git a/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java b/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
index 12e236c..a308552 100644
--- a/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
+++ b/src/main/java/com/google/devtools/build/lib/rules/apple/DottedVersion.java
@@ -262,38 +262,36 @@
     printer.append(stringRepresentation);
   }
 
-  public static final ObjectCodec<DottedVersion> CODEC =
-      new ObjectCodec<DottedVersion>() {
-        @Override
-        public void serialize(
-            SerializationContext context, DottedVersion obj, CodedOutputStream codedOut)
-            throws IOException {
-          codedOut.writeInt32NoTag(obj.components.size());
-          for (Component component : obj.components) {
-            component.serialize(codedOut);
-          }
-          codedOut.writeStringNoTag(obj.stringRepresentation);
-          codedOut.writeInt32NoTag(obj.numOriginalComponents);
-        }
+  private static class DottedVersionCodec implements ObjectCodec<DottedVersion> {
+    @Override
+    public Class<DottedVersion> getEncodedClass() {
+      return DottedVersion.class;
+    }
 
-        @Override
-        public DottedVersion deserialize(DeserializationContext context, CodedInputStream codedIn)
-            throws IOException {
-          int numComponents = codedIn.readInt32();
-          // TODO(janakr: Presize this if/when https://github.com/google/guava/issues/196 is
-          // resolved.
-          ImmutableList.Builder<Component> components = ImmutableList.builder();
-          for (int i = 0; i < numComponents; i++) {
-            components.add(Component.deserialize(codedIn));
-          }
-          return new DottedVersion(components.build(), codedIn.readString(), codedIn.readInt32());
-        }
+    @Override
+    public void serialize(
+        SerializationContext context, DottedVersion obj, CodedOutputStream codedOut)
+        throws IOException {
+      codedOut.writeInt32NoTag(obj.components.size());
+      for (Component component : obj.components) {
+        component.serialize(codedOut);
+      }
+      codedOut.writeStringNoTag(obj.stringRepresentation);
+      codedOut.writeInt32NoTag(obj.numOriginalComponents);
+    }
 
-        @Override
-        public Class<DottedVersion> getEncodedClass() {
-          return DottedVersion.class;
-        }
-      };
+    @Override
+    public DottedVersion deserialize(DeserializationContext context, CodedInputStream codedIn)
+        throws IOException {
+      int numComponents = codedIn.readInt32();
+      // TODO(janakr: Presize this if/when https://github.com/google/guava/issues/196 is resolved.
+      ImmutableList.Builder<Component> components = ImmutableList.builder();
+      for (int i = 0; i < numComponents; i++) {
+        components.add(Component.deserialize(codedIn));
+      }
+      return new DottedVersion(components.build(), codedIn.readString(), codedIn.readInt32());
+    }
+  }
 
   private static final class Component implements Comparable<Component> {
     private final int firstNumber;
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/AutoRegistry.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/AutoRegistry.java
new file mode 100644
index 0000000..cd8c3c4
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/AutoRegistry.java
@@ -0,0 +1,45 @@
+// Copyright 2018 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.skyframe.serialization;
+
+import com.google.common.base.Supplier;
+import com.google.common.base.Suppliers;
+import java.io.IOException;
+
+/**
+ * A lazy, automatically populated registry.
+ *
+ * <p>Must not be accessed by any {@link CodecRegisterer} or {@link ObjectCodec} constructors or
+ * static initializers.
+ */
+public class AutoRegistry {
+
+  private static final Supplier<ObjectCodecRegistry> SUPPLIER =
+      Suppliers.memoize(AutoRegistry::create);
+
+  public static ObjectCodecRegistry get() {
+    return SUPPLIER.get();
+  }
+
+  private static ObjectCodecRegistry create() {
+    try {
+      return CodecScanner.initializeCodecRegistry("com.google.devtools.build.lib")
+          .setAllowDefaultCodec(false)
+          .build();
+    } catch (IOException | ReflectiveOperationException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/CodecRegisterer.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/CodecRegisterer.java
index ec9ba20..72d9919 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/CodecRegisterer.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/CodecRegisterer.java
@@ -40,5 +40,5 @@
  */
 public interface CodecRegisterer<T extends ObjectCodec<?>> {
 
-  void register(ObjectCodecRegistry.Builder builder);
+  default void register(ObjectCodecRegistry.Builder builder) {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/DeserializationContext.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/DeserializationContext.java
index 3bf44ad..5377d4f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/DeserializationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/DeserializationContext.java
@@ -16,17 +16,36 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.CodedInputStream;
+import java.io.IOException;
 
 /** Stateful class for providing additional context to a single deserialization "session". */
 // TODO(bazel-team): This class is just a shell, fill in.
 public class DeserializationContext {
 
+  private final ObjectCodecRegistry registry;
   private final ImmutableMap<Class<?>, Object> dependencies;
 
-  public DeserializationContext(ImmutableMap<Class<?>, Object> dependencies) {
+  public DeserializationContext(
+      ObjectCodecRegistry registry, ImmutableMap<Class<?>, Object> dependencies) {
+    this.registry = registry;
     this.dependencies = dependencies;
   }
 
+  public DeserializationContext(ImmutableMap<Class<?>, Object> dependencies) {
+    this(AutoRegistry.get(), dependencies);
+  }
+
+  // TODO(shahan): consider making codedIn a member of this class.
+  @SuppressWarnings({"TypeParameterUnusedInFormals", "unchecked"})
+  public <T> T deserialize(CodedInputStream codedIn) throws IOException, SerializationException {
+    int tag = codedIn.readSInt32();
+    if (tag == 0) {
+      return null;
+    }
+    return (T) registry.getCodecDescriptorByTag(tag).deserialize(this, codedIn);
+  }
+
   @SuppressWarnings("unchecked")
   public <T> T getDependency(Class<T> type) {
     Preconditions.checkNotNull(type);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/JavaSerializableCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/JavaSerializableCodec.java
index e6e4bd6..d0b6a73 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/JavaSerializableCodec.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/JavaSerializableCodec.java
@@ -63,4 +63,8 @@
       throw new SerializationException("Java deserialization failed", e);
     }
   }
+
+  /** Disables auto-registration. */
+  private static class JavaSerializableCodecRegisterer
+      implements CodecRegisterer<JavaSerializableCodec> {}
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
index 20f8708..e404ab0 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecRegistry.java
@@ -17,6 +17,9 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.protobuf.ByteString;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
 import java.util.Map;
 import java.util.Map.Entry;
 import javax.annotation.Nullable;
@@ -38,20 +41,19 @@
   @Nullable
   private final CodecDescriptor defaultCodecDescriptor;
 
-  private ObjectCodecRegistry(Map<String, ObjectCodec<?>> codecs, boolean allowDefaultCodec) {
+  private ObjectCodecRegistry(Map<String, CodecHolder> codecs, boolean allowDefaultCodec) {
     ImmutableMap.Builder<String, CodecDescriptor> codecMappingsBuilder = ImmutableMap.builder();
-    int nextTag = 0;
+    int nextTag = 1; // 0 is reserved for null.
     for (String classifier : ImmutableList.sortedCopyOf(codecs.keySet())) {
-      codecMappingsBuilder.put(classifier, new CodecDescriptor(nextTag, codecs.get(classifier)));
+      codecMappingsBuilder.put(classifier, codecs.get(classifier).createDescriptor(nextTag));
       nextTag++;
     }
     this.stringMappedCodecs = codecMappingsBuilder.build();
 
     this.byteStringMappedCodecs = makeByteStringMappedCodecs(stringMappedCodecs);
 
-    this.defaultCodecDescriptor = allowDefaultCodec
-        ? new CodecDescriptor(nextTag, new JavaSerializableCodec())
-        : null;
+    this.defaultCodecDescriptor =
+        allowDefaultCodec ? new TypedCodecDescriptor<>(nextTag, new JavaSerializableCodec()) : null;
     this.tagMappedCodecs = makeTagMappedCodecs(stringMappedCodecs, defaultCodecDescriptor);
   }
 
@@ -83,26 +85,37 @@
     }
   }
 
+  /**
+   * Returns a {@link CodecDescriptor} for the given type.
+   *
+   * <p>Falls back to a codec for the nearest super type of type. Failing that, may fall back to the
+   * registry's default codec.
+   */
   public CodecDescriptor getCodecDescriptor(Class<?> type)
       throws SerializationException.NoCodecException {
-    CodecDescriptor result =
-        stringMappedCodecs.getOrDefault(type.getName(), defaultCodecDescriptor);
-    if (result != null) {
-      return result;
-    } else {
+    // TODO(blaze-team): consider caching this traversal.
+    for (Class<?> nextType = type; nextType != null; nextType = nextType.getSuperclass()) {
+      CodecDescriptor result = stringMappedCodecs.get(nextType.getName());
+      if (result != null) {
+        return result;
+      }
+    }
+    if (defaultCodecDescriptor == null) {
       throw new SerializationException.NoCodecException(
           "No codec available for " + type + " and default fallback disabled");
     }
+    return defaultCodecDescriptor;
   }
 
   /** Returns the {@link CodecDescriptor} associated with the supplied tag. */
   public CodecDescriptor getCodecDescriptorByTag(int tag)
       throws SerializationException.NoCodecException {
-    if (tag < 0 || tag > tagMappedCodecs.size()) {
+    int tagOffset = tag - 1;
+    if (tagOffset < 0 || tagOffset > tagMappedCodecs.size()) {
       throw new SerializationException.NoCodecException("No codec available for tag " + tag);
     }
 
-    CodecDescriptor result = tagMappedCodecs.get(tag);
+    CodecDescriptor result = tagMappedCodecs.get(tagOffset);
     if (result != null) {
       return result;
     } else {
@@ -111,32 +124,86 @@
   }
 
   /** Describes encoding logic. */
-  static class CodecDescriptor {
-    private final int tag;
-    private final ObjectCodec<?> codec;
+  static interface CodecDescriptor {
+    void serialize(SerializationContext context, Object obj, CodedOutputStream codedOut)
+        throws IOException, SerializationException;
 
-    private CodecDescriptor(int tag, ObjectCodec<?> codec) {
+    Object deserialize(DeserializationContext context, CodedInputStream codedIn)
+        throws IOException, SerializationException;
+
+    /**
+     * Unique identifier identifying the associated codec.
+     *
+     * <p>Intended to be used as a compact on-the-wire representation of an encoded object's type.
+     *
+     * <p>Returns a value ≥ 1.
+     *
+     * <p>0 is a special tag representing null while negative numbers are reserved for
+     * backreferences.
+     */
+    int getTag();
+
+    /**
+     * Returns the underlying codec.
+     *
+     * <p>For backwards compatibility. New callers should prefer the methods above.
+     */
+    ObjectCodec<?> getCodec();
+  }
+
+  private static class TypedCodecDescriptor<T> implements CodecDescriptor {
+    private final int tag;
+    private final ObjectCodec<T> codec;
+
+    private TypedCodecDescriptor(int tag, ObjectCodec<T> codec) {
       this.tag = tag;
       this.codec = codec;
     }
 
-    /**
-     * Unique identifier identifying the associated codec. Intended to be used as a compact
-     * on-the-wire representation of an encoded object's type.
-     */
-    int getTag() {
+    @Override
+    @SuppressWarnings("unchecked")
+    public void serialize(SerializationContext context, Object obj, CodedOutputStream codedOut)
+        throws IOException, SerializationException {
+      codec.serialize(context, (T) obj, codedOut);
+    }
+
+    @Override
+    public T deserialize(DeserializationContext context, CodedInputStream codedIn)
+        throws IOException, SerializationException {
+      return codec.deserialize(context, codedIn);
+    }
+
+    @Override
+    public int getTag() {
       return tag;
     }
 
-    ObjectCodec<?> getCodec() {
+    @Override
+    public ObjectCodec<T> getCodec() {
       return codec;
     }
   }
 
+  private interface CodecHolder {
+    CodecDescriptor createDescriptor(int tag);
+  }
+
+  private static class TypedCodecHolder<T> implements CodecHolder {
+    private final ObjectCodec<T> codec;
+
+    private TypedCodecHolder(ObjectCodec<T> codec) {
+      this.codec = codec;
+    }
+
+    @Override
+    public CodecDescriptor createDescriptor(int tag) {
+      return new TypedCodecDescriptor<T>(tag, codec);
+    }
+  }
+
   /** Builder for {@link ObjectCodecRegistry}. */
   public static class Builder {
-    private final ImmutableMap.Builder<String, ObjectCodec<?>> codecsBuilder =
-        ImmutableMap.builder();
+    private final ImmutableMap.Builder<String, CodecHolder> codecsBuilder = ImmutableMap.builder();
     private boolean allowDefaultCodec = true;
 
     private Builder() {}
@@ -147,8 +214,8 @@
      * <p>Intended for package-internal usage only. Consider using the specialized build types
      * returned by {@link #asClassKeyedBuilder()} before using this method.
      */
-    Builder add(String classifier, ObjectCodec<?> codec) {
-      codecsBuilder.put(classifier, codec);
+    <T> Builder add(String classifier, ObjectCodec<T> codec) {
+      codecsBuilder.put(classifier, new TypedCodecHolder<>(codec));
       return this;
     }
 
@@ -208,11 +275,11 @@
     CodecDescriptor[] codecTable =
         new CodecDescriptor[codecs.size() + (defaultCodecDescriptor != null ? 1 : 0)];
     for (Entry<String, CodecDescriptor> entry : codecs.entrySet()) {
-      codecTable[entry.getValue().getTag()] = entry.getValue();
+      codecTable[entry.getValue().getTag() - 1] = entry.getValue();
     }
 
     if (defaultCodecDescriptor != null) {
-      codecTable[defaultCodecDescriptor.getTag()] = defaultCodecDescriptor;
+      codecTable[defaultCodecDescriptor.getTag() - 1] = defaultCodecDescriptor;
     }
     return ImmutableList.copyOf(codecTable);
   }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
index 7470fe4..bb5a504 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/SerializationContext.java
@@ -16,17 +16,38 @@
 
 import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
+import com.google.protobuf.CodedOutputStream;
+import java.io.IOException;
 
 /** Stateful class for providing additional context to a single serialization "session". */
 // TODO(bazel-team): This class is just a shell, fill in.
 public class SerializationContext {
 
+  private final ObjectCodecRegistry registry;
   private final ImmutableMap<Class<?>, Object> dependencies;
 
-  public SerializationContext(ImmutableMap<Class<?>, Object> dependencies) {
+  public SerializationContext(
+      ObjectCodecRegistry registry, ImmutableMap<Class<?>, Object> dependencies) {
+    this.registry = registry;
     this.dependencies = dependencies;
   }
 
+  public SerializationContext(ImmutableMap<Class<?>, Object> dependencies) {
+    this(AutoRegistry.get(), dependencies);
+  }
+
+  // TODO(shahan): consider making codedOut a member of this class.
+  public void serialize(Object object, CodedOutputStream codedOut)
+      throws IOException, SerializationException {
+    if (object == null) {
+      codedOut.writeSInt32NoTag(0);
+      return;
+    }
+    ObjectCodecRegistry.CodecDescriptor descriptor = registry.getCodecDescriptor(object.getClass());
+    codedOut.writeSInt32NoTag(descriptor.getTag());
+    descriptor.serialize(this, object, codedOut);
+  }
+
   @SuppressWarnings("unchecked")
   public <T> T getDependency(Class<T> type) {
     Preconditions.checkNotNull(type);
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
index 8476298..0f597db 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/AutoCodec.java
@@ -25,11 +25,11 @@
  * <pre>{@code
  * @AutoCodec
  * class Target {
- *   public static final ObjectCodec<Target> CODEC = new Target_AutoCodec();
- * }
  * }</pre>
  *
  * The {@code _AutoCodec} suffix is added to the {@code Target} to obtain the generated class name.
+ * In the example, that results in a class named {@code Target_AutoCodec} but applications should
+ * not need to directly access the generated class.
  */
 @Target(ElementType.TYPE)
 public @interface AutoCodec {
@@ -46,7 +46,8 @@
      *
      * <ul>
      *   <li>a designated constructor or factory method to inspect to generate the codec
-     *   <li>the parameters must match member fields on name and type.
+     *   <li>each parameter must match a member field on name and the field will be interpreted as
+     *       an instance of the parameter type.
      * </ul>
      *
      * <p>If there is a unique constructor, @AutoCodec may select that as the default instantiator,
@@ -65,13 +66,14 @@
      *
      * <p>Uses reflection to determine the concrete subclass, stores the name of the subclass and
      * uses its codec to serialize the data.
+     *
+     * <p>This is no longer needed and only adds useless overhead.
      */
+    // TODO(shahan): delete this and all references to it
     POLYMORPHIC,
     /**
      * For use with classes that are singleton.
      *
-     * <p>Commonly used with the POLYMORPHIC strategy.
-     *
      * <p>The serialized class must have a codec accessible, static INSTANCE field.
      */
     SINGLETON
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
index 91d46d5..9c7bc19 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/BUILD
@@ -16,6 +16,8 @@
         # Generated classes have the following dependencies.
         ":unsafe-provider",
         "//third_party/protobuf:protobuf_java",
+        "//src/main/java/com/google/devtools/build/lib/collect/nestedset",
+        "//src/main/java/com/google/devtools/build/lib/collect/nestedset:serialization",
         "//src/main/java/com/google/devtools/build/lib/skyframe/serialization",
     ],
 )
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/Marshallers.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/Marshallers.java
index d1a04da..bf84474 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/Marshallers.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/autocodec/Marshallers.java
@@ -28,7 +28,6 @@
 import com.google.common.hash.HashCode;
 import com.google.devtools.build.lib.collect.nestedset.NestedSet;
 import com.google.devtools.build.lib.collect.nestedset.NestedSetCodec;
-import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationCodeGenerator.Context;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationCodeGenerator.Marshaller;
 import com.google.devtools.build.lib.skyframe.serialization.autocodec.SerializationCodeGenerator.PrimitiveValueSerializationCodeGenerator;
@@ -45,9 +44,7 @@
 import java.util.function.Consumer;
 import java.util.regex.Pattern;
 import javax.annotation.processing.ProcessingEnvironment;
-import javax.lang.model.element.Element;
 import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.Modifier;
 import javax.lang.model.element.TypeElement;
 import javax.lang.model.type.ArrayType;
 import javax.lang.model.type.DeclaredType;
@@ -64,12 +61,14 @@
   }
 
   void writeSerializationCode(Context context) {
-    if (context.canBeNull()) {
+    SerializationCodeGenerator generator = getMatchingCodeGenerator(context.type);
+    boolean needsNullHandling = context.canBeNull() && generator != contextMarshaller;
+    if (needsNullHandling) {
       context.builder.beginControlFlow("if ($L != null)", context.name);
       context.builder.addStatement("codedOut.writeBoolNoTag(true)");
     }
-    getMatchingCodeGenerator(context.type).addSerializationCode(context);
-    if (context.canBeNull()) {
+    generator.addSerializationCode(context);
+    if (needsNullHandling) {
       context.builder.nextControlFlow("else");
       context.builder.addStatement("codedOut.writeBoolNoTag(false)");
       context.builder.endControlFlow();
@@ -77,14 +76,16 @@
   }
 
   void writeDeserializationCode(Context context) {
-    if (context.canBeNull()) {
+    SerializationCodeGenerator generator = getMatchingCodeGenerator(context.type);
+    boolean needsNullHandling = context.canBeNull() && generator != contextMarshaller;
+    if (needsNullHandling) {
       context.builder.addStatement("$T $L = null", context.getTypeName(), context.name);
       context.builder.beginControlFlow("if (codedIn.readBool())");
     } else {
       context.builder.addStatement("$T $L", context.getTypeName(), context.name);
     }
-    getMatchingCodeGenerator(context.type).addDeserializationCode(context);
-    if (requiresNullityCheck(context)) {
+    generator.addDeserializationCode(context);
+    if (needsNullHandling) {
       context.builder.endControlFlow();
     }
   }
@@ -108,10 +109,6 @@
     context.builder.addStatement("$L = $L.build()", context.name, builderName);
   }
 
-  private static boolean requiresNullityCheck(Context context) {
-    return !(context.type instanceof PrimitiveType);
-  }
-
   private SerializationCodeGenerator getMatchingCodeGenerator(TypeMirror type) {
     if (type.getKind() == TypeKind.ARRAY) {
       return arrayCodeGenerator;
@@ -814,27 +811,13 @@
 
   private void addSerializationCodeForNestedSet(Context context) {
     TypeMirror typeParameter = context.getDeclaredType().getTypeArguments().get(0);
-          String nestedSetCodec = context.makeName("nestedSetCodec");
-    Optional<? extends Element> typeParameterCodec =
-        Optional.fromJavaUtil(getCodec((DeclaredType) typeParameter));
-    if (!typeParameterCodec.isPresent()) {
-      // AutoCodec can only serialize NestedSets of declared types.  However, this code must
-      // be generated for Iterables of non-declared types (e.g. String), since Iterable
-      // serialization involves a runtime check for NestedSet.  In this case, throw on the unused
-      // NestedSet branch.
-      context.builder.addStatement(
-          "throw new $T(\"NestedSet<$T> is not supported in AutoCodec\")",
-          AssertionError.class,
-          typeParameter);
-      return;
-    }
+    String nestedSetCodec = context.makeName("nestedSetCodec");
     context.builder.addStatement(
-        "$T<$T> $L = new $T<>($T.CODEC)",
+        "$T<$T> $L = new $T<>()",
         NestedSetCodec.class,
         typeParameter,
         nestedSetCodec,
-        NestedSetCodec.class,
-        typeParameter);
+        NestedSetCodec.class);
     context.builder.addStatement(
         "$L.serialize(context, ($T<$T>) $L, codedOut)",
         nestedSetCodec,
@@ -846,26 +829,12 @@
   private void addDeserializationCodeForNestedSet(Context context) {
     TypeMirror typeParameter = context.getDeclaredType().getTypeArguments().get(0);
           String nestedSetCodec = context.makeName("nestedSetCodec");
-    Optional<? extends Element> typeParameterCodec =
-        Optional.fromJavaUtil(getCodec((DeclaredType) typeParameter));
-    if (!typeParameterCodec.isPresent()) {
-      // AutoCodec can only serialize NestedSets of declared types.  However, this code must
-      // be generated for Iterables of non-declared types (e.g. String), since Iterable
-      // serialization involves a runtime check for NestedSet.  In this case, we throw on the unused
-      // NestedSet branch.
-      context.builder.addStatement(
-          "throw new $T(\"NestedSet<$T> is not supported in AutoCodec\")",
-          AssertionError.class,
-          typeParameter);
-      return;
-    }
     context.builder.addStatement(
-        "$T<$T> $L = new $T<>($T.CODEC)",
+        "$T<$T> $L = new $T<>()",
         NestedSetCodec.class,
         typeParameter,
         nestedSetCodec,
-        NestedSetCodec.class,
-        typeParameter);
+        NestedSetCodec.class);
     context.builder.addStatement(
         "$L = $L.deserialize(context, codedIn)", context.name, nestedSetCodec);
   }
@@ -893,39 +862,22 @@
         }
       };
 
-  private final Marshaller codecMarshaller =
+  /** Delegates marshalling back to the context. */
+  private final Marshaller contextMarshaller =
       new Marshaller() {
         @Override
-        public boolean matches(DeclaredType type) {
-          return getCodec(type).isPresent();
+        public boolean matches(DeclaredType unusedType) {
+          return true;
         }
 
         @Override
         public void addSerializationCode(Context context) {
-          TypeMirror codecType = getCodec(context.getDeclaredType()).get().asType();
-          if (isSubtypeErased(codecType, ObjectCodec.class)) {
-            context.builder.addStatement(
-                "$T.CODEC.serialize(context, $L, codedOut)", context.getTypeName(), context.name);
-          } else {
-            throw new IllegalArgumentException(
-                "CODEC field of "
-                    + ((TypeElement) context.getDeclaredType().asElement()).getQualifiedName()
-                    + " is not ObjectCodec");
-          }
+          context.builder.addStatement("context.serialize($L, codedOut)", context.name);
         }
 
         @Override
         public void addDeserializationCode(Context context) {
-          TypeMirror codecType = getCodec(context.getDeclaredType()).get().asType();
-          if (isSubtypeErased(codecType, ObjectCodec.class)) {
-            context.builder.addStatement(
-                "$L = $T.CODEC.deserialize(context, codedIn)", context.name, context.getTypeName());
-          } else {
-            throw new IllegalArgumentException(
-                "CODEC field of "
-                    + ((TypeElement) context.getDeclaredType().asElement()).getQualifiedName()
-                    + " is neither ObjectCodec");
-          }
+          context.builder.addStatement("$L = context.deserialize(codedIn)", context.name);
         }
       };
 
@@ -952,8 +904,8 @@
           patternMarshaller,
           hashCodeMarshaller,
           protoMarshaller,
-          codecMarshaller,
-          iterableMarshaller);
+          iterableMarshaller,
+          contextMarshaller);
 
   /** True when {@code type} has the same type as {@code clazz}. */
   private boolean matchesType(TypeMirror type, Class<?> clazz) {
@@ -971,24 +923,8 @@
         .isSameType(env.getTypeUtils().erasure(type), env.getTypeUtils().erasure(getType(clazz)));
   }
 
-  /** True when erasure of {@code type} is a subtype of the erasure of {@code clazz}. */
-  private boolean isSubtypeErased(TypeMirror type, Class<?> clazz) {
-    return env.getTypeUtils()
-        .isSubtype(env.getTypeUtils().erasure(type), env.getTypeUtils().erasure(getType(clazz)));
-  }
-
   /** Returns the TypeMirror corresponding to {@code clazz}. */
   private TypeMirror getType(Class<?> clazz) {
     return env.getElementUtils().getTypeElement((clazz.getCanonicalName())).asType();
   }
-
-  private static java.util.Optional<? extends Element> getCodec(DeclaredType type) {
-    return type.asElement()
-        .getEnclosedElements()
-        .stream()
-        .filter(t -> t.getModifiers().contains(Modifier.STATIC))
-        .filter(t -> t.getSimpleName().contentEquals("CODEC"))
-        .filter(t -> t.getKind() == ElementKind.FIELD)
-        .findAny();
-  }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java
new file mode 100644
index 0000000..5d561ad
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/SerializationTester.java
@@ -0,0 +1,155 @@
+// Copyright 2017 The Bazel Authors. All rights reserved.
+//
+// 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.devtools.build.lib.skyframe.serialization.testutils;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assert_;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
+import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
+import com.google.protobuf.CodedInputStream;
+import com.google.protobuf.CodedOutputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.Random;
+import java.util.function.Supplier;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Utility for testing serialization of given subjects.
+ *
+ * <p>Differs from {@link ObjectCodecTester} in that this uses the context to perform serialization
+ * deserialization instead of a specific codec.
+ */
+public class SerializationTester {
+  public static final int DEFAULT_JUNK_INPUTS = 20;
+  public static final int JUNK_LENGTH_UPPER_BOUND = 20;
+
+  private static final Logger logger = Logger.getLogger(SerializationTester.class.getName());
+
+  /** Interface for testing successful deserialization of an object. */
+  @FunctionalInterface
+  public interface VerificationFunction<T> {
+    /**
+     * Verifies that the original object was sufficiently serialized/deserialized.
+     *
+     * <p><i>Must</i> throw an exception on failure.
+     */
+    void verifyDeserialized(T original, T deserialized) throws Exception;
+  }
+
+  private final ImmutableList<Object> subjects;
+  private Supplier<SerializationContext> writeContextFactory =
+      () -> new SerializationContext(ImmutableMap.of());
+  private Supplier<DeserializationContext> readContextFactory =
+      () -> new DeserializationContext(ImmutableMap.of());
+
+  @SuppressWarnings("rawtypes")
+  private VerificationFunction verificationFunction =
+      (original, deserialized) -> assertThat(deserialized).isEqualTo(original);
+
+  public SerializationTester(Object... subjects) {
+    Preconditions.checkArgument(subjects.length > 0);
+    this.subjects = ImmutableList.copyOf(subjects);
+  }
+
+  public SerializationTester setWriteContextFactory(
+      Supplier<SerializationContext> writeContextFactory) {
+    this.writeContextFactory = writeContextFactory;
+    return this;
+  }
+
+  public SerializationTester setReadContextFactory(
+      Supplier<DeserializationContext> readContextFactory) {
+    this.readContextFactory = readContextFactory;
+    return this;
+  }
+
+  @SuppressWarnings("rawtypes")
+  public SerializationTester setVerificationFunction(VerificationFunction verificationFunction) {
+    this.verificationFunction = verificationFunction;
+    return this;
+  }
+
+  public void runTests() throws Exception {
+    testSerializeDeserialize();
+    testStableSerialization();
+    testDeserializeJunkData();
+  }
+
+  /** Runs serialization/deserialization tests. */
+  private void testSerializeDeserialize() throws Exception {
+    Stopwatch timer = Stopwatch.createStarted();
+    int totalBytes = 0;
+    for (Object subject : subjects) {
+      byte[] serialized = toBytes(subject);
+      totalBytes += serialized.length;
+      Object deserialized = fromBytes(serialized);
+      verificationFunction.verifyDeserialized(subject, deserialized);
+    }
+    logger.log(
+        Level.INFO,
+        subjects.get(0).getClass().getSimpleName()
+            + " total serialized bytes = "
+            + totalBytes
+            + ", "
+            + timer);
+  }
+
+  /** Runs serialized bytes stability tests. */
+  private void testStableSerialization() throws IOException, SerializationException {
+    for (Object subject : subjects) {
+      byte[] serialized = toBytes(subject);
+      Object deserialized = fromBytes(serialized);
+      byte[] reserialized = toBytes(deserialized);
+      assertThat(reserialized).isEqualTo(serialized);
+    }
+  }
+
+  /** Runs junk-data recognition tests. */
+  private void testDeserializeJunkData() {
+    Random rng = new Random(0);
+    for (int i = 0; i < DEFAULT_JUNK_INPUTS; ++i) {
+      byte[] junkData = new byte[rng.nextInt(JUNK_LENGTH_UPPER_BOUND)];
+      rng.nextBytes(junkData);
+      try {
+        readContextFactory.get().deserialize(CodedInputStream.newInstance(junkData));
+        // OK. Junk string was coincidentally parsed.
+      } catch (IOException | SerializationException e) {
+        // OK. Deserialization of junk failed.
+        return;
+      }
+    }
+    assert_().fail("all junk was parsed successfully");
+  }
+
+  private Object fromBytes(byte[] bytes) throws IOException, SerializationException {
+    return readContextFactory.get().deserialize(CodedInputStream.newInstance(bytes));
+  }
+
+  private byte[] toBytes(Object subject) throws IOException, SerializationException {
+    ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+    CodedOutputStream codedOut = CodedOutputStream.newInstance(bytes);
+    writeContextFactory.get().serialize(subject, codedOut);
+    codedOut.flush();
+    return bytes.toByteArray();
+  }
+}
diff --git a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/TestUtils.java b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/TestUtils.java
index 2c729d4..538d35f 100644
--- a/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/TestUtils.java
+++ b/src/main/java/com/google/devtools/build/lib/skyframe/serialization/testutils/TestUtils.java
@@ -19,7 +19,6 @@
 import com.google.devtools.build.lib.skyframe.serialization.CodecRegisterer;
 import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
 import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
-import com.google.devtools.build.lib.skyframe.serialization.ObjectCodecRegistry;
 import com.google.devtools.build.lib.skyframe.serialization.SerializationContext;
 import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
 import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
@@ -94,9 +93,7 @@
     }
 
     /** Disables auto-registration of ConstantStringCodec. */
-    static class ConstantStringCodecRegisterer implements CodecRegisterer<ConstantStringCodec> {
-      @Override
-      public void register(ObjectCodecRegistry.Builder unusedBuilder) {}
-    }
+    private static class ConstantStringCodecRegisterer
+        implements CodecRegisterer<ConstantStringCodec> {}
   }
 }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Root.java b/src/main/java/com/google/devtools/build/lib/vfs/Root.java
index 6f090e4..c23dbeb 100644
--- a/src/main/java/com/google/devtools/build/lib/vfs/Root.java
+++ b/src/main/java/com/google/devtools/build/lib/vfs/Root.java
@@ -33,37 +33,37 @@
  * <p>A typical root could be the exec path, a package root, or an output root specific to some
  * configuration. We also support absolute roots for non-hermetic paths outside the user workspace.
  */
-public interface Root extends Comparable<Root>, Serializable {
+public abstract class Root implements Comparable<Root>, Serializable {
 
-  ObjectCodec<Root> CODEC = new RootCodec();
+  public static final ObjectCodec<Root> CODEC = new RootCodec();
 
   /** Constructs a root from a path. */
-  static Root fromPath(Path path) {
+  public static Root fromPath(Path path) {
     return new PathRoot(path);
   }
 
   /** Returns an absolute root. Can only be used with absolute path fragments. */
-  static Root absoluteRoot(FileSystem fileSystem) {
+  public static Root absoluteRoot(FileSystem fileSystem) {
     return fileSystem.getAbsoluteRoot();
   }
 
   /** Returns a path by concatenating the root and the root-relative path. */
-  Path getRelative(PathFragment rootRelativePath);
+  public abstract Path getRelative(PathFragment rootRelativePath);
 
   /** Returns a path by concatenating the root and the root-relative path. */
-  Path getRelative(String rootRelativePath);
+  public abstract Path getRelative(String rootRelativePath);
 
   /** Returns the relative path between the root and the given path. */
-  PathFragment relativize(Path path);
+  public abstract PathFragment relativize(Path path);
 
   /** Returns the relative path between the root and the given absolute path fragment. */
-  PathFragment relativize(PathFragment absolutePathFragment);
+  public abstract PathFragment relativize(PathFragment absolutePathFragment);
 
   /** Returns whether the given path is under this root. */
-  boolean contains(Path path);
+  public abstract boolean contains(Path path);
 
   /** Returns whether the given absolute path fragment is under this root. */
-  boolean contains(PathFragment absolutePathFragment);
+  public abstract boolean contains(PathFragment absolutePathFragment);
 
   /**
    * Returns the underlying path. Please avoid using this method.
@@ -71,12 +71,12 @@
    * <p>Not all roots are backed by paths, so this may return null.
    */
   @Nullable
-  Path asPath();
+  public abstract Path asPath();
 
-  boolean isAbsolute();
+  public abstract boolean isAbsolute();
 
   /** Implementation of Root that is backed by a {@link Path}. */
-  final class PathRoot implements Root {
+  public static final class PathRoot extends Root {
     private final Path path;
 
     PathRoot(Path path) {
@@ -160,7 +160,7 @@
   }
 
   /** An absolute root of a file system. Can only resolve absolute path fragments. */
-  final class AbsoluteRoot implements Root {
+  public static final class AbsoluteRoot extends Root {
     private FileSystem fileSystem; // Non-final for serialization
 
     AbsoluteRoot(FileSystem fileSystem) {
@@ -258,7 +258,7 @@
   }
 
   /** Codec to serialize {@link Root}s. */
-  class RootCodec implements ObjectCodec<Root> {
+  private static class RootCodec implements ObjectCodec<Root> {
     @Override
     public Class<Root> getEncodedClass() {
       return Root.class;
@@ -272,7 +272,7 @@
         throws SerializationException, IOException {
       if (obj instanceof PathRoot) {
         codedOut.writeBoolNoTag(false);
-        Path.CODEC.serialize(context, ((PathRoot) obj).path, codedOut);
+        context.serialize(((PathRoot) obj).path, codedOut);
       } else if (obj instanceof AbsoluteRoot) {
         Preconditions.checkArgument(
             ((AbsoluteRoot) obj).fileSystem == context.getDependency(FileSystem.class));
@@ -289,7 +289,7 @@
       if (isAbsolute) {
         return context.getDependency(FileSystem.class).getAbsoluteRoot();
       } else {
-        Path path = Path.CODEC.deserialize(context, codedIn);
+        Path path = context.deserialize(codedIn);
         return new PathRoot(path);
       }
     }
diff --git a/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTest.java b/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTest.java
index 9adb1d6..57f0a4a 100644
--- a/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTest.java
+++ b/src/test/java/com/google/devtools/build/lib/collect/nestedset/NestedSetCodecTest.java
@@ -16,7 +16,6 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.common.collect.ImmutableList;
-import com.google.devtools.build.lib.skyframe.serialization.strings.StringCodecs;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.ObjectCodecTester;
 import com.google.devtools.build.lib.skyframe.serialization.testutils.SerializerTester;
 import org.junit.Test;
@@ -54,7 +53,7 @@
 
   @Test
   public void testCodec() throws Exception {
-    ObjectCodecTester.newBuilder(new NestedSetCodec<>(StringCodecs.simple()))
+    ObjectCodecTester.newBuilder(new NestedSetCodec<String>())
         .addSubjects(SUBJECTS)
         .verificationFunction(NestedSetCodecTest::verifyDeserialization)
         .buildAndRunTests();
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/PackageValueTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/PackageValueTest.java
deleted file mode 100644
index 38cbbf7..0000000
--- a/src/test/java/com/google/devtools/build/lib/skyframe/PackageValueTest.java
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright 2018 The Bazel Authors. All rights reserved.
-//
-// 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.devtools.build.lib.skyframe;
-
-import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.doThrow;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-import com.google.common.collect.ImmutableMap;
-import com.google.devtools.build.lib.packages.Package;
-import com.google.devtools.build.lib.packages.PackageCodecDependencies;
-import com.google.devtools.build.lib.packages.PackageCodecDependencies.SimplePackageCodecDependencies;
-import com.google.devtools.build.lib.packages.PackageDeserializationException;
-import com.google.devtools.build.lib.packages.PackageDeserializerInterface;
-import com.google.devtools.build.lib.skyframe.serialization.DeserializationContext;
-import com.google.devtools.build.lib.skyframe.serialization.ObjectCodec;
-import com.google.devtools.build.lib.skyframe.serialization.SerializationException;
-import com.google.protobuf.CodedInputStream;
-import java.io.IOException;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-/** Basic tests for {@link PackageValue}. */
-@RunWith(JUnit4.class)
-public class PackageValueTest {
-
-  private PackageDeserializerInterface mockDeserializer;
-  private ObjectCodec<PackageValue> underTest;
-  SimplePackageCodecDependencies codecDeps;
-
-  @Before
-  public void setUp() {
-    this.mockDeserializer = mock(PackageDeserializerInterface.class);
-    this.underTest = PackageValue.CODEC;
-    this.codecDeps = new SimplePackageCodecDependencies(null, mockDeserializer);
-  }
-
-  @Test
-  public void testDeserializationIsDelegatedToPackageDeserializer()
-      throws SerializationException, IOException, PackageDeserializationException,
-          InterruptedException {
-    // Mock because all we need is to verify that we're properly delegating to Package deserializer.
-    Package mockPackage = mock(Package.class);
-
-    when(mockDeserializer.deserialize(
-        any(DeserializationContext.class), any(CodedInputStream.class))).thenReturn(mockPackage);
-
-    CodedInputStream codedIn = CodedInputStream.newInstance(new byte[] {1, 2, 3, 4});
-    PackageValue result =
-        underTest.deserialize(
-            new DeserializationContext(ImmutableMap.of(PackageCodecDependencies.class, codecDeps)),
-            codedIn);
-
-    assertThat(result.getPackage()).isSameAs(mockPackage);
-  }
-
-  @Test
-  public void testInterruptedExceptionRaisesIllegalStateException() throws Exception {
-    InterruptedException staged = new InterruptedException("Stop that!");
-    doThrow(staged).when(mockDeserializer)
-    .deserialize(any(DeserializationContext.class), any(CodedInputStream.class));
-
-    try {
-      underTest.deserialize(
-          new DeserializationContext(ImmutableMap.of(PackageCodecDependencies.class, codecDeps)),
-          CodedInputStream.newInstance(new byte[] {1, 2, 3, 4}));
-      fail("Expected exception");
-    } catch (IllegalStateException e) {
-      assertThat(e)
-          .hasMessageThat()
-          .isEqualTo("Unexpected InterruptedException during Package deserialization");
-      assertThat(e).hasCauseThat().isSameAs(staged);
-    }
-  }
-}
diff --git a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecsTest.java b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecsTest.java
index dc835c7..f6448df 100644
--- a/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecsTest.java
+++ b/src/test/java/com/google/devtools/build/lib/skyframe/serialization/ObjectCodecsTest.java
@@ -63,6 +63,9 @@
         throws SerializationException, IOException {
       return codedIn.readInt32();
     }
+
+    /** Disables auto-registration. */
+    private static class IntegerCodecRegisterer implements CodecRegisterer<IntegerCodec> {}
   }
 
   private static final String KNOWN_CLASSIFIER = "KNOWN_CLASSIFIER";