Snap for 4459973 from 80375a9329669589dca4abd5228ad2366d8ff5f3 to pi-release

Change-Id: I294537368265592de6e7c3383f90612f8aeaa10d
diff --git a/Android.bp b/Android.bp
index 92ca2e4..b57fdf7 100644
--- a/Android.bp
+++ b/Android.bp
@@ -24,9 +24,6 @@
 
     manifest: "manifest.txt",
     static_libs: [
-        "asm-5.2",
-        "asm-commons-5.2",
-        "asm-tree-5.2",
         "error_prone_annotations-2.0.18",
         "guava-21.0",
         "jsr305-3.0.1",
diff --git a/java/com/google/turbine/binder/ConstEvaluator.java b/java/com/google/turbine/binder/ConstEvaluator.java
index b8243b9..0598d8e 100644
--- a/java/com/google/turbine/binder/ConstEvaluator.java
+++ b/java/com/google/turbine/binder/ConstEvaluator.java
@@ -878,7 +878,7 @@
   ImmutableList<AnnoInfo> evaluateAnnotations(ImmutableList<AnnoInfo> annotations) {
     ImmutableList.Builder<AnnoInfo> result = ImmutableList.builder();
     for (AnnoInfo annotation : annotations) {
-      result.add(evaluateAnnotation(annotation.sym(), annotation.args()));
+      result.add(evaluateAnnotation(annotation));
     }
     return result.build();
   }
@@ -887,14 +887,14 @@
    * Evaluates annotation arguments given the symbol of the annotation declaration and a list of
    * expression trees.
    */
-  AnnoInfo evaluateAnnotation(ClassSymbol sym, ImmutableList<Expression> args) {
+  AnnoInfo evaluateAnnotation(AnnoInfo info) {
     Map<String, Type> template = new LinkedHashMap<>();
-    for (MethodInfo method : env.get(sym).methods()) {
+    for (MethodInfo method : env.get(info.sym()).methods()) {
       template.put(method.name(), method.returnType());
     }
 
     ImmutableMap.Builder<String, Const> values = ImmutableMap.builder();
-    for (Expression arg : args) {
+    for (Expression arg : info.args()) {
       Expression expr;
       String key;
       if (arg.kind() == Tree.Kind.ASSIGN) {
@@ -916,7 +916,7 @@
       }
       values.put(key, value);
     }
-    return new AnnoInfo(sym, args, values.build());
+    return info.withValues(values.build());
   }
 
   private AnnotationValue evalAnno(Tree.Anno t) {
@@ -925,7 +925,7 @@
     for (String name : result.remaining()) {
       sym = Resolve.resolve(env, sym, sym, name);
     }
-    AnnoInfo annoInfo = evaluateAnnotation(sym, t.args());
+    AnnoInfo annoInfo = evaluateAnnotation(new AnnoInfo(base.source(), sym, t, null));
     return new AnnotationValue(annoInfo.sym(), annoInfo.values());
   }
 
@@ -946,6 +946,9 @@
       throw error(tree.position(), ErrorKind.EXPRESSION_ERROR);
     }
     Const value = eval(tree);
+    if (value == null) {
+      throw error(tree.position(), ErrorKind.EXPRESSION_ERROR);
+    }
     switch (ty.tyKind()) {
       case PRIM_TY:
         return coerce((Const.Value) value, ((Type.PrimTy) ty).primkind());
diff --git a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
index afb899c..26dce32 100644
--- a/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
+++ b/java/com/google/turbine/binder/DisambiguateTypeAnnotations.java
@@ -31,6 +31,8 @@
 import com.google.turbine.binder.bound.TypeBoundClass.ParamInfo;
 import com.google.turbine.binder.env.Env;
 import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.diag.TurbineError;
+import com.google.turbine.diag.TurbineError.ErrorKind;
 import com.google.turbine.model.Const;
 import com.google.turbine.type.AnnoInfo;
 import com.google.turbine.type.Type;
@@ -248,10 +250,13 @@
         }
         ClassSymbol container = env.get(symbol).annotationMetadata().repeatable();
         if (container == null) {
-          throw new AssertionError(symbol);
+          AnnoInfo anno = infos.iterator().next();
+          throw TurbineError.format(
+              anno.source(), anno.position(), ErrorKind.NONREPEATABLE_ANNOTATION, symbol);
         }
         result.add(
             new AnnoInfo(
+                null,
                 container,
                 null,
                 ImmutableMap.of("value", new Const.ArrayInitValue(elements.build()))));
diff --git a/java/com/google/turbine/binder/TypeBinder.java b/java/com/google/turbine/binder/TypeBinder.java
index 5bee45d..b8f6ecd 100644
--- a/java/com/google/turbine/binder/TypeBinder.java
+++ b/java/com/google/turbine/binder/TypeBinder.java
@@ -16,6 +16,7 @@
 
 package com.google.turbine.binder;
 
+import com.google.common.base.Joiner;
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.turbine.binder.bound.HeaderBoundClass;
@@ -51,8 +52,10 @@
 import com.google.turbine.type.Type;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 /** Type binding. */
 public class TypeBinder {
@@ -515,10 +518,15 @@
   }
 
   private ImmutableList<FieldInfo> bindFields(CompoundScope scope, ImmutableList<Tree> members) {
+    Set<FieldSymbol> seen = new HashSet<>();
     ImmutableList.Builder<FieldInfo> fields = ImmutableList.builder();
     for (Tree member : members) {
       if (member.kind() == Tree.Kind.VAR_DECL) {
-        fields.add(bindField(scope, (Tree.VarDecl) member));
+        FieldInfo field = bindField(scope, (Tree.VarDecl) member);
+        if (!seen.add(field.sym())) {
+          throw error(member.position(), ErrorKind.DUPLICATE_DECLARATION, "field: " + field.name());
+        }
+        fields.add(field);
       }
     }
     return fields.build();
@@ -549,16 +557,19 @@
     for (Tree.Anno tree : trees) {
       LookupResult lookupResult = scope.lookup(new LookupKey(tree.name()));
       if (lookupResult == null) {
-        throw error(tree.position(), ErrorKind.SYMBOL_NOT_FOUND, tree.name());
+        throw error(tree.position(), ErrorKind.SYMBOL_NOT_FOUND, Joiner.on('.').join(tree.name()));
       }
       ClassSymbol sym = (ClassSymbol) lookupResult.sym();
       for (String name : lookupResult.remaining()) {
         sym = Resolve.resolve(env, owner, sym, name);
+        if (sym == null) {
+          throw error(tree.position(), ErrorKind.SYMBOL_NOT_FOUND, name);
+        }
       }
       if (env.get(sym).kind() != TurbineTyKind.ANNOTATION) {
         throw error(tree.position(), ErrorKind.NOT_AN_ANNOTATION, sym);
       }
-      result.add(new AnnoInfo(sym, tree.args(), null));
+      result.add(new AnnoInfo(base.source(), sym, tree, null));
     }
     return result.build();
   }
diff --git a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
index b2ca745..76a06e5 100644
--- a/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
+++ b/java/com/google/turbine/binder/bytecode/BytecodeBoundClass.java
@@ -83,7 +83,7 @@
             new Supplier<ClassFile>() {
               @Override
               public ClassFile get() {
-                ClassFile cf = ClassReader.read(bytes.get());
+                ClassFile cf = ClassReader.read(jarFile + "!" + sym, bytes.get());
                 verify(
                     cf.name().equals(sym.binaryName()),
                     "expected class data for %s, saw %s instead",
diff --git a/java/com/google/turbine/binder/sym/FieldSymbol.java b/java/com/google/turbine/binder/sym/FieldSymbol.java
index 9ff8fb1..21304e7 100644
--- a/java/com/google/turbine/binder/sym/FieldSymbol.java
+++ b/java/com/google/turbine/binder/sym/FieldSymbol.java
@@ -58,4 +58,9 @@
     FieldSymbol other = (FieldSymbol) obj;
     return name().equals(other.name()) && owner().equals(other.owner());
   }
+
+  @Override
+  public String toString() {
+    return owner + "#" + name;
+  }
 }
diff --git a/java/com/google/turbine/binder/sym/MethodSymbol.java b/java/com/google/turbine/binder/sym/MethodSymbol.java
index 17459b4..b2050f4 100644
--- a/java/com/google/turbine/binder/sym/MethodSymbol.java
+++ b/java/com/google/turbine/binder/sym/MethodSymbol.java
@@ -58,4 +58,9 @@
     MethodSymbol other = (MethodSymbol) obj;
     return name().equals(other.name()) && owner().equals(other.owner());
   }
+
+  @Override
+  public String toString() {
+    return owner + "#" + name;
+  }
 }
diff --git a/java/com/google/turbine/binder/sym/TyVarSymbol.java b/java/com/google/turbine/binder/sym/TyVarSymbol.java
index 9276f91..a73a79e 100644
--- a/java/com/google/turbine/binder/sym/TyVarSymbol.java
+++ b/java/com/google/turbine/binder/sym/TyVarSymbol.java
@@ -59,4 +59,9 @@
     TyVarSymbol other = (TyVarSymbol) obj;
     return name.equals(other.name()) && owner().equals(other.owner());
   }
+
+  @Override
+  public String toString() {
+    return owner + "#" + name;
+  }
 }
diff --git a/java/com/google/turbine/bytecode/ClassReader.java b/java/com/google/turbine/bytecode/ClassReader.java
index b59448a..96ad454 100644
--- a/java/com/google/turbine/bytecode/ClassReader.java
+++ b/java/com/google/turbine/bytecode/ClassReader.java
@@ -26,30 +26,50 @@
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import javax.annotation.CheckReturnValue;
+import javax.annotation.Nullable;
 
 /** A JVMS §4 class file reader. */
 public class ClassReader {
 
   /** Reads the given bytes into an {@link ClassFile}. */
+  @Deprecated
   public static ClassFile read(byte[] bytes) {
-    return new ClassReader(bytes).read();
+    return read(null, bytes);
   }
 
+  /** Reads the given bytes into an {@link ClassFile}. */
+  public static ClassFile read(@Nullable String path, byte[] bytes) {
+    return new ClassReader(path, bytes).read();
+  }
+
+  @Nullable private final String path;
   private final ByteReader reader;
 
-  private ClassReader(byte[] bytes) {
+  private ClassReader(@Nullable String path, byte[] bytes) {
+    this.path = path;
     this.reader = new ByteReader(bytes, 0);
   }
 
+  @CheckReturnValue
+  Error error(String format, Object... args) {
+    StringBuilder sb = new StringBuilder();
+    if (path != null) {
+      sb.append(path).append(": ");
+    }
+    sb.append(String.format(format, args));
+    return new AssertionError(sb.toString());
+  }
+
   private ClassFile read() {
     int magic = reader.u4();
     if (magic != 0xcafebabe) {
-      throw new AssertionError(String.format("bad magic: 0x%x", magic));
+      throw error("bad magic: 0x%x", path, magic);
     }
     int minorVersion = reader.u2();
     int majorVersion = reader.u2();
-    if (majorVersion < 45 || majorVersion > 52) {
-      throw new AssertionError(String.format("bad version: %d.%d", majorVersion, minorVersion));
+    if (majorVersion < 45 || majorVersion > 53) {
+      throw error("bad version: %d.%d", majorVersion, minorVersion);
     }
     ConstantPoolReader constantPool = ConstantPoolReader.readConstantPool(reader);
     int accessFlags = reader.u2();
@@ -253,7 +273,7 @@
           break;
         }
       default:
-        throw new AssertionError(String.format("bad tag value %c", tag));
+        throw error("bad tag value %c", tag);
     }
     return null;
   }
diff --git a/java/com/google/turbine/deps/Dependencies.java b/java/com/google/turbine/deps/Dependencies.java
index 110d420..a6ac05f 100644
--- a/java/com/google/turbine/deps/Dependencies.java
+++ b/java/com/google/turbine/deps/Dependencies.java
@@ -52,6 +52,7 @@
       Lowered lowered) {
     DepsProto.Dependencies.Builder deps = DepsProto.Dependencies.newBuilder();
     Set<ClassSymbol> closure = superTypeClosure(bound, lowered);
+    addPackageInfos(closure, bound);
     Set<String> jars = new LinkedHashSet<>();
     for (ClassSymbol sym : closure) {
       BytecodeBoundClass info = bound.classPathEnv().get(sym);
@@ -108,6 +109,22 @@
     }
   }
 
+  private static void addPackageInfos(Set<ClassSymbol> closure, BindingResult bound) {
+    Set<ClassSymbol> packages = new LinkedHashSet<>();
+    for (ClassSymbol sym : closure) {
+      int idx = sym.binaryName().lastIndexOf('/');
+      if (idx == -1) {
+        continue;
+      }
+      packages.add(new ClassSymbol(sym.binaryName().substring(0, idx) + "/package-info"));
+    }
+    for (ClassSymbol pkg : packages) {
+      if (bound.classPathEnv().get(pkg) != null) {
+        closure.add(pkg);
+      }
+    }
+  }
+
   /**
    * Filters a transitive classpath to contain only the entries for direct dependencies, and the
    * types needed to compile those direct deps as reported by jdeps.
diff --git a/java/com/google/turbine/diag/TurbineError.java b/java/com/google/turbine/diag/TurbineError.java
index 9db3b67..22abd3e 100644
--- a/java/com/google/turbine/diag/TurbineError.java
+++ b/java/com/google/turbine/diag/TurbineError.java
@@ -39,7 +39,9 @@
     CANNOT_RESOLVE("cannot resolve %s"),
     EXPRESSION_ERROR("could not evaluate constant expression"),
     CYCLIC_HIERARCHY("cycle in class hierarchy: %s"),
-    NOT_AN_ANNOTATION("%s is not an annotation");
+    NOT_AN_ANNOTATION("%s is not an annotation"),
+    NONREPEATABLE_ANNOTATION("%s is not @Repeatable"),
+    DUPLICATE_DECLARATION("duplicate declaration of %s");
 
     private final String message;
 
diff --git a/java/com/google/turbine/parse/Parser.java b/java/com/google/turbine/parse/Parser.java
index 1ec8a38..88529de 100644
--- a/java/com/google/turbine/parse/Parser.java
+++ b/java/com/google/turbine/parse/Parser.java
@@ -481,8 +481,9 @@
         {
           result = new Tree.VoidTy(position);
           next();
+          int pos = position;
           name = eatIdent();
-          return memberRest(access, annos, typaram, result, name);
+          return memberRest(pos, access, annos, typaram, result, name);
         }
       case BOOLEAN:
       case BYTE:
@@ -494,17 +495,19 @@
       case FLOAT:
         {
           result = referenceType(ImmutableList.of());
+          int pos = position;
           name = eatIdent();
-          return memberRest(access, annos, typaram, result, name);
+          return memberRest(pos, access, annos, typaram, result, name);
         }
       case IDENT:
         {
+          int pos = position;
           String ident = eatIdent();
           switch (token) {
             case LPAREN:
               {
                 name = ident;
-                return ImmutableList.of(methodRest(access, annos, typaram, null, name));
+                return ImmutableList.of(methodRest(pos, access, annos, typaram, null, name));
               }
             case IDENT:
               {
@@ -515,8 +518,9 @@
                         ident,
                         ImmutableList.<Type>of(),
                         ImmutableList.of());
+                pos = position;
                 name = eatIdent();
-                return memberRest(access, annos, typaram, result, name);
+                return memberRest(pos, access, annos, typaram, result, name);
               }
             case AT:
             case LBRACK:
@@ -560,10 +564,11 @@
             result = classty((ClassTy) result);
           }
           result = maybeDims(maybeAnnos(), result);
+          pos = position;
           name = eatIdent();
           switch (token) {
             case LPAREN:
-              return ImmutableList.of(methodRest(access, annos, typaram, result, name));
+              return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
             case LBRACK:
             case SEMI:
             case ASSIGN:
@@ -572,7 +577,7 @@
                 if (!typaram.isEmpty()) {
                   throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
                 }
-                return fieldRest(access, annos, result, name);
+                return fieldRest(pos, access, annos, result, name);
               }
             default:
               throw error(token);
@@ -596,6 +601,7 @@
   }
 
   private ImmutableList<Tree> memberRest(
+      int pos,
       EnumSet<TurbineModifier> access,
       ImmutableList<Anno> annos,
       ImmutableList<TyParam> typaram,
@@ -610,17 +616,21 @@
           if (!typaram.isEmpty()) {
             throw error(ErrorKind.UNEXPECTED_TYPE_PARAMETER, typaram);
           }
-          return fieldRest(access, annos, result, name);
+          return fieldRest(pos, access, annos, result, name);
         }
       case LPAREN:
-        return ImmutableList.of(methodRest(access, annos, typaram, result, name));
+        return ImmutableList.of(methodRest(pos, access, annos, typaram, result, name));
       default:
         throw error(token);
     }
   }
 
   private ImmutableList<Tree> fieldRest(
-      EnumSet<TurbineModifier> access, ImmutableList<Anno> annos, Type baseTy, String name) {
+      int pos,
+      EnumSet<TurbineModifier> access,
+      ImmutableList<Anno> annos,
+      Type baseTy,
+      String name) {
     ImmutableList.Builder<Tree> result = ImmutableList.builder();
     VariableInitializerParser initializerParser = new VariableInitializerParser(token, lexer);
     List<List<SavedToken>> bits = initializerParser.parseInitializers();
@@ -642,13 +652,14 @@
       if (init != null && init.kind() == Tree.Kind.ARRAY_INIT) {
         init = null;
       }
-      result.add(new VarDecl(position, access, annos, ty, name, Optional.fromNullable(init)));
+      result.add(new VarDecl(pos, access, annos, ty, name, Optional.fromNullable(init)));
     }
     eat(Token.SEMI);
     return result.build();
   }
 
   private Tree methodRest(
+      int pos,
       EnumSet<TurbineModifier> access,
       ImmutableList<Anno> annos,
       ImmutableList<TyParam> typaram,
@@ -697,7 +708,7 @@
       name = CTOR_NAME;
     }
     return new MethDecl(
-        position,
+        pos,
         access,
         annos,
         typaram,
diff --git a/java/com/google/turbine/type/AnnoInfo.java b/java/com/google/turbine/type/AnnoInfo.java
index 4af2e66..c564f48 100644
--- a/java/com/google/turbine/type/AnnoInfo.java
+++ b/java/com/google/turbine/type/AnnoInfo.java
@@ -21,25 +21,40 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 import com.google.turbine.binder.sym.ClassSymbol;
+import com.google.turbine.diag.SourceFile;
 import com.google.turbine.model.Const;
+import com.google.turbine.tree.Tree;
+import com.google.turbine.tree.Tree.Anno;
 import com.google.turbine.tree.Tree.Expression;
 
 /** An annotation use. */
 public class AnnoInfo {
+  private final SourceFile source;
   private final ClassSymbol sym;
-  private final ImmutableList<Expression> args;
+  private final Tree.Anno tree;
   private final ImmutableMap<String, Const> values;
 
   public AnnoInfo(
-      ClassSymbol sym, ImmutableList<Expression> args, ImmutableMap<String, Const> values) {
+      SourceFile source, ClassSymbol sym, Anno tree, ImmutableMap<String, Const> values) {
+    this.source = source;
     this.sym = requireNonNull(sym);
-    this.args = args;
+    this.tree = tree;
     this.values = values;
   }
 
+  /** The annotation's source, for diagnostics. */
+  public SourceFile source() {
+    return source;
+  }
+
+  /** The annotation's diagnostic position. */
+  public int position() {
+    return tree.position();
+  }
+
   /** Arguments, either assignments or a single expression. */
   public ImmutableList<Expression> args() {
-    return args;
+    return tree.args();
   }
 
   /** Bound element-value pairs. */
@@ -51,4 +66,8 @@
   public ClassSymbol sym() {
     return sym;
   }
+
+  public AnnoInfo withValues(ImmutableMap<String, Const> values) {
+    return new AnnoInfo(source, sym, tree, values);
+  }
 }
diff --git a/javatests/com/google/turbine/binder/BinderErrorTest.java b/javatests/com/google/turbine/binder/BinderErrorTest.java
index c6535ca..102db93 100644
--- a/javatests/com/google/turbine/binder/BinderErrorTest.java
+++ b/javatests/com/google/turbine/binder/BinderErrorTest.java
@@ -257,7 +257,86 @@
           "  @Object int x;",
           "   ^",
         },
-      }
+      },
+      {
+        {
+          "public class Test {", //
+          "  @Deprecated @Deprecated int x;",
+          "}",
+        },
+        {
+          "<>:2: error: java/lang/Deprecated is not @Repeatable", //
+          "  @Deprecated @Deprecated int x;",
+          "   ^",
+        },
+      },
+      {
+        {
+          "public class Test {", //
+          "  @NoSuch.NoSuch int x;",
+          "}",
+        },
+        {
+          "<>:2: error: symbol not found NoSuch.NoSuch", //
+          "  @NoSuch.NoSuch int x;",
+          "   ^",
+        },
+      },
+      {
+        {
+          "public class Test {", //
+          "  @Deprecated.NoSuch int x;",
+          "}",
+        },
+        {
+          "<>:2: error: symbol not found NoSuch", //
+          "  @Deprecated.NoSuch int x;",
+          "   ^",
+        },
+      },
+      {
+        {
+          "public class Test {", //
+          "  @interface Anno {",
+          "    int[] value() default 0;",
+          "  }",
+          "  @Anno(value=Test.NO_SUCH) int x;",
+          "}",
+        },
+        {
+          "<>:5: error: could not evaluate constant expression", //
+          "  @Anno(value=Test.NO_SUCH) int x;",
+          "              ^",
+        },
+      },
+      {
+        {
+          "public class Test {", //
+          "  @interface Anno {",
+          "    String value() default \"\";",
+          "  }",
+          "  @Anno(value=null) int x;",
+          "}",
+        },
+        {
+          "<>:5: error: invalid annotation argument", //
+          "  @Anno(value=null) int x;",
+          "              ^",
+        },
+      },
+      {
+        {
+          "public class Test {", //
+          "  static final String x = 1;",
+          "  static final String x = 2;",
+          "}",
+        },
+        {
+          "<>:3: error: duplicate declaration of field: x", //
+          "  static final String x = 2;",
+          "                      ^",
+        },
+      },
     };
     return Arrays.asList((Object[][]) testCases);
   }
diff --git a/javatests/com/google/turbine/bytecode/ClassReaderTest.java b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
index 30e3d5b..dde37d5 100644
--- a/javatests/com/google/turbine/bytecode/ClassReaderTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassReaderTest.java
@@ -60,7 +60,7 @@
     cw.visitMethod(0, "h", "(I)V", null, null);
     byte[] bytes = cw.toByteArray();
 
-    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(bytes);
+    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes);
 
     assertThat(classFile.access())
         .isEqualTo(TurbineFlag.ACC_PUBLIC | TurbineFlag.ACC_FINAL | TurbineFlag.ACC_SUPER);
@@ -110,7 +110,7 @@
     cw.visitEnd();
     byte[] bytes = cw.toByteArray();
 
-    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(bytes);
+    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes);
 
     assertThat(classFile.access())
         .isEqualTo(
@@ -157,7 +157,7 @@
     cw.visitEnd();
     byte[] bytes = cw.toByteArray();
 
-    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(bytes);
+    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes);
 
     assertThat(classFile.fields()).hasSize(3);
 
@@ -193,7 +193,7 @@
     cw.visitInnerClass("test/Hello$Inner$InnerMost", "test/Hello$Inner", "InnerMost", 0);
     byte[] bytes = cw.toByteArray();
 
-    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(bytes);
+    ClassFile classFile = com.google.turbine.bytecode.ClassReader.read(null, bytes);
 
     assertThat(classFile.innerClasses()).hasSize(2);
 
@@ -217,7 +217,22 @@
     cw.visit(52, Opcodes.ACC_SUPER, jumbo, null, "java/lang/Object", null);
     byte[] bytes = cw.toByteArray();
 
-    ClassFile cf = ClassReader.read(bytes);
+    ClassFile cf = ClassReader.read(null, bytes);
     assertThat(cf.name()).isEqualTo(jumbo);
   }
+
+  @Test
+  public void v53() {
+    ClassWriter cw = new ClassWriter(0);
+    cw.visitAnnotation("Ljava/lang/Deprecated;", true);
+    cw.visit(
+        53,
+        Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL | Opcodes.ACC_SUPER,
+        "Hello",
+        null,
+        "java/lang/Object",
+        null);
+    ClassFile cf = ClassReader.read(null, cw.toByteArray());
+    assertThat(cf.name()).isEqualTo("Hello");
+  }
 }
diff --git a/javatests/com/google/turbine/bytecode/ClassWriterTest.java b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
index d95c00b..a2544fc 100644
--- a/javatests/com/google/turbine/bytecode/ClassWriterTest.java
+++ b/javatests/com/google/turbine/bytecode/ClassWriterTest.java
@@ -84,7 +84,7 @@
     assertThat(task.call()).named(collector.getDiagnostics().toString()).isTrue();
 
     byte[] original = Files.readAllBytes(out.resolve("test/Test.class"));
-    byte[] actual = ClassWriter.writeClass(ClassReader.read(original));
+    byte[] actual = ClassWriter.writeClass(ClassReader.read(null, original));
 
     assertThat(AsmUtils.textify(original)).isEqualTo(AsmUtils.textify(actual));
   }
diff --git a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
index 3c24de4..6335216 100644
--- a/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
+++ b/javatests/com/google/turbine/deps/AbstractTransitiveTest.java
@@ -129,7 +129,7 @@
             "META-INF/TRANSITIVE/a/A$Anno.class",
             "META-INF/TRANSITIVE/a/A$Inner.class");
 
-    ClassFile a = ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
+    ClassFile a = ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A.class"));
     // methods and non-constant fields are removed
     assertThat(getOnlyElement(a.fields()).name()).isEqualTo("CONST");
     assertThat(a.methods()).isEmpty();
@@ -137,7 +137,9 @@
         .containsExactly("a/A$Anno", "a/A$Inner");
 
     // annotation interface methods are preserved
-    assertThat(ClassReader.read(readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class")).methods())
+    assertThat(
+            ClassReader.read(null, readJar(libb).get("META-INF/TRANSITIVE/a/A$Anno.class"))
+                .methods())
         .hasSize(1);
 
     // A class that references members of the transitive supertype A by simple name
diff --git a/javatests/com/google/turbine/deps/DependenciesTest.java b/javatests/com/google/turbine/deps/DependenciesTest.java
index 6a882f9..dd94650 100644
--- a/javatests/com/google/turbine/deps/DependenciesTest.java
+++ b/javatests/com/google/turbine/deps/DependenciesTest.java
@@ -296,6 +296,50 @@
         .inOrder();
   }
 
+  @Test
+  public void packageInfo() throws Exception {
+    Path libpackageInfo =
+        new LibraryBuilder()
+            .addSourceLines(
+                "p/Anno.java",
+                "package p;",
+                "import java.lang.annotation.Retention;",
+                "import static java.lang.annotation.RetentionPolicy.RUNTIME;",
+                "@Retention(RUNTIME)",
+                "@interface Anno {}")
+            .addSourceLines(
+                "p/package-info.java", //
+                "@Anno",
+                "package p;")
+            .compileToJar("libpackage-info.jar");
+    Path libp =
+        new LibraryBuilder()
+            .setClasspath(libpackageInfo)
+            .addSourceLines(
+                "p/P.java", //
+                "package p;",
+                "public class P {}")
+            .compileToJar("libp.jar");
+    {
+      DepsProto.Dependencies deps =
+          new DepsBuilder()
+              .setClasspath(libp, libpackageInfo)
+              .addSourceLines(
+                  "Test.java", //
+                  "import p.P;",
+                  "class Test {",
+                  "  P p;",
+                  "}")
+              .run();
+      assertThat(depsMap(deps))
+          .containsExactly(
+              libpackageInfo,
+              DepsProto.Dependency.Kind.EXPLICIT,
+              libp,
+              DepsProto.Dependency.Kind.EXPLICIT);
+    }
+  }
+
   void writeDeps(Path path, ImmutableMap<String, DepsProto.Dependency.Kind> deps)
       throws IOException {
     DepsProto.Dependencies.Builder builder =
diff --git a/pom.xml b/pom.xml
index ade3cef..6829bce 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,7 +69,7 @@
     <dependency>
       <groupId>com.google.truth</groupId>
       <artifactId>truth</artifactId>
-      <version>0.25</version>
+      <version>0.36</version>
       <scope>test</scope>
     </dependency>
     <dependency>