Merge commit '180e357dde825969b7839774ecacf4c4cae4cf05' into dev-release
diff --git a/infra/config/global/luci-scheduler.cfg b/infra/config/global/luci-scheduler.cfg
index dd2df27..ab75708 100644
--- a/infra/config/global/luci-scheduler.cfg
+++ b/infra/config/global/luci-scheduler.cfg
@@ -70,6 +70,7 @@
   triggers: "linux-android-7.0.0_release"
   triggers: "linux-android-8.1.0_release"
   triggers: "linux-android-9.0.0_release"
+  triggers: "linux-android-10.0.0_release"
   triggers: "linux-internal_release"
   triggers: "linux-jctf_release"
   triggers: "linux-run-on-as-app_release"
@@ -78,17 +79,6 @@
   triggers: "windows_release"
 }
 
-trigger {
-  id: "branch-gitiles-trigger-10"
-  acl_sets: "default"
-  gitiles: {
-    repo: "https://r8.googlesource.com/r8"
-    refs: "refs/heads/2.0"
-    path_regexps: "src/main/java/com/android/tools/r8/Version.java"
-  }
-  triggers: "linux-android-10.0.0_release"
-}
-
 
 job {
   id: "archive"
@@ -160,6 +150,9 @@
 job {
   id: "linux-android-4.0.4_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -183,6 +176,9 @@
 job {
   id: "linux-android-4.4.4_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -206,6 +202,9 @@
 job {
   id: "linux-android-5.1.1_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -229,6 +228,9 @@
 job {
   id: "linux-android-6.0.1_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -252,6 +254,9 @@
 job {
   id: "linux-android-7.0.0_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -276,7 +281,7 @@
   id: "linux-android-8.1.0_release"
   acl_sets: "default"
   triggering_policy: {
-    max_concurrent_invocations: 2
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -303,7 +308,7 @@
   id: "linux-android-9.0.0_release"
   acl_sets: "default"
   triggering_policy: {
-    max_concurrent_invocations: 2
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -329,7 +334,7 @@
   id: "linux-android-10.0.0_release"
   acl_sets: "default"
   triggering_policy: {
-    max_concurrent_invocations: 2
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
@@ -381,6 +386,9 @@
 job {
   id: "linux-run-on-as-app_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -402,6 +410,9 @@
 job {
   id: "linux-jctf_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -412,6 +423,9 @@
 job {
   id: "linux_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -432,6 +446,9 @@
 job {
   id: "r8cf-linux-jctf_release"
   acl_sets: "default"
+  triggering_policy: {
+    max_batch_size: 1
+  }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
     bucket: "luci.r8.ci"
@@ -456,7 +473,7 @@
   id: "windows_release"
   acl_sets: "default"
   triggering_policy: {
-    max_concurrent_invocations: 3
+    max_batch_size: 1
   }
   buildbucket {
     server: "cr-buildbucket.appspot.com"
diff --git a/src/library_desugar/desugar_jdk_libs.json b/src/library_desugar/desugar_jdk_libs.json
index 07765bb..8c023aa 100644
--- a/src/library_desugar/desugar_jdk_libs.json
+++ b/src/library_desugar/desugar_jdk_libs.json
@@ -2,7 +2,7 @@
   "configuration_format_version": 4,
   "group_id" : "com.tools.android",
   "artifact_id" : "desugar_jdk_libs",
-  "version": "0.11.2",
+  "version": "0.12.0",
   "required_compilation_api_level": 26,
   "synthesized_library_classes_package_prefix": "j$.",
   "library_flags": [
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index c443620..c40d755 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -26,6 +26,7 @@
 import com.android.tools.r8.jar.CfApplicationWriter;
 import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.utils.AndroidApp;
@@ -256,6 +257,8 @@
               hasDexResources
                   ? NamingLens.getIdentityLens()
                   : PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+          new GenericSignatureRewriter(appView, namingLens)
+              .run(appView.appInfo().classes(), executor);
         } else {
           // There are both cf and dex inputs in the program, and rewriting is required for
           // desugared library only on cf inputs. We cannot easily rewrite part of the program
@@ -315,6 +318,10 @@
     }
     DexApplication cfApp = app.builder().replaceProgramClasses(nonDexProgramClasses).build();
     ConvertedCfFiles convertedCfFiles = new ConvertedCfFiles();
+    NamingLens prefixRewritingNamingLens =
+        PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+    new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+        .run(appView.appInfo().classes(), executor);
     new ApplicationWriter(
             cfApp,
             null,
@@ -322,7 +329,7 @@
             null,
             GraphLense.getIdentityLense(),
             InitClassLens.getDefault(),
-            PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+            prefixRewritingNamingLens,
             null,
             convertedCfFiles)
         .write(executor);
diff --git a/src/main/java/com/android/tools/r8/L8.java b/src/main/java/com/android/tools/r8/L8.java
index 4ba88f4..4bef8cec 100644
--- a/src/main/java/com/android/tools/r8/L8.java
+++ b/src/main/java/com/android/tools/r8/L8.java
@@ -15,7 +15,9 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.ir.desugar.PrefixRewritingMapper;
 import com.android.tools.r8.jar.CfApplicationWriter;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.naming.PrefixRewritingNamingLens;
+import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.origin.CommandLineOrigin;
 import com.android.tools.r8.shaking.AnnotationRemover;
 import com.android.tools.r8.shaking.L8TreePruner;
@@ -132,13 +134,16 @@
       app = converter.convert(app, executor);
       assert appView.appInfo() == appInfo;
 
+      NamingLens namingLens = PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView);
+      new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
+
       new CfApplicationWriter(
               app,
               appView,
               options,
               options.getMarker(Tool.L8),
               GraphLense.getIdentityLense(),
-              PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView),
+              namingLens,
               null)
           .write(options.getClassFileConsumer());
       options.printWarnings();
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index 5fe37ab..c94d67c 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -120,7 +120,7 @@
 
     @Override
     public boolean registerInvokeStatic(DexMethod method) {
-      DexEncodedMethod target = appInfo.lookupStaticTarget(method, context);
+      DexEncodedMethod target = appInfo.unsafeResolveMethodDueToDexFormat(method).getSingleTarget();
       if (target != null && target.method != method) {
         addType(method.holder);
         addMethod(target.method);
@@ -148,13 +148,13 @@
 
     @Override
     public boolean registerInstanceFieldWrite(DexField field) {
-      addField(field, false);
+      addField(field);
       return false;
     }
 
     @Override
     public boolean registerInstanceFieldRead(DexField field) {
-      addField(field, false);
+      addField(field);
       return false;
     }
 
@@ -166,13 +166,13 @@
 
     @Override
     public boolean registerStaticFieldRead(DexField field) {
-      addField(field, true);
+      addField(field);
       return false;
     }
 
     @Override
     public boolean registerStaticFieldWrite(DexField field) {
-      addField(field, true);
+      addField(field);
       return false;
     }
 
@@ -200,10 +200,9 @@
       return descriptors.contains(type.toDescriptorString());
     }
 
-    private void addField(DexField field, boolean isStatic) {
+    private void addField(DexField field) {
       addType(field.type);
-      DexEncodedField baseField =
-          isStatic ? appInfo.lookupStaticTarget(field) : appInfo.lookupInstanceTarget(field);
+      DexEncodedField baseField = appInfo.resolveField(field).getResolvedField();
       if (baseField != null && baseField.holder() != field.holder) {
         field = baseField.field;
       }
@@ -229,13 +228,14 @@
       addType(method.proto.returnType);
       Set<DexMethod> typeMethods = methods.get(method.holder);
       if (typeMethods != null) {
-        DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
-        assert encodedMethod != null : "Could not find method " + method.toString();
+        DexClass holder = appInfo.definitionForHolder(method);
+        DexEncodedMethod definition = method.lookupOnClass(holder);
+        assert definition != null : "Could not find method " + method.toString();
         if (!allowObfuscation) {
           noObfuscationTypes.add(method.holder);
         }
-        if (encodedMethod.accessFlags.isVisibilityDependingOnPackage()) {
-          keepPackageNames.add(encodedMethod.holder().getPackageName());
+        if (definition.accessFlags.isVisibilityDependingOnPackage()) {
+          keepPackageNames.add(definition.holder().getPackageName());
         }
         typeMethods.add(method);
       }
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 34677e0..8f6bcca 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -802,12 +802,6 @@
         namingLens = new Minifier(appView.withLiveness()).run(executorService, timing);
         timing.end();
       } else {
-        // Rewrite signature annotations for applications that are not minified.
-        if (appView.appInfo().hasLiveness()) {
-          // TODO(b/124726014): Rewrite signature annotations in lens rewriting instead of here?
-          new GenericSignatureRewriter(appView.withLiveness())
-              .run(appView.appInfo().classes(), executorService);
-        }
         namingLens = NamingLens.getIdentityLens();
       }
 
@@ -839,6 +833,7 @@
       }
 
       // Validity checks.
+      assert application.asDirect().verifyCodeObjectsOwners();
       assert application.classes().stream().allMatch(clazz -> clazz.isValid(options));
       if (options.isShrinking()
           || options.isMinifying()
@@ -859,6 +854,12 @@
         options.syntheticProguardRulesConsumer.accept(synthesizedProguardRules);
       }
 
+      NamingLens prefixRewritingNamingLens =
+          PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens);
+
+      new GenericSignatureRewriter(appView, prefixRewritingNamingLens)
+          .run(appView.appInfo().classes(), executorService);
+
       // Generate the resulting application resources.
       writeApplication(
           executorService,
@@ -866,7 +867,7 @@
           appView,
           appView.graphLense(),
           appView.initClassLens(),
-          PrefixRewritingNamingLens.createPrefixRewritingNamingLens(appView, namingLens),
+          prefixRewritingNamingLens,
           options,
           ProguardMapSupplier.create(classNameMapper, options));
 
@@ -975,9 +976,7 @@
           new StringDiagnostic(
               "Item " + definition.toSourceString() + " was not discarded.\n" + baos.toString()));
     }
-    if (!options.testing.allowCheckDiscardedErrors) {
-      throw new CompilationError("Discard checks failed.");
-    }
+    throw new CompilationError("Discard checks failed.");
   }
 
   private static boolean verifyNoJarApplicationReaders(List<DexProgramClass> classes) {
diff --git a/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java b/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java
new file mode 100644
index 0000000..c705c8c
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/algorithms/scc/SCC.java
@@ -0,0 +1,74 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.algorithms.scc;
+
+import com.android.tools.r8.utils.SetUtils;
+import com.google.common.collect.Sets;
+import it.unimi.dsi.fastutil.objects.Reference2IntMap;
+import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Set;
+import java.util.function.Function;
+
+public class SCC<Node> {
+
+  private int currentTime = 0;
+  private final Reference2IntMap<Node> discoverTime = new Reference2IntOpenHashMap<>();
+  private final Set<Node> unassignedSet = Sets.newIdentityHashSet();
+  private final Deque<Node> unassignedStack = new ArrayDeque<>();
+  private final Deque<Node> preorderStack = new ArrayDeque<>();
+  private final List<Set<Node>> components = new ArrayList<>();
+
+  private final Function<Node, Iterable<? extends Node>> successors;
+
+  public SCC(Function<Node, Iterable<? extends Node>> successors) {
+    this.successors = successors;
+  }
+
+  public List<Set<Node>> computeSCC(Node v) {
+    assert currentTime == 0;
+    dfs(v);
+    return components;
+  }
+
+  private void dfs(Node value) {
+    discoverTime.put(value, currentTime++);
+    unassignedSet.add(value);
+    unassignedStack.push(value);
+    preorderStack.push(value);
+    for (Node successor : successors.apply(value)) {
+      if (!discoverTime.containsKey(successor)) {
+        // If not seen yet, continue the search.
+        dfs(successor);
+      } else if (unassignedSet.contains(successor)) {
+        // If seen already and the element is on the unassigned stack we have found a cycle.
+        // Pop off everything discovered later than the target from the preorder stack. This may
+        // not coincide with the cycle as an outer cycle may already have popped elements off.
+        int discoverTimeOfPhi = discoverTime.getInt(successor);
+        while (discoverTimeOfPhi < discoverTime.getInt(preorderStack.peek())) {
+          preorderStack.pop();
+        }
+      }
+    }
+    if (preorderStack.peek() == value) {
+      // If the current element is the top of the preorder stack, then we are at entry to a
+      // strongly-connected component consisting of this element and every element above this
+      // element on the stack.
+      Set<Node> component = SetUtils.newIdentityHashSet(unassignedStack.size());
+      while (true) {
+        Node member = unassignedStack.pop();
+        unassignedSet.remove(member);
+        component.add(member);
+        if (member == value) {
+          components.add(component);
+          break;
+        }
+      }
+      preorderStack.pop();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
index b47570c..8889e81 100644
--- a/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
+++ b/src/main/java/com/android/tools/r8/cf/code/CfInvoke.java
@@ -286,7 +286,7 @@
       // default interface methods, it is expected they are targeted with invoke-direct.
       return this.itf && desugaringEnabled ? Type.DIRECT : Type.SUPER;
     }
-    if (!encodedMethod.isNonPrivateVirtualMethod()) {
+    if (encodedMethod.isPrivateMethod() || !encodedMethod.isVirtualMethod()) {
       return Type.DIRECT;
     }
     if (encodedMethod.accessFlags.isFinal()) {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfo.java b/src/main/java/com/android/tools/r8/graph/AppInfo.java
index 3706dec..60b8220 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -161,21 +161,6 @@
     return definition == null ? Origin.unknown() : definition.origin;
   }
 
-  @Deprecated
-  @Override
-  public DexEncodedMethod definitionFor(DexMethod method) {
-    assert checkIfObsolete();
-    assert method.holder.isClassType();
-    if (!method.holder.isClassType()) {
-      return null;
-    }
-    DexClass clazz = definitionFor(method.holder);
-    if (clazz == null) {
-      return null;
-    }
-    return clazz.getMethodCollection().getMethod(method);
-  }
-
   /**
    * Lookup static method on the method holder, or answers null.
    *
diff --git a/src/main/java/com/android/tools/r8/graph/AppView.java b/src/main/java/com/android/tools/r8/graph/AppView.java
index b4534f5..9763f50 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -226,12 +226,6 @@
     return this.sourceDebugExtensions.get(clazz);
   }
 
-  @Deprecated
-  @Override
-  public final DexEncodedMethod definitionFor(DexMethod method) {
-    return appInfo().definitionFor(method);
-  }
-
   @Override
   public final DexClass definitionFor(DexType type) {
     return appInfo().definitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index 09aeb11..1825fb3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -15,6 +15,7 @@
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Iterators;
 import com.google.common.collect.Sets;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -827,6 +828,16 @@
     return superClass == null || superClass.hasInstanceFieldsDirectlyOrIndirectly(appView);
   }
 
+  public List<DexEncodedField> getDirectAndIndirectInstanceFields(AppView<?> appView) {
+    List<DexEncodedField> result = new ArrayList<>();
+    DexClass current = this;
+    while (current != null && current.type != appView.dexItemFactory().objectType) {
+      result.addAll(current.instanceFields());
+      current = appView.definitionFor(current.superType);
+    }
+    return result;
+  }
+
   public boolean isValid(InternalOptions options) {
     assert verifyNoAbstractMethodsOnNonAbstractClasses(virtualMethods(), options);
     assert !isInterface() || !getMethodCollection().hasVirtualMethods(DexEncodedMethod::isFinal);
diff --git a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
index 214e41c..2ec6dbf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
+++ b/src/main/java/com/android/tools/r8/graph/DexDefinitionSupplier.java
@@ -6,9 +6,6 @@
 
 public interface DexDefinitionSupplier {
 
-  @Deprecated
-  DexEncodedMethod definitionFor(DexMethod method);
-
   DexClass definitionFor(DexType type);
 
   DexProgramClass definitionForProgramType(DexType type);
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
index c12f3f4..74a31bf 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedMethod.java
@@ -250,6 +250,18 @@
     assert parameterAnnotationsList != null;
   }
 
+  public DexType getHolderType() {
+    return getReference().holder;
+  }
+
+  public DexString getName() {
+    return getReference().name;
+  }
+
+  public DexProto getProto() {
+    return getReference().proto;
+  }
+
   public DexMethod getReference() {
     return method;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d61746d..8b2bd65 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -764,6 +764,8 @@
 
     public final DexMethod booleanValue =
         createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+    public final DexMethod parseBoolean =
+        createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
     public final DexMethod valueOf =
         createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
 
@@ -1887,6 +1889,10 @@
     return createProto(proto.returnType, parameterTypes);
   }
 
+  public DexProto prependHolderToProto(DexMethod method) {
+    return prependTypeToProto(method.holder, method.proto);
+  }
+
   public DexProto prependTypeToProto(DexType extraFirstType, DexProto initialProto) {
     DexType[] parameterTypes = new DexType[initialProto.parameters.size() + 1];
     parameterTypes[0] = extraFirstType;
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 3699d2e..ab9be40 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,13 +73,6 @@
     return classpathClasses;
   }
 
-  @Deprecated
-  @Override
-  public DexEncodedMethod definitionFor(DexMethod method) {
-    DexClass clazz = definitionFor(method.holder);
-    return clazz != null ? clazz.lookupMethod(method) : null;
-  }
-
   @Override
   public DexClass definitionFor(DexType type) {
     assert type.isClassType() : "Cannot lookup definition for type: " + type;
@@ -173,7 +166,7 @@
     return computeCodeObjectOwnersForDebugging().get(code);
   }
 
-  private boolean verifyCodeObjectsOwners() {
+  public boolean verifyCodeObjectsOwners() {
     codeOwners.clear();
     for (DexProgramClass clazz : programClasses) {
       for (DexEncodedMethod method :
diff --git a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
index 5a8f39f..3c7b2d8 100644
--- a/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
+++ b/src/main/java/com/android/tools/r8/graph/LookupCompletenessHelper.java
@@ -21,7 +21,7 @@
   }
 
   void checkClass(DexClass clazz) {
-    if (pinnedPredicate.isPinned(clazz.type)) {
+    if (pinnedPredicate.isPinned(clazz)) {
       if (pinnedInstantiations == null) {
         pinnedInstantiations = Sets.newIdentityHashSet();
       }
@@ -30,7 +30,7 @@
   }
 
   void checkMethod(DexEncodedMethod method) {
-    if (pinnedPredicate.isPinned(method.method)) {
+    if (pinnedPredicate.isPinned(method)) {
       if (pinnedMethods == null) {
         pinnedMethods = Sets.newIdentityHashSet();
       }
@@ -70,7 +70,7 @@
       }
       DexEncodedMethod methodInClass = parent.lookupVirtualMethod(method);
       if (methodInClass != null
-          && (parent.isNotProgramClass() || pinnedPredicate.isPinned(methodInClass.method))) {
+          && (parent.isNotProgramClass() || pinnedPredicate.isPinned(methodInClass))) {
         return true;
       }
       if (parent.superType != null) {
diff --git a/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
index d81becc..5828fd5 100644
--- a/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
+++ b/src/main/java/com/android/tools/r8/graph/PinnedPredicate.java
@@ -7,5 +7,5 @@
 @FunctionalInterface
 public interface PinnedPredicate {
 
-  boolean isPinned(DexReference reference);
+  boolean isPinned(DexDefinition reference);
 }
diff --git a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
index fab752df..d8eab37 100644
--- a/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
+++ b/src/main/java/com/android/tools/r8/graph/ResolutionResult.java
@@ -70,6 +70,11 @@
   public abstract OptionalBool isAccessibleForVirtualDispatchFrom(
       DexProgramClass context, AppInfoWithClassHierarchy appInfo);
 
+  public final OptionalBool isAccessibleForVirtualDispatchFrom(
+      ProgramMethod context, AppInfoWithClassHierarchy appInfo) {
+    return isAccessibleForVirtualDispatchFrom(context.getHolder(), appInfo);
+  }
+
   public abstract boolean isVirtualTarget();
 
   /** Lookup the single target of an invoke-special on this resolution result if possible. */
@@ -367,8 +372,7 @@
         // This is assuming that the method is accessible, which implies self/nest access.
         // Only include if the target has code or is native.
         boolean isIncomplete =
-            pinnedPredicate.isPinned(resolvedHolder.type)
-                && pinnedPredicate.isPinned(resolvedMethod.method);
+            pinnedPredicate.isPinned(resolvedHolder) && pinnedPredicate.isPinned(resolvedMethod);
         return LookupResult.createResult(
             Collections.singletonMap(
                 resolvedMethod, DexClassAndMethod.create(resolvedHolder, resolvedMethod)),
@@ -536,18 +540,17 @@
         LambdaDescriptor lambdaInstance, AppInfoWithClassHierarchy appInfo) {
       if (lambdaInstance.getMainMethod().match(resolvedMethod)) {
         DexMethod method = lambdaInstance.implHandle.asMethod();
-        DexClass holder = appInfo.definitionFor(method.holder);
+        DexClass holder = appInfo.definitionForHolder(method);
         if (holder == null) {
           assert false;
           return null;
         }
-        DexEncodedMethod encodedMethod = appInfo.definitionFor(method);
-        if (encodedMethod == null) {
+        DexEncodedMethod definition = holder.lookupMethod(method);
+        if (definition == null) {
           // The targeted method might not exist, eg, Throwable.addSuppressed in an old library.
           return null;
         }
-        return new LookupLambdaTarget(
-            lambdaInstance, DexClassAndMethod.create(holder, encodedMethod));
+        return new LookupLambdaTarget(lambdaInstance, DexClassAndMethod.create(holder, definition));
       }
       return lookupMaximallySpecificDispatchTarget(lambdaInstance, appInfo);
     }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
index 41f5dc8..416f064 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ClassInitializationAnalysis.java
@@ -297,7 +297,9 @@
           return false;
         }
       }
-      DexEncodedMethod method = appView.definitionFor(instruction.getInvokedMethod());
+      DexMethod invokedMethod = instruction.getInvokedMethod();
+      DexClass holder = appView.definitionForHolder(invokedMethod);
+      DexEncodedMethod method = invokedMethod.lookupOnClass(holder);
       return method != null && isTypeInitializedBy(instruction, type, method, appView, mode);
     }
 
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
index 01d86a1..29d38c6 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/ValueMayDependOnEnvironmentAnalysis.java
@@ -12,12 +12,15 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
+import com.android.tools.r8.ir.analysis.environmentdependence.ValueGraph;
+import com.android.tools.r8.ir.analysis.environmentdependence.ValueGraph.Node;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
 import com.android.tools.r8.ir.code.ArrayPut;
+import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeNewArray;
 import com.android.tools.r8.ir.code.NewArrayEmpty;
@@ -26,9 +29,14 @@
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.info.initializer.InstanceInitializerInfo;
-import com.android.tools.r8.utils.LongInterval;
-import com.google.common.collect.ImmutableSet;
+import com.android.tools.r8.utils.SetUtils;
+import com.android.tools.r8.utils.WorkList;
 import com.google.common.collect.Sets;
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -68,219 +76,175 @@
 public class ValueMayDependOnEnvironmentAnalysis {
 
   private final AppView<?> appView;
-  private final IRCode code;
   private final ProgramMethod context;
 
-  private final Set<Value> knownNotToDependOnEnvironment = Sets.newIdentityHashSet();
-  private final Set<Value> visited = Sets.newIdentityHashSet();
-
   public ValueMayDependOnEnvironmentAnalysis(AppView<?> appView, IRCode code) {
     this.appView = appView;
-    this.code = code;
     this.context = code.context();
   }
 
-  public boolean valueMayDependOnEnvironment(Value value) {
-    boolean result = valueMayDependOnEnvironment(value, Sets.newIdentityHashSet());
-    assert visited.isEmpty();
-    return result;
-  }
+  public boolean anyValueMayDependOnEnvironment(Iterable<Value> values) {
+    ValueGraph graph = new ValueGraph();
+    Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
+    Set<Value> mutableValues = Sets.newIdentityHashSet();
+    WorkList<Value> worklist = WorkList.newIdentityWorkList(values);
+    while (worklist.hasNext()) {
+      Value value = worklist.next();
+      Value root = value.getAliasedValue();
+      Node node = graph.createNodeIfAbsent(root);
+      if (root != value) {
+        // An alias depends on the environment if the aliased value depends on the environment, thus
+        // an edge is added from the alias to the aliased value.
+        graph.addDirectedEdge(graph.createNodeIfAbsent(value), node);
+      }
+      if (!addValueToValueGraph(root, node, graph, consumedInstructions, mutableValues, worklist)) {
+        return true;
+      }
+    }
 
-  private boolean valueMayDependOnEnvironment(
-      Value value, Set<Value> assumedNotToDependOnEnvironment) {
-    Value root = value.getAliasedValue();
-    if (assumedNotToDependOnEnvironment.contains(root)) {
-      return false;
-    }
-    if (knownNotToDependOnEnvironment.contains(root)) {
-      return false;
-    }
-    if (!visited.add(root)) {
-      // Guard against cycle by conservatively returning true.
-      return true;
-    }
-    try {
-      if (root.isConstant()) {
-        return false;
-      }
-      if (isConstantArrayThroughoutMethod(root, assumedNotToDependOnEnvironment)) {
-        return false;
-      }
-      AbstractValue abstractValue = root.getAbstractValue(appView, context);
-      if (abstractValue.isSingleFieldValue()) {
-        DexField fieldReference = abstractValue.asSingleFieldValue().getField();
-        DexClass holder = appView.definitionForHolder(fieldReference);
-        DexEncodedField field = fieldReference.lookupOnClass(holder);
-        if (field != null && field.isEnum()) {
-          return false;
+    // At this point, the graph has been populated with a node for each value of interest, and edges
+    // have been added to reflect the dependency. We now attempt to prove that no values depend on
+    // environment, starting from the leaves of the graph.
+    //
+    // First we collapse strongly connected components in the graph. By doing so we will attempt to
+    // prove that all values in a strongly connected component are independent of the environment at
+    // once.
+    graph.mergeStronglyConnectedComponents();
+
+    Set<Node> nodesDependentOnEnvironment = SetUtils.newIdentityHashSet(graph.getNodes());
+    while (!nodesDependentOnEnvironment.isEmpty()) {
+      Set<Node> newNodesIndependentOfEnvironment = Sets.newIdentityHashSet();
+      for (Node node : nodesDependentOnEnvironment) {
+        boolean isDependentOfEnvironment =
+            node.hasSuccessorThatMatches(
+                successor ->
+                    nodesDependentOnEnvironment.contains(successor)
+                        && !newNodesIndependentOfEnvironment.contains(successor));
+        if (!isDependentOfEnvironment) {
+          newNodesIndependentOfEnvironment.add(node);
         }
       }
-      if (isNewInstanceWithoutEnvironmentDependentFields(root, assumedNotToDependOnEnvironment)) {
-        return false;
+      if (newNodesIndependentOfEnvironment.isEmpty()) {
+        return true;
       }
+      nodesDependentOnEnvironment.removeAll(newNodesIndependentOfEnvironment);
+    }
+
+    // At this point, we have proved that all values in the graph are independent on the
+    // environment. However, we still need to prove that they are not mutated between the point
+    // where they are defined and all normal exits.
+    return anyValueMayBeMutatedBeforeMethodExit(mutableValues, consumedInstructions);
+  }
+
+  private boolean addValueToValueGraph(
+      Value value,
+      Node node,
+      ValueGraph graph,
+      Set<Instruction> consumedInstructions,
+      Set<Value> mutableValues,
+      WorkList<Value> worklist) {
+    return addConstantValueToValueGraph(value)
+        || addArrayValueToValueGraph(
+            value, node, graph, consumedInstructions, mutableValues, worklist)
+        || addNewInstanceValueToValueGraph(
+            value, node, graph, consumedInstructions, mutableValues, worklist);
+  }
+
+  private boolean addConstantValueToValueGraph(Value value) {
+    // Constants do not depend on any other values, thus no edges are added to the graph.
+    if (value.isConstant()) {
       return true;
-    } finally {
-      boolean changed = visited.remove(root);
-      assert changed;
     }
-  }
-
-  private boolean valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
-      Value value, Value array, Set<Value> assumedNotToDependOnEnvironment) {
-    assert !value.hasAliasedValue();
-    assert !array.hasAliasedValue();
-
-    if (assumedNotToDependOnEnvironment.add(array)) {
-      boolean valueMayDependOnEnvironment =
-          valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
-      boolean changed = assumedNotToDependOnEnvironment.remove(array);
-      assert changed;
-      return !valueMayDependOnEnvironment;
+    assert !value.getAliasedValue().isConstant();
+    AbstractValue abstractValue = value.getAbstractValue(appView, context);
+    if (abstractValue.isSingleConstValue()) {
+      return true;
     }
-    return !valueMayDependOnEnvironment(value, assumedNotToDependOnEnvironment);
+    if (abstractValue.isSingleFieldValue()) {
+      DexField fieldReference = abstractValue.asSingleFieldValue().getField();
+      DexClass holder = appView.definitionForHolder(fieldReference);
+      DexEncodedField field = fieldReference.lookupOnClass(holder);
+      if (field != null && field.isEnum()) {
+        return true;
+      }
+    }
+    return false;
   }
 
-  /**
-   * Used to identify if an array is "constant" in the sense that none of the values written into
-   * the array may depend on the environment.
-   *
-   * <p>Examples include {@code new int[] {1,2,3}} and {@code new Object[]{new Object()}}.
-   */
-  public boolean isConstantArrayThroughoutMethod(Value value) {
-    boolean result = isConstantArrayThroughoutMethod(value, Sets.newIdentityHashSet());
-    assert visited.isEmpty();
-    return result;
-  }
-
-  private boolean isConstantArrayThroughoutMethod(
-      Value value, Set<Value> assumedNotToDependOnEnvironment) {
-    Value root = value.getAliasedValue();
-    if (root.isPhi()) {
+  private boolean addArrayValueToValueGraph(
+      Value value,
+      Node node,
+      ValueGraph graph,
+      Set<Instruction> consumedInstructions,
+      Set<Value> mutableValues,
+      WorkList<Value> worklist) {
+    if (value.isPhi()) {
       // Would need to track the aliases, just give up.
       return false;
     }
 
-    Instruction definition = root.definition;
+    Instruction definition = value.definition;
 
     // Check that it is a constant array with a known size at this point in the IR.
-    long size;
     if (definition.isInvokeNewArray()) {
       InvokeNewArray invokeNewArray = definition.asInvokeNewArray();
       for (Value argument : invokeNewArray.arguments()) {
-        if (!argument.isConstant()) {
-          return false;
-        }
+        graph.addDirectedEdge(node, graph.createNodeIfAbsent(argument));
+        worklist.addIfNotSeen(argument);
       }
-      size = invokeNewArray.arguments().size();
     } else if (definition.isNewArrayEmpty()) {
       NewArrayEmpty newArrayEmpty = definition.asNewArrayEmpty();
-      Value sizeValue = newArrayEmpty.size().getAliasedValue();
-      if (!sizeValue.hasValueRange()) {
-        return false;
-      }
-      LongInterval sizeRange = sizeValue.getValueRange();
-      if (!sizeRange.isSingleValue()) {
-        return false;
-      }
-      size = sizeRange.getSingleValue();
+      Value sizeValue = newArrayEmpty.size();
+      graph.addDirectedEdge(node, graph.createNodeIfAbsent(sizeValue));
+      worklist.addIfNotSeen(sizeValue);
     } else {
       // Some other array creation.
       return false;
     }
 
-    if (size < 0) {
-      // Check for NegativeArraySizeException.
-      return false;
-    }
-
-    if (size == 0) {
-      // Empty arrays are always constant.
-      return true;
-    }
-
     // Allow array-put and new-array-filled-data instructions that immediately follow the array
     // creation.
-    Set<Value> arrayValues = Sets.newIdentityHashSet();
-    Set<Instruction> consumedInstructions = Sets.newIdentityHashSet();
-
     for (Instruction instruction : definition.getBlock().instructionsAfter(definition)) {
       if (instruction.isArrayPut()) {
         ArrayPut arrayPut = instruction.asArrayPut();
         Value array = arrayPut.array().getAliasedValue();
-        if (array != root) {
+        if (array != value) {
           // This ends the chain of array-put instructions that are allowed immediately after the
           // array creation.
           break;
         }
-
-        LongInterval indexRange = arrayPut.index().getValueRange();
-        if (!indexRange.isSingleValue()) {
-          return false;
-        }
-
-        long index = indexRange.getSingleValue();
-        if (index < 0 || index >= size) {
-          return false;
-        }
-
-        // Check if the value being written into the array may depend on the environment.
-        //
-        // When analyzing if the value may depend on the environment, we assume that the current
-        // array does not depend on the environment. Otherwise, we would classify the value as
-        // possibly depending on the environment since it could escape via the array and then
-        // be mutated indirectly.
-        Value rhs = arrayPut.value().getAliasedValue();
-        if (!valueMayNotDependOnEnvironmentAssumingArrayDoesNotDependOnEnvironment(
-            rhs, root, assumedNotToDependOnEnvironment)) {
-          return false;
-        }
-
-        arrayValues.add(rhs);
-        consumedInstructions.add(arrayPut);
-        continue;
-      }
-
-      if (instruction.isNewArrayFilledData()) {
+        graph.addDirectedEdge(node, graph.createNodeIfAbsent(arrayPut.index()));
+        worklist.addIfNotSeen(arrayPut.index());
+        graph.addDirectedEdge(node, graph.createNodeIfAbsent(arrayPut.value()));
+        worklist.addIfNotSeen(arrayPut.value());
+      } else if (instruction.isNewArrayFilledData()) {
         NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
         Value array = newArrayFilledData.src();
-        if (array != root) {
+        if (array != value) {
+          // This ends the chain of array-put instructions that are allowed immediately after the
+          // array creation.
           break;
         }
-
-        consumedInstructions.add(newArrayFilledData);
-        continue;
-      }
-
-      if (instruction.instructionMayHaveSideEffects(appView, context)) {
+        consumedInstructions.add(instruction);
+      } else if (instruction.instructionMayHaveSideEffects(appView, context)) {
         // This ends the chain of array-put instructions that are allowed immediately after the
         // array creation.
         break;
       }
+      consumedInstructions.add(instruction);
     }
-
-    // Check that the array is not mutated before the end of this method.
-    //
-    // Currently, we only allow the array to flow into static-put instructions that are not
-    // followed by an instruction that may have side effects. Instructions that do not have any
-    // side effects are ignored because they cannot mutate the array.
-    if (valueMayBeMutatedBeforeMethodExit(
-        root, assumedNotToDependOnEnvironment, consumedInstructions)) {
-      return false;
-    }
-
-    if (assumedNotToDependOnEnvironment.isEmpty()) {
-      knownNotToDependOnEnvironment.add(root);
-      knownNotToDependOnEnvironment.addAll(arrayValues);
-    }
-
+    mutableValues.add(value);
     return true;
   }
 
-  private boolean isNewInstanceWithoutEnvironmentDependentFields(
-      Value value, Set<Value> assumedNotToDependOnEnvironment) {
-    assert !value.hasAliasedValue();
-
-    if (value.isPhi() || !value.definition.isNewInstance()) {
+  private boolean addNewInstanceValueToValueGraph(
+      Value value,
+      Node node,
+      ValueGraph graph,
+      Set<Instruction> consumedInstructions,
+      Set<Value> mutableValues,
+      WorkList<Value> worklist) {
+    if (!value.isDefinedByInstructionSatisfying(Instruction::isNewInstance)) {
       return false;
     }
 
@@ -291,123 +255,154 @@
     }
 
     // Find the single constructor invocation.
-    InvokeMethod constructorInvoke = null;
-    for (Instruction instruction : value.uniqueUsers()) {
-      if (!instruction.isInvokeDirect()) {
-        continue;
-      }
-
-      InvokeDirect invoke = instruction.asInvokeDirect();
-      if (!appView.dexItemFactory().isConstructor(invoke.getInvokedMethod())) {
-        continue;
-      }
-
-      if (invoke.getReceiver().getAliasedValue() != value) {
-        continue;
-      }
-
-      if (constructorInvoke == null) {
-        constructorInvoke = invoke;
-      } else {
-        // Not a single constructor invocation, give up.
-        return false;
-      }
-    }
-
-    if (constructorInvoke == null) {
-      // Didn't find a constructor invocation, give up.
+    InvokeMethod constructorInvoke =
+        newInstance.getUniqueConstructorInvoke(appView.dexItemFactory());
+    if (constructorInvoke == null || constructorInvoke.getInvokedMethod().holder != clazz.type) {
+      // Didn't find a (valid) constructor invocation, give up.
       return false;
     }
 
     // Check that it is a trivial initializer (otherwise, the constructor could do anything).
-    DexEncodedMethod constructor = appView.definitionFor(constructorInvoke.getInvokedMethod());
+    DexEncodedMethod constructor = clazz.lookupMethod(constructorInvoke.getInvokedMethod());
     if (constructor == null) {
       return false;
     }
 
-    if (clazz.hasInstanceFieldsDirectlyOrIndirectly(appView)) {
-      InstanceInitializerInfo initializerInfo =
-          constructor.getOptimizationInfo().getInstanceInitializerInfo();
+    InstanceInitializerInfo initializerInfo =
+        constructor.getOptimizationInfo().getInstanceInitializerInfo();
+
+    List<DexEncodedField> fields = clazz.getDirectAndIndirectInstanceFields(appView);
+    if (!fields.isEmpty()) {
       if (initializerInfo.instanceFieldInitializationMayDependOnEnvironment()) {
         return false;
       }
 
       // Check that none of the arguments to the constructor depend on the environment.
       for (int i = 1; i < constructorInvoke.arguments().size(); i++) {
-        Value argument = constructorInvoke.arguments().get(i);
-        if (valueMayDependOnEnvironment(argument, assumedNotToDependOnEnvironment)) {
-          return false;
-        }
+        Value argument = constructorInvoke.getArgument(i);
+        graph.addDirectedEdge(node, graph.createNodeIfAbsent(argument));
+        worklist.addIfNotSeen(argument);
       }
 
-      // Finally, check that the object does not escape.
-      if (valueMayBeMutatedBeforeMethodExit(
-          value, assumedNotToDependOnEnvironment, ImmutableSet.of(constructorInvoke))) {
-        return false;
+      // Mark this value as mutable if it has a non-final field.
+      boolean hasNonFinalField = false;
+      for (DexEncodedField field : fields) {
+        if (!field.isFinal()) {
+          hasNonFinalField = true;
+          break;
+        }
+      }
+      if (hasNonFinalField) {
+        mutableValues.add(value);
       }
     }
 
-    if (assumedNotToDependOnEnvironment.isEmpty()) {
-      knownNotToDependOnEnvironment.add(value);
+    if (!initializerInfo.mayHaveOtherSideEffectsThanInstanceFieldAssignments()) {
+      consumedInstructions.add(constructorInvoke);
     }
 
     return true;
   }
 
-  private boolean valueMayBeMutatedBeforeMethodExit(
-      Value value, Set<Value> assumedNotToDependOnEnvironment, Set<Instruction> whitelist) {
-    assert !value.hasAliasedValue();
-
-    if (value.numberOfPhiUsers() > 0) {
-      // Could be mutated indirectly.
-      return true;
+  private boolean anyValueMayBeMutatedBeforeMethodExit(
+      Set<Value> values, Set<Instruction> whitelist) {
+    Set<BasicBlock> initialBlocks = Sets.newIdentityHashSet();
+    for (Value value : values) {
+      assert !value.isPhi();
+      initialBlocks.add(value.definition.getBlock());
     }
-
-    Set<Instruction> visited = Sets.newIdentityHashSet();
-    for (Instruction user : value.uniqueUsers()) {
-      if (whitelist.contains(user)) {
-        continue;
-      }
-
-      if (user.isArrayPut()) {
-        ArrayPut arrayPut = user.asArrayPut();
-        if (value == arrayPut.value()
-            && !valueMayDependOnEnvironment(arrayPut.array(), assumedNotToDependOnEnvironment)) {
+    Map<BasicBlock, TrackedValuesState> blockExitStates = new IdentityHashMap<>();
+    Deque<BasicBlock> worklist = new ArrayDeque<>(initialBlocks);
+    while (!worklist.isEmpty()) {
+      BasicBlock block = worklist.removeFirst();
+      TrackedValuesState state = computeBlockEntryState(block, blockExitStates);
+      boolean changed = false;
+      for (Instruction instruction : block.getInstructions()) {
+        if (whitelist.contains(instruction)) {
           continue;
         }
-      }
-
-      if (user.isStaticPut()) {
-        StaticPut staticPut = user.asStaticPut();
-        if (visited.contains(staticPut)) {
-          // Already visited previously.
-          continue;
-        }
-        for (Instruction instruction : code.getInstructionsReachableFrom(staticPut)) {
-          if (!visited.add(instruction)) {
-            // Already visited previously.
-            continue;
+        if (instruction.isStaticPut()) {
+          StaticPut staticPut = instruction.asStaticPut();
+          if (state.isTrackingValue(staticPut.value())) {
+            changed |= state.recordTrackedValueHasEscaped();
           }
-          if (instruction.isStaticPut()) {
-            StaticPut otherStaticPut = instruction.asStaticPut();
-            if (otherStaticPut.getField().holder == staticPut.getField().holder
-                && !instruction.instructionInstanceCanThrow(appView, context)) {
-              continue;
+          if (state.hasTrackedValueEscaped()) {
+            DexType holder = staticPut.getField().holder;
+            if (holder.classInitializationMayHaveSideEffects(
+                appView,
+                // Types that are a super type of the current context are guaranteed to be
+                // initialized already.
+                type -> appView.isSubtype(context.getHolderType(), type).isTrue(),
+                Sets.newIdentityHashSet())) {
+              return true;
             }
-            return true;
           }
-          if (instruction.instructionMayTriggerMethodInvocation(appView, context)
+          continue;
+        }
+        if (instruction.instructionMayTriggerMethodInvocation(appView, context)) {
+          if (instruction.hasInValueThatMatches(state::isTrackingValue)) {
+            changed |= state.recordTrackedValueHasEscaped();
+          }
+          if (state.hasTrackedValueEscaped()
               && instruction.instructionMayHaveSideEffects(appView, context)) {
             return true;
           }
         }
-        continue;
+        if (instruction.hasOutValue() && values.contains(instruction.outValue())) {
+          changed |= state.startTrackingValue(instruction.outValue());
+        }
       }
+      blockExitStates.put(block, state);
+      if (changed) {
+        worklist.addAll(block.getSuccessors());
+      }
+    }
+    return false;
+  }
 
-      // Other user than array-put or static-put, just give up.
-      return false;
+  private TrackedValuesState computeBlockEntryState(
+      BasicBlock block, Map<BasicBlock, TrackedValuesState> states) {
+    TrackedValuesState state = new TrackedValuesState();
+    for (BasicBlock predecessor : block.getPredecessors()) {
+      state.add(states.getOrDefault(predecessor, TrackedValuesState.empty()));
+    }
+    return state;
+  }
+
+  static class TrackedValuesState {
+
+    private static final TrackedValuesState EMPTY = new TrackedValuesState();
+
+    boolean hasTrackedValueEscaped;
+    Set<Value> trackedValues = Sets.newIdentityHashSet();
+
+    public static TrackedValuesState empty() {
+      return EMPTY;
     }
 
-    return false;
+    public void add(TrackedValuesState state) {
+      hasTrackedValueEscaped |= state.hasTrackedValueEscaped;
+      trackedValues.addAll(state.trackedValues);
+    }
+
+    public boolean hasTrackedValueEscaped() {
+      return hasTrackedValueEscaped;
+    }
+
+    public boolean isTrackingValue(Value value) {
+      return trackedValues.contains(value);
+    }
+
+    public boolean recordTrackedValueHasEscaped() {
+      if (hasTrackedValueEscaped) {
+        return false;
+      }
+      hasTrackedValueEscaped = true;
+      return true;
+    }
+
+    public boolean startTrackingValue(Value value) {
+      return trackedValues.add(value);
+    }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java b/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java
new file mode 100644
index 0000000..8857645
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/analysis/environmentdependence/ValueGraph.java
@@ -0,0 +1,113 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.ir.analysis.environmentdependence;
+
+import com.android.tools.r8.algorithms.scc.SCC;
+import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.utils.WorkList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Predicate;
+
+/**
+ * A directed graph where the nodes are values.
+ *
+ * <ol>
+ *   <li>An edge from a value v to another value v' specifies that if v' may depend on the
+ *       environment then v may also depend on the environment.
+ *   <li>If a value v has no outgoing edges, then it does not depend on the environment.
+ * </ol>
+ */
+public class ValueGraph {
+
+  private final Map<Value, Node> nodes = new IdentityHashMap<>();
+
+  public Node createNodeIfAbsent(Value value) {
+    return nodes.computeIfAbsent(value, Node::new);
+  }
+
+  public void addDirectedEdge(Node from, Node to) {
+    to.predecessors.add(from);
+    from.successors.add(to);
+  }
+
+  public Collection<Node> getNodes() {
+    return nodes.values();
+  }
+
+  public void mergeNodes(Iterable<Node> iterable) {
+    Iterator<Node> iterator = iterable.iterator();
+    assert iterator.hasNext();
+    Node primary = iterator.next();
+    while (iterator.hasNext()) {
+      Node secondary = iterator.next();
+      secondary.moveEdgesTo(primary);
+      primary.addLabel(secondary.label);
+      nodes.put(secondary.value, primary);
+    }
+  }
+
+  public void mergeStronglyConnectedComponents() {
+    WorkList<Node> worklist = WorkList.newIdentityWorkList(nodes.values());
+    while (worklist.hasNext()) {
+      Node node = worklist.next();
+      List<Set<Node>> components = new SCC<>(Node::getSuccessors).computeSCC(node);
+      for (Set<Node> component : components) {
+        mergeNodes(component);
+        worklist.markAsSeen(component);
+      }
+    }
+  }
+
+  public static class Node {
+
+    private final Value value;
+
+    private final Set<Value> label = Sets.newIdentityHashSet();
+    private final Set<Node> predecessors = Sets.newIdentityHashSet();
+    private final Set<Node> successors = Sets.newIdentityHashSet();
+
+    public Node(Value value) {
+      this.label.add(value);
+      this.value = value;
+    }
+
+    public void addLabel(Set<Value> label) {
+      this.label.addAll(label);
+    }
+
+    public Set<Node> getSuccessors() {
+      return successors;
+    }
+
+    public boolean hasSuccessorThatMatches(Predicate<Node> predicate) {
+      for (Node successor : successors) {
+        if (predicate.test(successor)) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    public void moveEdgesTo(Node node) {
+      for (Node predecessor : predecessors) {
+        predecessor.successors.remove(this);
+        predecessor.successors.add(node);
+        node.predecessors.add(predecessor);
+      }
+      predecessors.clear();
+      for (Node successor : successors) {
+        successor.predecessors.remove(this);
+        successor.predecessors.add(node);
+        node.successors.add(successor);
+      }
+      successors.clear();
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
index 1481061..5b1dd91 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/fieldaccess/FieldAssignmentTracker.java
@@ -23,7 +23,6 @@
 import com.android.tools.r8.ir.code.NewInstance;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.ir.optimize.ClassInitializerDefaultsOptimization.ClassInitializerDefaultsResult;
-import com.android.tools.r8.ir.optimize.info.FieldOptimizationInfo;
 import com.android.tools.r8.ir.optimize.info.OptimizationFeedbackDelayed;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldArgumentInitializationInfo;
 import com.android.tools.r8.ir.optimize.info.field.InstanceFieldInitializationInfo;
@@ -131,8 +130,6 @@
   }
 
   private void recordFieldPut(DexEncodedField field, Value value, ProgramMethod context) {
-    assert verifyValueIsConsistentWithFieldOptimizationInfo(
-        value, field.getOptimizationInfo(), context);
     if (!value.isZero()) {
       nonZeroFields.add(field);
     }
@@ -293,16 +290,6 @@
     feedback.updateVisibleOptimizationInfo();
   }
 
-  private boolean verifyValueIsConsistentWithFieldOptimizationInfo(
-      Value value, FieldOptimizationInfo optimizationInfo, ProgramMethod context) {
-    AbstractValue abstractValue = optimizationInfo.getAbstractValue();
-    if (abstractValue.isUnknown()) {
-      return true;
-    }
-    assert abstractValue == value.getAbstractValue(appView, context);
-    return true;
-  }
-
   static class FieldAccessGraph {
 
     // The fields written by each method.
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
index c589048..96cdb99 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/GeneratedMessageLiteBuilderShrinker.java
@@ -10,6 +10,7 @@
 
 import com.android.tools.r8.graph.AppInfoWithClassHierarchy;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
@@ -74,19 +75,24 @@
   }
 
   private boolean computeEnableAggressiveBuilderOptimization() {
+    DexClass generatedMessageLiteBuilderClass =
+        appView
+            .appInfo()
+            .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType);
+    DexClass generatedMessageLiteExtendableBuilderClass =
+        appView
+            .appInfo()
+            .definitionForWithoutExistenceAssert(
+                references.generatedMessageLiteExtendableBuilderType);
+    if (generatedMessageLiteBuilderClass == null
+        && generatedMessageLiteExtendableBuilderClass == null) {
+      // This build likely doesn't contain any proto, so disable the optimization. Don't report a
+      // warning in this case.
+      return false;
+    }
     boolean unexpectedGeneratedMessageLiteBuilder =
         ObjectUtils.getBooleanOrElse(
-            appView
-                .appInfo()
-                .definitionForWithoutExistenceAssert(references.generatedMessageLiteBuilderType),
-            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
-            true);
-    boolean unexpectedGeneratedMessageLiteExtendableBuilder =
-        ObjectUtils.getBooleanOrElse(
-            appView
-                .appInfo()
-                .definitionForWithoutExistenceAssert(
-                    references.generatedMessageLiteExtendableBuilderType),
+            generatedMessageLiteBuilderClass,
             clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
             true);
     if (unexpectedGeneratedMessageLiteBuilder) {
@@ -99,6 +105,11 @@
                   + "`: disabling aggressive protobuf builder optimization.");
       return false;
     }
+    boolean unexpectedGeneratedMessageLiteExtendableBuilder =
+        ObjectUtils.getBooleanOrElse(
+            generatedMessageLiteExtendableBuilderClass,
+            clazz -> clazz.getMethodCollection().hasMethods(DexEncodedMethod::isAbstract),
+            true);
     if (unexpectedGeneratedMessageLiteExtendableBuilder) {
       appView
           .options()
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
index e4741d9..363a2de 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/RawMessageInfoDecoder.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getInfoValueFromMessageInfoConstructionInvoke;
 import static com.android.tools.r8.ir.analysis.proto.ProtoUtils.getObjectsValueFromMessageInfoConstructionInvoke;
 
+import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexReference;
@@ -152,6 +153,10 @@
         int fieldNumber = infoIterator.nextIntComputeIfAbsent(this::invalidInfoFailure);
         int fieldTypeWithExtraBits = infoIterator.nextIntComputeIfAbsent(this::invalidInfoFailure);
         ProtoFieldType fieldType = factory.createField(fieldTypeWithExtraBits);
+        if (fieldType.serialize() != fieldTypeWithExtraBits) {
+          throw new CompilationError(
+              "Unexpected proto field type `" + fieldTypeWithExtraBits + "`");
+        }
 
         OptionalInt auxData;
         if (fieldType.hasAuxData(isProto2)) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
index a9cff43..39419b4 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldType.java
@@ -22,23 +22,31 @@
 
   private static final int FIELD_ID_MASK = 0xFF;
   private static final int FIELD_IS_REQUIRED_MASK = 0x100;
+  private static final int FIELD_ENFORCE_UTF8_MASK = 0x200;
   private static final int FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK = 0x400;
   private static final int FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK = 0x800;
+  private static final int FIELD_HAS_HAS_BIT_MASK = 0x1000;
 
   private final int id;
   private final boolean isRequired;
+  private final boolean enforceUtf8Mask;
   private final boolean needsIsInitializedCheck;
   private final boolean isMapFieldWithProto2EnumValue;
+  private final boolean hasHasBit;
 
   ProtoFieldType(
       int id,
       boolean isRequired,
+      boolean enforceUtf8Mask,
       boolean needsIsInitializedCheck,
-      boolean isMapFieldWithProto2EnumValue) {
+      boolean isMapFieldWithProto2EnumValue,
+      boolean hasHasBit) {
     this.id = id;
     this.isRequired = isRequired;
+    this.enforceUtf8Mask = enforceUtf8Mask;
     this.needsIsInitializedCheck = needsIsInitializedCheck;
     this.isMapFieldWithProto2EnumValue = isMapFieldWithProto2EnumValue;
+    this.hasHasBit = hasHasBit;
     assert isValid();
   }
 
@@ -48,14 +56,18 @@
       return new ProtoFieldType(
           fieldTypeWithExtraBits & FIELD_ID_MASK,
           isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_REQUIRED_MASK),
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_ENFORCE_UTF8_MASK),
           isBitInMaskSet(fieldTypeWithExtraBits, FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK),
-          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK));
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK),
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_HAS_HAS_BIT_MASK));
     } else {
       return new ProtoOneOfFieldType(
           fieldTypeWithExtraBits & FIELD_ID_MASK,
           isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_REQUIRED_MASK),
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_ENFORCE_UTF8_MASK),
           isBitInMaskSet(fieldTypeWithExtraBits, FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK),
-          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK));
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK),
+          isBitInMaskSet(fieldTypeWithExtraBits, FIELD_HAS_HAS_BIT_MASK));
     }
   }
 
@@ -141,12 +153,18 @@
     if (isRequired) {
       result |= FIELD_IS_REQUIRED_MASK;
     }
+    if (enforceUtf8Mask) {
+      result |= FIELD_ENFORCE_UTF8_MASK;
+    }
     if (needsIsInitializedCheck) {
       result |= FIELD_NEEDS_IS_INITIALIZED_CHECK_MASK;
     }
     if (isMapFieldWithProto2EnumValue) {
       result |= FIELD_IS_MAP_FIELD_WITH_PROTO_2_ENUM_VALUE_MASK;
     }
+    if (hasHasBit) {
+      result |= FIELD_HAS_HAS_BIT_MASK;
+    }
     return result;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
index 54000eb..c655ce8 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoFieldTypeFactory.java
@@ -15,6 +15,7 @@
     ProtoFieldType result = fieldTypes.get(fieldTypeIdWithExtraBits);
     if (result == null) {
       result = ProtoFieldType.fromFieldIdWithExtraBits(fieldTypeIdWithExtraBits);
+      assert result.serialize() == fieldTypeIdWithExtraBits;
       fieldTypes.put(fieldTypeIdWithExtraBits, result);
     }
     return result;
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
index c476382..1bc2ab5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/proto/schema/ProtoOneOfFieldType.java
@@ -13,9 +13,17 @@
   ProtoOneOfFieldType(
       int id,
       boolean isRequired,
+      boolean enforceUtf8Mask,
       boolean needsIsInitializedCheck,
-      boolean isMapFieldWithProto2EnumValue) {
-    super(id, isRequired, needsIsInitializedCheck, isMapFieldWithProto2EnumValue);
+      boolean isMapFieldWithProto2EnumValue,
+      boolean hasHasBit) {
+    super(
+        id,
+        isRequired,
+        enforceUtf8Mask,
+        needsIsInitializedCheck,
+        isMapFieldWithProto2EnumValue,
+        hasHasBit);
   }
 
   public ProtoFieldType getActualFieldType(ProtoFieldTypeFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
index 136c209..5d771a5 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/sideeffect/ClassInitializerSideEffectAnalysis.java
@@ -11,12 +11,11 @@
 import com.android.tools.r8.ir.code.ArrayPut;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
-import com.android.tools.r8.ir.code.InvokeNewArray;
-import com.android.tools.r8.ir.code.NewArrayFilledData;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
-import com.android.tools.r8.utils.OptionalBool;
+import com.google.common.collect.Sets;
+import java.util.Set;
 
 public class ClassInitializerSideEffectAnalysis {
 
@@ -41,69 +40,28 @@
   public static ClassInitializerSideEffect classInitializerCanBePostponed(
       AppView<AppInfoWithLiveness> appView, IRCode code) {
     ProgramMethod context = code.context();
-    OptionalBool controlFlowMayDependOnEnvironment = OptionalBool.unknown();
-    boolean mayHaveSideEffects = false;
-
-    ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
-        new ValueMayDependOnEnvironmentAnalysis(appView, code);
+    // Will be set to true if the control flow must be independent of the environment in order for
+    // this class initializer to be postponeable.
+    boolean controlFlowRequiredToBeIndependentOfControlFlow = false;
+    // The set of values that must be independent of the environment in order for this class
+    // initializer to be postponeable.
+    Set<Value> valuesRequiredToBeIndependentOfEnvironment = Sets.newIdentityHashSet();
     for (Instruction instruction : code.instructions()) {
-      // Array stores to a newly created array are only observable if they may throw, or if the
-      // array content may depend on the environment.
+      // Array stores are observable if they mutate a non-local array or if they may throw.
       if (instruction.isArrayPut()) {
         ArrayPut arrayPut = instruction.asArrayPut();
         Value array = arrayPut.array().getAliasedValue();
-        if (array.isPhi()
-            || !array.definition.isCreatingArray()
-            || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.index())
-            || environmentAnalysis.valueMayDependOnEnvironment(arrayPut.value())
+        if (!array.isDefinedByInstructionSatisfying(Instruction::isCreatingArray)
             || arrayPut.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
-        if (controlFlowMayDependOnEnvironment.isUnknown()) {
-          controlFlowMayDependOnEnvironment =
-              OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
-        }
-        if (controlFlowMayDependOnEnvironment.isTrue()) {
-          return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
-        }
         continue;
       }
 
-      // NewArrayFilledData is handled similarly to ArrayPut.
-      if (instruction.isNewArrayFilledData()) {
-        NewArrayFilledData newArrayFilledData = instruction.asNewArrayFilledData();
-        Value array = newArrayFilledData.src();
-        if (array.isPhi()
-            || !array.definition.isCreatingArray()
-            || newArrayFilledData.instructionInstanceCanThrow(appView, context)) {
-          return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
-        }
-        if (controlFlowMayDependOnEnvironment.isUnknown()) {
-          controlFlowMayDependOnEnvironment =
-              OptionalBool.of(code.controlFlowMayDependOnEnvironment(environmentAnalysis));
-        }
-        if (controlFlowMayDependOnEnvironment.isTrue()) {
-          return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
-        }
-        continue;
-      }
-
-      // Array creations are only observable if they may throw, or if the array content may depend
-      // on the environment.
-      if (instruction.isInvokeNewArray()) {
-        InvokeNewArray invokeNewArray = instruction.asInvokeNewArray();
-        if (invokeNewArray.instructionInstanceCanThrow(appView, context)) {
-          return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
-        }
-        for (Value argument : invokeNewArray.arguments()) {
-          if (environmentAnalysis.valueMayDependOnEnvironment(argument)) {
-            return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
-          }
-        }
-        continue;
-      }
-
-      if (instruction.isNewArrayEmpty()) {
+      // Array creations are observable if they may throw.
+      if (instruction.isInvokeNewArray()
+          || instruction.isNewArrayEmpty()
+          || instruction.isNewArrayFilledData()) {
         if (instruction.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
@@ -116,11 +74,11 @@
             appView.appInfo().resolveField(staticPut.getField()).getResolvedField();
         if (field == null
             || field.holder() != context.getHolderType()
-            || environmentAnalysis.valueMayDependOnEnvironment(staticPut.value())
             || instruction.instructionInstanceCanThrow(appView, context)) {
           return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
         }
-        mayHaveSideEffects = true;
+        controlFlowRequiredToBeIndependentOfControlFlow = true;
+        valuesRequiredToBeIndependentOfEnvironment.add(staticPut.value());
         continue;
       }
 
@@ -130,8 +88,21 @@
       }
     }
 
-    return mayHaveSideEffects
-        ? ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CAN_BE_POSTPONED
-        : ClassInitializerSideEffect.NONE;
+    if (controlFlowRequiredToBeIndependentOfControlFlow) {
+      if (code.controlFlowMayDependOnEnvironment(valuesRequiredToBeIndependentOfEnvironment::add)) {
+        return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
+      }
+    }
+
+    if (!valuesRequiredToBeIndependentOfEnvironment.isEmpty()) {
+      ValueMayDependOnEnvironmentAnalysis environmentAnalysis =
+          new ValueMayDependOnEnvironmentAnalysis(appView, code);
+      if (environmentAnalysis.anyValueMayDependOnEnvironment(
+          valuesRequiredToBeIndependentOfEnvironment)) {
+        return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CANNOT_BE_POSTPONED;
+      }
+      return ClassInitializerSideEffect.SIDE_EFFECTS_THAT_CAN_BE_POSTPONED;
+    }
+    return ClassInitializerSideEffect.NONE;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
index 50cec6e..57d52df 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionListIterator.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Phi.RegisterReadType;
@@ -544,12 +545,14 @@
       Set<BasicBlock> blocksToRemove,
       DexType downcast) {
     assert blocksToRemove != null;
-    DexType codeHolder = code.method().holder();
-    DexType inlineeHolder = inlinee.method().holder();
-    if (codeHolder != inlineeHolder && inlinee.method().isOnlyInlinedIntoNestMembers()) {
+    ProgramMethod callerContext = code.context();
+    ProgramMethod calleeContext = inlinee.context();
+    if (callerContext.getHolder() != calleeContext.getHolder()
+        && calleeContext.getDefinition().isOnlyInlinedIntoNestMembers()) {
       // Should rewrite private calls to virtual calls.
-      assert NestUtils.sameNest(codeHolder, inlineeHolder, appView);
-      NestUtils.rewriteNestCallsForInlining(inlinee, codeHolder, appView);
+      assert NestUtils.sameNest(
+          callerContext.getHolderType(), calleeContext.getHolderType(), appView);
+      NestUtils.rewriteNestCallsForInlining(inlinee, callerContext, appView);
     }
     boolean inlineeCanThrow = canThrow(inlinee);
     // Split the block with the invocation into three blocks, where the first block contains all
diff --git a/src/main/java/com/android/tools/r8/ir/code/IRCode.java b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
index 3ef2eee..f177619 100644
--- a/src/main/java/com/android/tools/r8/ir/code/IRCode.java
+++ b/src/main/java/com/android/tools/r8/ir/code/IRCode.java
@@ -15,7 +15,6 @@
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.classmerging.VerticallyMergedClasses;
 import com.android.tools.r8.ir.analysis.TypeChecker;
-import com.android.tools.r8.ir.analysis.ValueMayDependOnEnvironmentAnalysis;
 import com.android.tools.r8.ir.analysis.type.ClassTypeElement;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
@@ -50,6 +49,7 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.function.Consumer;
 import java.util.function.Function;
 import java.util.function.Predicate;
 import java.util.stream.Collectors;
@@ -283,8 +283,16 @@
     return liveAtEntrySets;
   }
 
+  /**
+   * Returns true if the control flow of this code object may depend on the environment. If false is
+   * returned, then the control flow of this code object does not depend on the environment if none
+   * of the values passed to the given consumer depend on the environment.
+   *
+   * <p>It is the responsibility of the client to prove that none of the values passed to {@param
+   * valueRequiredToBeIndependentOfEnvironmentConsumer} may depend on the environment.
+   */
   public boolean controlFlowMayDependOnEnvironment(
-      ValueMayDependOnEnvironmentAnalysis environmentAnalysis) {
+      Consumer<Value> valueRequiredToBeIndependentOfEnvironmentConsumer) {
     for (BasicBlock block : blocks) {
       if (block.hasCatchHandlers()) {
         // Whether an instruction throws may generally depend on the environment.
@@ -292,18 +300,13 @@
       }
       if (block.exit().isIf()) {
         If ifInstruction = block.exit().asIf();
-        if (environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.lhs())) {
-          return true;
-        }
-        if (!ifInstruction.isZeroTest()
-            && environmentAnalysis.valueMayDependOnEnvironment(ifInstruction.rhs())) {
-          return true;
+        valueRequiredToBeIndependentOfEnvironmentConsumer.accept(ifInstruction.lhs());
+        if (!ifInstruction.isZeroTest()) {
+          valueRequiredToBeIndependentOfEnvironmentConsumer.accept(ifInstruction.rhs());
         }
       } else if (block.exit().isSwitch()) {
         Switch switchInstruction = block.exit().asSwitch();
-        if (environmentAnalysis.valueMayDependOnEnvironment(switchInstruction.value())) {
-          return true;
-        }
+        valueRequiredToBeIndependentOfEnvironmentConsumer.accept(switchInstruction.value());
       }
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/ir/code/Instruction.java b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
index ee7d57e..be64578 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Instruction.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Instruction.java
@@ -39,6 +39,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public abstract class Instruction implements InstructionOrPhi, TypeAndLocalInfoSupplier {
 
@@ -104,8 +105,12 @@
   }
 
   public boolean hasInValueWithLocalInfo() {
-    for (Value inValue : inValues()) {
-      if (inValue.hasLocalInfo()) {
+    return hasInValueThatMatches(Value::hasLocalInfo);
+  }
+
+  public boolean hasInValueThatMatches(Predicate<Value> predicate) {
+    for (Value value : inValues()) {
+      if (predicate.test(value)) {
         return true;
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
index 00c88b8..e89d9b0 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeStatic.java
@@ -117,10 +117,10 @@
       return result;
     }
     // Allow optimizing static library invokes in D8.
-    DexClass clazz = appView.definitionFor(getInvokedMethod().holder);
+    DexClass clazz = appView.definitionForHolder(getInvokedMethod());
     if (clazz != null
         && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) {
-      return appView.definitionFor(getInvokedMethod());
+      return clazz.lookupMethod(getInvokedMethod());
     }
     // In D8, we can treat invoke-static instructions as having a single target if the invoke is
     // targeting a method in the enclosing class.
diff --git a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
index 2d95115..951a451 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InvokeVirtual.java
@@ -130,7 +130,7 @@
       DexClass clazz = appView.definitionFor(holder);
       if (clazz != null
           && (clazz.isLibraryClass() || appView.libraryMethodOptimizer().isModeled(clazz.type))) {
-        DexEncodedMethod singleTargetCandidate = appView.definitionFor(method);
+        DexEncodedMethod singleTargetCandidate = clazz.lookupMethod(method);
         if (singleTargetCandidate != null && (clazz.isFinal() || singleTargetCandidate.isFinal())) {
           return singleTargetCandidate;
         }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
index 35760b4..c53b686 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/IRConverter.java
@@ -741,14 +741,17 @@
       enumUnboxer.finishAnalysis();
       enumUnboxer.unboxEnums(postMethodProcessorBuilder, executorService, feedback);
     }
-    new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
-        .run(executorService, feedback, timing);
+    if (!options.debug) {
+      new TrivialFieldAccessReprocessor(appView.withLiveness(), postMethodProcessorBuilder)
+          .run(executorService, feedback, timing);
+    }
 
     timing.begin("IR conversion phase 2");
     graphLenseForIR = appView.graphLense();
     PostMethodProcessor postMethodProcessor =
         postMethodProcessorBuilder.build(appView.withLiveness(), executorService, timing);
     if (postMethodProcessor != null) {
+      assert !options.debug;
       postMethodProcessor.forEachWave(feedback, executorService);
       feedback.updateVisibleOptimizationInfo();
       assert graphLenseForIR == appView.graphLense();
@@ -1193,6 +1196,10 @@
     }
 
     if (method.isProcessed()) {
+      // We loose locals information when processing dex code, so if in debug mode only process
+      // synthesized methods.
+      // TODO(b/158818229): Check if the synthesized check is needed.
+      assert !isDebugMode || method.isD8R8Synthesized();
       assert !appView.enableWholeProgramOptimizations()
           || !appView.appInfo().withLiveness().neverReprocess.contains(method.method);
     } else {
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
index fbeff68..f1c403b 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PostMethodProcessor.java
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.ir.conversion;
 
+import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
+
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexProgramClass;
@@ -126,10 +128,10 @@
             .reprocess
             .forEach(
                 reference -> {
-                  DexEncodedMethod definition = appView.definitionFor(reference);
+                  DexProgramClass clazz =
+                      asProgramClassOrNull(appView.definitionForHolder(reference));
+                  DexEncodedMethod definition = reference.lookupOnClass(clazz);
                   if (definition != null) {
-                    DexProgramClass clazz =
-                        appView.definitionForHolder(definition).asProgramClass();
                     set.createAndAdd(clazz, definition);
                   }
                 });
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
index 10f34af..9dfe8e3 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/PrimaryMethodProcessor.java
@@ -89,7 +89,7 @@
       SortedProgramMethodSet wave = callGraph.extractLeaves();
       wave.forEach(
           method -> {
-            if (callSiteInformation.hasSingleCallSite(method)) {
+            if (callSiteInformation.hasSingleCallSite(method) && options.enableInlining) {
               callGraph.cycleEliminationResult.forEachRemovedCaller(method, reprocessing::add);
             }
           });
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
index 4737d39..3971c88 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/ClassProcessor.java
@@ -337,8 +337,7 @@
     if (method.isFinal()) {
       return false;
     }
-    return appView.options().desugaredLibraryConfiguration.retargetMethod(method.method, appView)
-        != null;
+    return appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView) != null;
   }
 
   private boolean dontRewrite(DexClass clazz, DexEncodedMethod method) {
@@ -396,7 +395,7 @@
     DexMethod forwardMethod =
         targetHolder.isInterface()
             ? rewriter.defaultAsMethodOfCompanionClass(method)
-            : appView.options().desugaredLibraryConfiguration.retargetMethod(method, appView);
+            : appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
     DexEncodedMethod desugaringForwardingMethod =
         DexEncodedMethod.createDesugaringForwardingMethod(
             target, clazz, forwardMethod, dexItemFactory);
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
index ba49fc0..bd468b0 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/D8NestBasedAccessDesugaring.java
@@ -65,12 +65,18 @@
         Instruction instruction = instructions.next();
         if (instruction.isInvokeMethod()) {
           InvokeMethod invokeMethod = instruction.asInvokeMethod();
-          DexMethod methodCalled = invokeMethod.getInvokedMethod();
-          DexEncodedMethod encodedMethodCalled =
-              methodCalled.holder.isClassType() ? appView.definitionFor(methodCalled) : null;
-          if (encodedMethodCalled != null && invokeRequiresRewriting(encodedMethodCalled, method)) {
-            DexMethod bridge = ensureInvokeBridge(encodedMethodCalled);
-            if (encodedMethodCalled.isInstanceInitializer()) {
+          DexMethod invokedMethod = invokeMethod.getInvokedMethod();
+          if (!invokedMethod.holder.isClassType()) {
+            continue;
+          }
+          // Since we only need to desugar accesses to private methods, and all accesses to private
+          // methods must be accessing the private method directly on its holder, we can lookup the
+          // method on the holder instead of resolving the method.
+          DexClass holder = appView.definitionForHolder(invokedMethod);
+          DexEncodedMethod definition = invokedMethod.lookupOnClass(holder);
+          if (definition != null && invokeRequiresRewriting(definition, method)) {
+            DexMethod bridge = ensureInvokeBridge(definition);
+            if (definition.isInstanceInitializer()) {
               instructions.previous();
               Value extraNullValue =
                   instructions.insertConstNullInstruction(code, appView.options());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
index 1590484..a7d5252 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryConfiguration.java
@@ -6,6 +6,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
@@ -132,15 +133,15 @@
   }
 
   // If the method is retargeted, answers the retargeted method, else null.
-  public DexMethod retargetMethod(DexMethod method, AppView<?> appView) {
-    Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.name);
-    if (typeMap != null && typeMap.containsKey(method.holder)) {
+  public DexMethod retargetMethod(DexEncodedMethod method, AppView<?> appView) {
+    Map<DexType, DexType> typeMap = retargetCoreLibMember.get(method.getName());
+    if (typeMap != null && typeMap.containsKey(method.getHolderType())) {
       return appView
           .dexItemFactory()
           .createMethod(
-              typeMap.get(method.holder),
-              appView.dexItemFactory().prependTypeToProto(method.holder, method.proto),
-              method.name);
+              typeMap.get(method.getHolderType()),
+              appView.dexItemFactory().prependHolderToProto(method.getReference()),
+              method.getName());
     }
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
index 53ff67e..cfb48f2 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/DesugaredLibraryRetargeter.java
@@ -55,7 +55,7 @@
   // d8 needs to force resolution of given methods to see if the invoke needs to be rewritten.
   private final Map<DexString, List<DexMethod>> virtualRewrites = new IdentityHashMap<>();
   // Non final virtual library methods requiring generation of emulated dispatch.
-  private final Set<DexMethod> emulatedDispatchMethods = Sets.newHashSet();
+  private final Set<DexEncodedMethod> emulatedDispatchMethods = Sets.newIdentityHashSet();
 
   public DesugaredLibraryRetargeter(AppView<?> appView) {
     this.appView = appView;
@@ -139,7 +139,7 @@
               appView
                   .options()
                   .desugaredLibraryConfiguration
-                  .retargetMethod(dexEncodedMethod.method, appView);
+                  .retargetMethod(dexEncodedMethod, appView);
           if (retargetMethod != null) {
             iterator.replaceCurrentInstruction(
                 new InvokeStatic(retargetMethod, invoke.outValue(), invoke.arguments()));
@@ -197,8 +197,8 @@
                 } else if (!encodedMethod.isFinal()) {
                   // Virtual rewrites require emulated dispatch for inheritance.
                   // The call is rewritten to the dispatch holder class instead.
-                  handleEmulateDispatch(appView, encodedMethod.method);
-                  newHolder = dispatchHolderTypeFor(encodedMethod.method);
+                  handleEmulateDispatch(appView, encodedMethod);
+                  newHolder = dispatchHolderTypeFor(encodedMethod);
                 }
               }
               DexProto proto = encodedMethod.method.proto;
@@ -213,8 +213,7 @@
 
     private DexMethod computeRetargetMethod(DexMethod method, boolean isStatic, DexType newHolder) {
       DexItemFactory factory = appView.dexItemFactory();
-      DexProto newProto =
-          isStatic ? method.proto : factory.prependTypeToProto(method.holder, method.proto);
+      DexProto newProto = isStatic ? method.proto : factory.prependHolderToProto(method);
       return factory.createMethod(newHolder, newProto, method.name);
     }
 
@@ -230,7 +229,7 @@
       return found;
     }
 
-    private void handleEmulateDispatch(AppView<?> appView, DexMethod method) {
+    private void handleEmulateDispatch(AppView<?> appView, DexEncodedMethod method) {
       emulatedDispatchMethods.add(method);
       if (!appView.options().isDesugaredLibraryCompilation()) {
         // Add rewrite rules so keeps rules are correctly generated in the program.
@@ -266,10 +265,10 @@
     private void addInterfacesAndForwardingMethods(
         ExecutorService executorService, IRConverter converter) throws ExecutionException {
       assert !appView.options().isDesugaredLibraryCompilation();
-      Map<DexType, List<DexMethod>> map = Maps.newIdentityHashMap();
-      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
-        map.putIfAbsent(emulatedDispatchMethod.holder, new ArrayList<>(1));
-        map.get(emulatedDispatchMethod.holder).add(emulatedDispatchMethod);
+      Map<DexType, List<DexEncodedMethod>> map = Maps.newIdentityHashMap();
+      for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+        map.putIfAbsent(emulatedDispatchMethod.getHolderType(), new ArrayList<>(1));
+        map.get(emulatedDispatchMethod.getHolderType()).add(emulatedDispatchMethod);
       }
       SortedProgramMethodSet addedMethods = SortedProgramMethodSet.create();
       for (DexProgramClass clazz : appView.appInfo().classes()) {
@@ -295,7 +294,8 @@
       converter.processMethodsConcurrently(addedMethods, executorService);
     }
 
-    private boolean inherit(DexLibraryClass clazz, DexType typeToInherit, Set<DexMethod> retarget) {
+    private boolean inherit(
+        DexLibraryClass clazz, DexType typeToInherit, Set<DexEncodedMethod> retarget) {
       DexLibraryClass current = clazz;
       while (current.type != appView.dexItemFactory().objectType) {
         if (current.type == typeToInherit) {
@@ -316,36 +316,35 @@
 
     private void addInterfacesAndForwardingMethods(
         DexProgramClass clazz,
-        List<DexMethod> methods,
+        List<DexEncodedMethod> methods,
         Consumer<DexEncodedMethod> newForwardingMethodsConsumer) {
       // DesugaredLibraryRetargeter emulate dispatch: insertion of a marker interface & forwarding
       // methods.
       // We cannot use the ClassProcessor since this applies up to 26, while the ClassProcessor
       // applies up to 24.
-      for (DexMethod dexMethod : methods) {
+      for (DexEncodedMethod method : methods) {
         DexType[] newInterfaces =
             Arrays.copyOf(clazz.interfaces.values, clazz.interfaces.size() + 1);
-        newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(dexMethod);
+        newInterfaces[newInterfaces.length - 1] = dispatchInterfaceTypeFor(method);
         clazz.interfaces = new DexTypeList(newInterfaces);
-        DexEncodedMethod dexEncodedMethod = clazz.lookupVirtualMethod(dexMethod);
-        if (dexEncodedMethod == null) {
-          DexEncodedMethod newMethod = createForwardingMethod(dexMethod, clazz);
+        if (clazz.lookupVirtualMethod(method.getReference()) == null) {
+          DexEncodedMethod newMethod = createForwardingMethod(method, clazz);
           clazz.addVirtualMethod(newMethod);
           newForwardingMethodsConsumer.accept(newMethod);
         }
       }
     }
 
-    private DexEncodedMethod createForwardingMethod(DexMethod target, DexClass clazz) {
+    private DexEncodedMethod createForwardingMethod(DexEncodedMethod target, DexClass clazz) {
       // NOTE: Never add a forwarding method to methods of classes unknown or coming from
       // android.jar
       // even if this results in invalid code, these classes are never desugared.
       // In desugared library, emulated interface methods can be overridden by retarget lib members.
       DexMethod forwardMethod =
           appView.options().desugaredLibraryConfiguration.retargetMethod(target, appView);
-      assert forwardMethod != null && forwardMethod != target;
+      assert forwardMethod != null && forwardMethod != target.getReference();
       return DexEncodedMethod.createDesugaringForwardingMethod(
-          appView.definitionFor(target), clazz, forwardMethod, appView.dexItemFactory());
+          target, clazz, forwardMethod, appView.dexItemFactory());
     }
 
     private void synthesizeEmulatedDispatchMethods(DexApplication.Builder<?> builder) {
@@ -361,7 +360,7 @@
                   | Constants.ACC_INTERFACE);
       ClassAccessFlags holderAccessFlags =
           ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC);
-      for (DexMethod emulatedDispatchMethod : emulatedDispatchMethods) {
+      for (DexEncodedMethod emulatedDispatchMethod : emulatedDispatchMethods) {
         // Dispatch interface.
         DexType interfaceType = dispatchInterfaceTypeFor(emulatedDispatchMethod);
         DexEncodedMethod itfMethod =
@@ -390,7 +389,7 @@
     }
 
     private DexEncodedMethod generateInterfaceDispatchMethod(
-        DexMethod emulatedDispatchMethod, DexType interfaceType) {
+        DexEncodedMethod emulatedDispatchMethod, DexType interfaceType) {
       MethodAccessFlags flags =
           MethodAccessFlags.fromSharedAccessFlags(
               Constants.ACC_PUBLIC | Constants.ACC_ABSTRACT | Constants.ACC_SYNTHETIC, false);
@@ -398,13 +397,15 @@
           appView
               .dexItemFactory()
               .createMethod(
-                  interfaceType, emulatedDispatchMethod.proto, emulatedDispatchMethod.name);
+                  interfaceType,
+                  emulatedDispatchMethod.getProto(),
+                  emulatedDispatchMethod.getName());
       return new DexEncodedMethod(
           newMethod, flags, DexAnnotationSet.empty(), ParameterAnnotationsList.empty(), null, true);
     }
 
     private DexEncodedMethod generateHolderDispatchMethod(
-        DexMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
+        DexEncodedMethod emulatedDispatchMethod, DexType dispatchHolder, DexMethod itfMethod) {
       // The method should look like:
       // static foo(rcvr, arg0, arg1) {
       //    if (rcvr instanceof interfaceType) {
@@ -423,9 +424,9 @@
       DexMethod newMethod =
           appView
               .dexItemFactory()
-              .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.name);
+              .createMethod(dispatchHolder, desugarMethod.proto, emulatedDispatchMethod.getName());
       return DexEncodedMethod.toEmulateDispatchLibraryMethod(
-          emulatedDispatchMethod.holder,
+          emulatedDispatchMethod.getHolderType(),
           newMethod,
           desugarMethod,
           itfMethod,
@@ -435,7 +436,7 @@
   }
 
   private void reportInvalidLibrarySupertype(
-      DexLibraryClass libraryClass, Set<DexMethod> retarget) {
+      DexLibraryClass libraryClass, Set<DexEncodedMethod> retarget) {
     DexClass dexClass = appView.definitionFor(libraryClass.superType);
     String message;
     if (dexClass == null) {
@@ -456,15 +457,15 @@
             retarget);
   }
 
-  private DexType dispatchInterfaceTypeFor(DexMethod method) {
+  private DexType dispatchInterfaceTypeFor(DexEncodedMethod method) {
     return dispatchTypeFor(method, "dispatchInterface");
   }
 
-  private DexType dispatchHolderTypeFor(DexMethod method) {
+  private DexType dispatchHolderTypeFor(DexEncodedMethod method) {
     return dispatchTypeFor(method, "dispatchHolder");
   }
 
-  private DexType dispatchTypeFor(DexMethod method, String suffix) {
+  private DexType dispatchTypeFor(DexEncodedMethod method, String suffix) {
     String descriptor =
         "L"
             + appView
@@ -473,9 +474,9 @@
                 .getSynthesizedLibraryClassesPackagePrefix()
             + DESUGAR_LIB_RETARGET_CLASS_NAME_PREFIX
             + '$'
-            + method.holder.getName()
+            + method.getHolderType().getName()
             + '$'
-            + method.name
+            + method.getName()
             + '$'
             + suffix
             + ';';
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 901517a..27a5224 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -307,7 +307,23 @@
                     invokeSuper.outValue(), invokeSuper.arguments()));
           } else {
             DexType dexType = maximallySpecificEmulatedInterfaceOrNull(invokedMethod);
-            if (dexType != null) {
+            if (dexType == null) {
+              if (clazz.isInterface()
+                  && appView.rewritePrefix.hasRewrittenType(clazz.type, appView)) {
+                DexEncodedMethod target =
+                    appView.appInfoForDesugaring().lookupSuperTarget(invokedMethod, code.context());
+                if (target != null && target.isDefaultMethod()) {
+                  DexClass holder = appView.definitionFor(target.holder());
+                  if (holder.isLibraryClass() && holder.isInterface()) {
+                    instructions.replaceCurrentInstruction(
+                        new InvokeStatic(
+                            defaultAsMethodOfCompanionClass(target.method, factory),
+                            invokeSuper.outValue(),
+                            invokeSuper.arguments()));
+                  }
+                }
+              }
+            } else {
               // That invoke super may not resolve since the super method may not be present
               // since it's in the emulated interface. We need to force resolution. If it resolves
               // to a library method, then it needs to be rewritten.
@@ -324,7 +340,7 @@
                   // retarget or if it just calls a companion class method and rewrite.
                   DexMethod retargetMethod =
                       options.desugaredLibraryConfiguration.retargetMethod(
-                          dexEncodedMethod.method, appView);
+                          dexEncodedMethod, appView);
                   if (retargetMethod == null) {
                     DexMethod originalCompanionMethod =
                         instanceAsMethodOfCompanionClass(
@@ -357,7 +373,7 @@
             continue;
           }
 
-          DexClass clazz = appInfo.definitionFor(method.holder);
+          DexClass clazz = appInfo.definitionForHolder(method);
           if (clazz == null) {
             // Report missing class since we don't know if it is an interface.
             warnMissingType(encodedMethod.method, method.holder);
@@ -367,7 +383,7 @@
                   "defined in library class " + clazz.toSourceString(),
                   getMethodOrigin(encodedMethod.method));
             }
-            DexEncodedMethod directTarget = appView.definitionFor(method);
+            DexEncodedMethod directTarget = clazz.lookupMethod(method);
             if (directTarget != null) {
               // This can be a private instance method call. Note that the referenced
               // method is expected to be in the current class since it is private, but desugaring
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
index 2586141..58730b9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/NestBasedAccessDesugaring.java
@@ -86,8 +86,9 @@
 
   private DexEncodedMethod lookupOnHolder(
       DexMethod method, DexClassAndMethod context, Invoke.Type invokeType) {
-    return appView.definitionFor(
-        appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod());
+    DexMethod rewritten =
+        appView.graphLense().lookupMethod(method, context.getReference(), invokeType).getMethod();
+    return rewritten.lookupOnClass(appView.definitionForHolder(rewritten));
   }
 
   private DexEncodedField lookupOnHolder(DexField field) {
@@ -235,7 +236,7 @@
     DexProto proto =
         encodedMethod.accessFlags.isStatic()
             ? method.proto
-            : appView.dexItemFactory().prependTypeToProto(method.holder, method.proto);
+            : appView.dexItemFactory().prependHolderToProto(method);
     return appView
         .dexItemFactory()
         .createMethod(method.holder, proto, computeMethodBridgeName(encodedMethod));
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index c6447c1..8f4650a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -8,6 +8,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.definitelyNotNull;
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
+import com.android.tools.r8.algorithms.scc.SCC;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
@@ -117,12 +118,10 @@
 import it.unimi.dsi.fastutil.objects.Object2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntMap;
 import it.unimi.dsi.fastutil.objects.Reference2IntOpenHashMap;
-import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
-import java.util.Deque;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
@@ -3591,7 +3590,8 @@
   // This is a simplified variant of the removeRedundantPhis algorithm in Section 3.2 of:
   // http://compilers.cs.uni-saarland.de/papers/bbhlmz13cc.pdf
   private static void replaceTrivialNewInstancePhis(Value newInstanceValue) {
-    List<Set<Value>> components = new SCC().computeSCC(newInstanceValue);
+    List<Set<Value>> components =
+        new SCC<Value>(Value::uniquePhiUsers).computeSCC(newInstanceValue);
     for (int i = components.size() - 1; i >= 0; i--) {
       Set<Value> component = components.get(i);
       if (component.size() == 1 && component.iterator().next() == newInstanceValue) {
@@ -3621,61 +3621,6 @@
     }
   }
 
-  // Dijkstra's path-based strongly-connected components algorithm.
-  // https://en.wikipedia.org/wiki/Path-based_strong_component_algorithm
-  private static class SCC {
-
-    private int currentTime = 0;
-    private final Reference2IntMap<Value> discoverTime = new Reference2IntOpenHashMap<>();
-    private final Set<Value> unassignedSet = Sets.newIdentityHashSet();
-    private final Deque<Value> unassignedStack = new ArrayDeque<>();
-    private final Deque<Value> preorderStack = new ArrayDeque<>();
-    private final List<Set<Value>> components = new ArrayList<>();
-
-    public List<Set<Value>> computeSCC(Value v) {
-      assert currentTime == 0;
-      dfs(v);
-      return components;
-    }
-
-    private void dfs(Value value) {
-      discoverTime.put(value, currentTime++);
-      unassignedSet.add(value);
-      unassignedStack.push(value);
-      preorderStack.push(value);
-      for (Phi phi : value.uniquePhiUsers()) {
-        if (!discoverTime.containsKey(phi)) {
-          // If not seen yet, continue the search.
-          dfs(phi);
-        } else if (unassignedSet.contains(phi)) {
-          // If seen already and the element is on the unassigned stack we have found a cycle.
-          // Pop off everything discovered later than the target from the preorder stack. This may
-          // not coincide with the cycle as an outer cycle may already have popped elements off.
-          int discoverTimeOfPhi = discoverTime.getInt(phi);
-          while (discoverTimeOfPhi < discoverTime.getInt(preorderStack.peek())) {
-            preorderStack.pop();
-          }
-        }
-      }
-      if (preorderStack.peek() == value) {
-        // If the current element is the top of the preorder stack, then we are at entry to a
-        // strongly-connected component consisting of this element and every element above this
-        // element on the stack.
-        Set<Value> component = SetUtils.newIdentityHashSet(unassignedStack.size());
-        while (true) {
-          Value member = unassignedStack.pop();
-          unassignedSet.remove(member);
-          component.add(member);
-          if (member == value) {
-            components.add(component);
-            break;
-          }
-        }
-        preorderStack.pop();
-      }
-    }
-  }
-
   // See comment for InternalOptions.canHaveNumberConversionRegisterAllocationBug().
   public void workaroundNumberConversionRegisterAllocationBug(IRCode code) {
     ListIterator<BasicBlock> blocks = code.listIterator();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
index cb8f858..6fe0808 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Devirtualizer.java
@@ -9,7 +9,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.ProgramMethod;
-import com.android.tools.r8.graph.ResolutionResult;
+import com.android.tools.r8.graph.ResolutionResult.SingleResolutionResult;
 import com.android.tools.r8.ir.analysis.type.TypeAnalysis;
 import com.android.tools.r8.ir.analysis.type.TypeElement;
 import com.android.tools.r8.ir.code.Assume;
@@ -290,61 +290,39 @@
     if (!receiver.getType().isClassType()) {
       return target;
     }
-    DexEncodedMethod encodedTarget = appView.definitionFor(target);
-    if (encodedTarget == null
-        || !canInvokeTargetWithInvokeVirtual(encodedTarget)
-        || !hasAccessToInvokeTargetFromContext(encodedTarget, context)) {
-      // Don't rewrite this instruction as it could remove an error from the program.
+
+    SingleResolutionResult resolutionResult =
+        appView.appInfo().resolveMethodOnClass(target).asSingleResolution();
+    if (resolutionResult == null
+        || resolutionResult
+            .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
+            .isPossiblyFalse()) {
+      // Method does not resolve or is not accessible.
       return target;
     }
-    DexType receiverType =
-        appView.graphLense().lookupType(receiver.getType().asClassType().getClassType());
+
+    DexType receiverType = receiver.getType().asClassType().getClassType();
     if (receiverType == target.holder) {
       // Virtual invoke is already as specific as it can get.
       return target;
     }
-    ResolutionResult resolutionResult =
-        appView.appInfo().resolveMethodOnClass(target, receiverType);
-    DexEncodedMethod newTarget =
-        resolutionResult.isVirtualTarget() ? resolutionResult.getSingleTarget() : null;
-    if (newTarget == null || newTarget.method == target) {
-      // Most likely due to a missing class, or invoke is already as specific as it gets.
+
+    SingleResolutionResult newResolutionResult =
+        appView.appInfo().resolveMethodOnClass(target, receiverType).asSingleResolution();
+    if (newResolutionResult == null
+        || newResolutionResult
+            .isAccessibleForVirtualDispatchFrom(context, appView.appInfo())
+            .isPossiblyFalse()) {
       return target;
     }
-    DexClass newTargetClass = appView.definitionFor(newTarget.holder());
-    if (newTargetClass == null
-        || newTargetClass.isLibraryClass()
-        || !canInvokeTargetWithInvokeVirtual(newTarget)
-        || !hasAccessToInvokeTargetFromContext(newTarget, context)) {
-      // Not safe to invoke `newTarget` with virtual invoke from the current context.
+
+    DexClass newTargetHolder = newResolutionResult.getResolvedHolder();
+    if (!newTargetHolder.isProgramClass() || newTargetHolder.isInterface()) {
+      // Not safe to invoke the new resolution result with virtual invoke from the current context.
       return target;
     }
-    return newTarget.method;
-  }
 
-  private boolean canInvokeTargetWithInvokeVirtual(DexEncodedMethod target) {
-    return target.isNonPrivateVirtualMethod() && appView.isInterface(target.holder()).isFalse();
-  }
-
-  private boolean hasAccessToInvokeTargetFromContext(
-      DexEncodedMethod target, ProgramMethod context) {
-    assert !target.accessFlags.isPrivate();
-    DexType holder = target.holder();
-    if (holder == context.getHolderType()) {
-      // It is always safe to invoke a method from the same enclosing class.
-      return true;
-    }
-    DexClass clazz = appView.definitionFor(holder);
-    if (clazz == null) {
-      // Conservatively report an illegal access.
-      return false;
-    }
-    if (holder.isSamePackage(context.getHolderType())) {
-      // The class must be accessible (note that we have already established that the method is not
-      // private).
-      return !clazz.accessFlags.isPrivate();
-    }
-    // If the method is in another package, then the method and its holder must be public.
-    return clazz.accessFlags.isPublic() && target.accessFlags.isPublic();
+    // Change the invoke-virtual instruction to target the refined resolution result instead.
+    return newResolutionResult.getResolvedMethod().method;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
index 9db947a..5ee91d0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/NestUtils.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -41,42 +42,45 @@
   }
 
   public static void rewriteNestCallsForInlining(
-      IRCode code, DexType callerHolder, AppView<?> appView) {
+      IRCode code, ProgramMethod callerContext, AppView<?> appView) {
     // This method is called when inlining code into the nest member callerHolder.
     InstructionListIterator iterator = code.instructionListIterator();
-    DexClass callerHolderClass = appView.definitionFor(callerHolder);
-    assert callerHolderClass != null;
-    assert code.method().holder() != callerHolder;
+    assert code.context().getHolder() != callerContext.getHolder();
     while (iterator.hasNext()) {
       Instruction instruction = iterator.next();
       if (instruction.isInvokeDirect()) {
         InvokeDirect invoke = instruction.asInvokeDirect();
         DexMethod method = invoke.getInvokedMethod();
-        DexEncodedMethod encodedMethod = appView.definitionFor(method);
+        DexClass holder = appView.definitionForHolder(method);
+        DexEncodedMethod encodedMethod = method.lookupOnClass(holder);
         if (encodedMethod != null && !encodedMethod.isInstanceInitializer()) {
           assert encodedMethod.isPrivateMethod();
           // Call to private method which has now to be interface/virtual
           // (Now call to nest member private method).
           if (invoke.getInterfaceBit()) {
             iterator.replaceCurrentInstruction(
-                new InvokeInterface(method, invoke.outValue(), invoke.inValues()));
+                new InvokeInterface(method, invoke.outValue(), invoke.arguments()));
           } else {
             iterator.replaceCurrentInstruction(
-                new InvokeVirtual(method, invoke.outValue(), invoke.inValues()));
+                new InvokeVirtual(method, invoke.outValue(), invoke.arguments()));
           }
         }
       } else if (instruction.isInvokeInterface() || instruction.isInvokeVirtual()) {
         InvokeMethod invoke = instruction.asInvokeMethod();
         DexMethod method = invoke.getInvokedMethod();
-        if (method.holder == callerHolder) {
-          DexEncodedMethod encodedMethod = appView.definitionFor(method);
+        if (method.holder == callerContext.getHolderType()) {
+          DexClass holder = appView.definitionForHolder(method);
+          DexEncodedMethod encodedMethod = method.lookupOnClass(holder);
           if (encodedMethod != null && encodedMethod.isPrivateMethod()) {
             // Interface/virtual nest member call to private method,
             // which has now to be a direct call
             // (Now call to same class private method).
             iterator.replaceCurrentInstruction(
                 new InvokeDirect(
-                    method, invoke.outValue(), invoke.inValues(), callerHolderClass.isInterface()));
+                    method,
+                    invoke.outValue(),
+                    invoke.arguments(),
+                    callerContext.getHolder().isInterface()));
           }
         }
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index da8c4cc..b3bbbfc 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -484,21 +484,25 @@
               throw new IllegalClassInlinerStateException();
             }
 
-            DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
-            if (singleTarget == null
-                || !singleTarget.isInliningCandidate(
-                    method,
-                    Reason.SIMPLE,
-                    appView.appInfo(),
-                    NopWhyAreYouNotInliningReporter.getInstance())) {
+            DexProgramClass holder =
+                asProgramClassOrNull(appView.definitionForHolder(invokedMethod));
+            if (holder == null) {
               throw new IllegalClassInlinerStateException();
             }
 
-            ProgramMethod singleTargetMethod =
-                new ProgramMethod(
-                    appView.definitionForHolder(singleTarget).asProgramClass(), singleTarget);
-            methodCallsOnInstance.put(
-                invoke, new InliningInfo(singleTargetMethod, root.asNewInstance().clazz));
+            ProgramMethod singleTarget = holder.lookupProgramMethod(invokedMethod);
+            if (singleTarget == null
+                || !singleTarget
+                    .getDefinition()
+                    .isInliningCandidate(
+                        method,
+                        Reason.SIMPLE,
+                        appView.appInfo(),
+                        NopWhyAreYouNotInliningReporter.getInstance())) {
+              throw new IllegalClassInlinerStateException();
+            }
+
+            methodCallsOnInstance.put(invoke, new InliningInfo(singleTarget, eligibleClass.type));
             break;
           }
         }
@@ -787,7 +791,11 @@
       if (parent == null) {
         return null;
       }
-      ProgramMethod encodedParent = asProgramMethodOrNull(appView.definitionFor(parent), appView);
+      DexProgramClass parentClass = asProgramClassOrNull(appView.definitionForHolder(parent));
+      if (parentClass == null) {
+        return null;
+      }
+      ProgramMethod encodedParent = parentClass.lookupProgramMethod(parent);
       if (encodedParent == null) {
         return null;
       }
@@ -1250,12 +1258,13 @@
     if (method == dexItemFactory.objectMembers.constructor) {
       return true;
     }
-    DexEncodedMethod encodedMethod = appView.definitionFor(method);
-    if (encodedMethod == null) {
+    DexProgramClass holder = asProgramClassOrNull(appView.definitionForHolder(method));
+    DexEncodedMethod definition = method.lookupOnClass(holder);
+    if (definition == null) {
       return false;
     }
     InstanceInitializerInfo initializerInfo =
-        encodedMethod.getOptimizationInfo().getInstanceInitializerInfo();
+        definition.getOptimizationInfo().getInstanceInitializerInfo();
     return initializerInfo.receiverNeverEscapesOutsideConstructorChain();
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
index c5cd89d..e03bc6b 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/enums/EnumUnboxer.java
@@ -768,9 +768,7 @@
                   + "$"
                   + method.name.toString());
       DexProto proto =
-          encodedMethod.isStatic()
-              ? method.proto
-              : factory.prependTypeToProto(method.holder, method.proto);
+          encodedMethod.isStatic() ? method.proto : factory.prependHolderToProto(method);
       DexMethod newMethod = factory.createMethod(newHolder, fixupProto(proto), newMethodName);
       lensBuilder.move(method, encodedMethod.isStatic(), newMethod, true);
       encodedMethod.accessFlags.promoteToPublic();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
index 9e270af..8a25765 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/info/MethodOptimizationInfoCollector.java
@@ -44,6 +44,7 @@
 import static com.android.tools.r8.ir.code.Opcodes.XOR;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -549,7 +550,8 @@
             {
               InvokeDirect invoke = instruction.asInvokeDirect();
               DexMethod invokedMethod = invoke.getInvokedMethod();
-              DexEncodedMethod singleTarget = appView.definitionFor(invokedMethod);
+              DexClass holder = appView.definitionForHolder(invokedMethod);
+              DexEncodedMethod singleTarget = invokedMethod.lookupOnClass(holder);
               if (singleTarget == null) {
                 return null;
               }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index a560844..dcb128a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -10,6 +10,7 @@
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.ConstString;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
 import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -42,6 +43,8 @@
       Set<Value> affectedValues) {
     if (singleTarget.method == dexItemFactory.booleanMembers.booleanValue) {
       optimizeBooleanValue(code, instructionIterator, invoke);
+    } else if (singleTarget.method == dexItemFactory.booleanMembers.parseBoolean) {
+      optimizeParseBoolean(code, instructionIterator, invoke);
     } else if (singleTarget.method == dexItemFactory.booleanMembers.valueOf) {
       optimizeValueOf(code, instructionIterator, invoke, affectedValues);
     }
@@ -64,6 +67,25 @@
     }
   }
 
+  private void optimizeParseBoolean(
+      IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+    Value argument = invoke.arguments().get(0).getAliasedValue();
+    if (!argument.isPhi()) {
+      Instruction definition = argument.definition;
+      if (definition.isConstString()) {
+        ConstString constString = definition.asConstString();
+        if (!constString.instructionInstanceCanThrow()) {
+          String value = constString.getValue().toString().toLowerCase();
+          if (value.equals("true")) {
+            instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
+          } else if (value.equals("false")) {
+            instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
+          }
+        }
+      }
+    }
+  }
+
   private void optimizeValueOf(
       IRCode code,
       InstructionListIterator instructionIterator,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
index 92c78af..e7e0fe6 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/LibraryOptimizationInfoInitializer.java
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.ir.analysis.type.Nullability.maybeNull;
 
 import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
@@ -101,10 +102,11 @@
   }
 
   private DexEncodedMethod lookupMethod(DexMethod method) {
-    DexEncodedMethod encodedMethod = appView.definitionFor(method);
-    if (encodedMethod != null) {
+    DexClass holder = appView.definitionForHolder(method);
+    DexEncodedMethod definition = method.lookupOnClass(holder);
+    if (definition != null) {
       modeledLibraryTypes.add(method.holder);
-      return encodedMethod;
+      return definition;
     }
     return null;
   }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
index 7431fb9..65bdeec 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KmVisitorProviders.java
@@ -5,6 +5,11 @@
 package com.android.tools.r8.kotlin;
 
 import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmContractVisitor;
+import kotlinx.metadata.KmEffectExpressionVisitor;
+import kotlinx.metadata.KmEffectInvocationKind;
+import kotlinx.metadata.KmEffectType;
+import kotlinx.metadata.KmEffectVisitor;
 import kotlinx.metadata.KmFunctionVisitor;
 import kotlinx.metadata.KmLambdaVisitor;
 import kotlinx.metadata.KmPropertyVisitor;
@@ -108,4 +113,22 @@
 
     KmVersionRequirementVisitor get();
   }
+
+  @FunctionalInterface
+  public interface KmContractVisitorProvider {
+
+    KmContractVisitor get();
+  }
+
+  @FunctionalInterface
+  public interface KmEffectVisitorProvider {
+
+    KmEffectVisitor get(KmEffectType type, KmEffectInvocationKind effectInvocationKind);
+  }
+
+  @FunctionalInterface
+  public interface KmEffectExpressionVisitorProvider {
+
+    KmEffectExpressionVisitor get();
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
index 07f6b0b..b16b8b6 100644
--- a/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
+++ b/src/main/java/com/android/tools/r8/kotlin/Kotlin.java
@@ -43,6 +43,7 @@
 
     public static final String arrayBinaryName = NAME + "/Array";
     public static final String anyDescriptor = "L" + NAME + "/Any;";
+    public static final String anyName = NAME + "/Any";
   }
 
   // Mappings from JVM types to Kotlin types (of type DexType)
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
new file mode 100644
index 0000000..6a9b23d
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationArgumentInfo.java
@@ -0,0 +1,210 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.Kotlin.ClassClassifiers;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Box;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.AnnotationValue;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import kotlinx.metadata.KmAnnotationArgument.EnumValue;
+import kotlinx.metadata.KmAnnotationArgument.KClassValue;
+
+abstract class KotlinAnnotationArgumentInfo implements EnqueuerMetadataTraceable {
+
+  private static final Map<String, KotlinAnnotationArgumentInfo> EMPTY_ARGUMENTS =
+      ImmutableMap.of();
+
+  abstract KmAnnotationArgument<?> rewrite(
+      AppView<AppInfoWithLiveness> appView, NamingLens namingLens);
+
+  private static KotlinAnnotationArgumentInfo createArgument(
+      KmAnnotationArgument<?> arg, DexItemFactory factory) {
+    if (arg instanceof KClassValue) {
+      return KotlinAnnotationClassValueInfo.create((KClassValue) arg, factory);
+    } else if (arg instanceof EnumValue) {
+      return KotlinAnnotationEnumValueInfo.create((EnumValue) arg, factory);
+    } else if (arg instanceof AnnotationValue) {
+      return KotlinAnnotationAnnotationValueInfo.create((AnnotationValue) arg, factory);
+    } else if (arg instanceof ArrayValue) {
+      return KotlinAnnotationArrayValueInfo.create((ArrayValue) arg, factory);
+    } else {
+      return KotlinAnnotationPrimitiveArgumentInfo.create(arg);
+    }
+  }
+
+  static Map<String, KotlinAnnotationArgumentInfo> create(
+      Map<String, KmAnnotationArgument<?>> arguments, DexItemFactory factory) {
+    if (arguments.isEmpty()) {
+      return EMPTY_ARGUMENTS;
+    }
+    LinkedHashMap<String, KotlinAnnotationArgumentInfo> modeled = new LinkedHashMap<>();
+    arguments.forEach((key, arg) -> modeled.put(key, createArgument(arg, factory)));
+    return modeled;
+  }
+
+  private static class KotlinAnnotationClassValueInfo extends KotlinAnnotationArgumentInfo {
+
+    private final KotlinTypeReference value;
+
+    private KotlinAnnotationClassValueInfo(KotlinTypeReference value) {
+      this.value = value;
+    }
+
+    private static KotlinAnnotationClassValueInfo create(KClassValue arg, DexItemFactory factory) {
+      return new KotlinAnnotationClassValueInfo(
+          KotlinTypeReference.fromBinaryName(arg.getValue(), factory));
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      value.trace(definitionSupplier);
+    }
+
+    @Override
+    KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      return new KClassValue(
+          value.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName));
+    }
+  }
+
+  private static class KotlinAnnotationEnumValueInfo extends KotlinAnnotationArgumentInfo {
+
+    private final KotlinTypeReference enumClassName;
+    private final String enumEntryName;
+
+    private KotlinAnnotationEnumValueInfo(KotlinTypeReference enumClassName, String enumEntryName) {
+      this.enumClassName = enumClassName;
+      this.enumEntryName = enumEntryName;
+    }
+
+    private static KotlinAnnotationEnumValueInfo create(EnumValue arg, DexItemFactory factory) {
+      return new KotlinAnnotationEnumValueInfo(
+          KotlinTypeReference.fromBinaryName(arg.getEnumClassName(), factory),
+          arg.getEnumEntryName());
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      enumClassName.trace(definitionSupplier);
+    }
+
+    @Override
+    KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      return new EnumValue(
+          enumClassName.toRenamedBinaryNameOrDefault(appView, namingLens, ClassClassifiers.anyName),
+          enumEntryName);
+    }
+  }
+
+  private static class KotlinAnnotationAnnotationValueInfo extends KotlinAnnotationArgumentInfo {
+
+    private final KotlinAnnotationInfo value;
+
+    private KotlinAnnotationAnnotationValueInfo(KotlinAnnotationInfo value) {
+      this.value = value;
+    }
+
+    private static KotlinAnnotationAnnotationValueInfo create(
+        AnnotationValue arg, DexItemFactory factory) {
+      return new KotlinAnnotationAnnotationValueInfo(
+          KotlinAnnotationInfo.create(arg.getValue(), factory));
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      value.trace(definitionSupplier);
+    }
+
+    @Override
+    KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      Box<KmAnnotation> rewrittenAnnotation = new Box<>();
+      value.rewrite(rewrittenAnnotation::set, appView, namingLens);
+      if (rewrittenAnnotation.isSet()) {
+        return new AnnotationValue(rewrittenAnnotation.get());
+      }
+      return null;
+    }
+  }
+
+  private static class KotlinAnnotationArrayValueInfo extends KotlinAnnotationArgumentInfo {
+
+    private static final KotlinAnnotationArrayValueInfo EMPTY =
+        new KotlinAnnotationArrayValueInfo(ImmutableList.of());
+
+    private final List<KotlinAnnotationArgumentInfo> value;
+
+    private KotlinAnnotationArrayValueInfo(List<KotlinAnnotationArgumentInfo> value) {
+      this.value = value;
+    }
+
+    private static KotlinAnnotationArrayValueInfo create(ArrayValue arg, DexItemFactory factory) {
+      if (arg.getValue().isEmpty()) {
+        return EMPTY;
+      }
+      ImmutableList.Builder<KotlinAnnotationArgumentInfo> builder = ImmutableList.builder();
+      for (KmAnnotationArgument<?> argument : arg.getValue()) {
+        builder.add(createArgument(argument, factory));
+      }
+      return new KotlinAnnotationArrayValueInfo(builder.build());
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      for (KotlinAnnotationArgumentInfo kotlinAnnotationArgumentInfo : value) {
+        kotlinAnnotationArgumentInfo.trace(definitionSupplier);
+      }
+    }
+
+    @Override
+    KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      List<KmAnnotationArgument<?>> rewrittenArguments = new ArrayList<>();
+      for (KotlinAnnotationArgumentInfo kotlinAnnotationArgumentInfo : value) {
+        KmAnnotationArgument<?> rewrittenArg =
+            kotlinAnnotationArgumentInfo.rewrite(appView, namingLens);
+        if (rewrittenArg != null) {
+          rewrittenArguments.add(rewrittenArg);
+        }
+      }
+      return new ArrayValue(rewrittenArguments);
+    }
+  }
+
+  private static class KotlinAnnotationPrimitiveArgumentInfo extends KotlinAnnotationArgumentInfo {
+
+    private final KmAnnotationArgument<?> argument;
+
+    private KotlinAnnotationPrimitiveArgumentInfo(KmAnnotationArgument<?> argument) {
+      this.argument = argument;
+    }
+
+    private static KotlinAnnotationPrimitiveArgumentInfo create(KmAnnotationArgument<?> argument) {
+      return new KotlinAnnotationPrimitiveArgumentInfo(argument);
+    }
+
+    @Override
+    public void trace(DexDefinitionSupplier definitionSupplier) {
+      // Nothing to trace
+    }
+
+    @Override
+    KmAnnotationArgument<?> rewrite(AppView<AppInfoWithLiveness> appView, NamingLens namingLens) {
+      return argument;
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
index 90ed44a..fd4d087 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinAnnotationInfo.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 import kotlinx.metadata.KmAnnotation;
@@ -23,19 +24,18 @@
   private static final List<KotlinAnnotationInfo> EMPTY_ANNOTATIONS = ImmutableList.of();
 
   private final KotlinTypeReference annotationType;
-  // TODO(b/155053894): Model KmAnnotationArgument.
-  private final Map<String, KmAnnotationArgument<?>> arguments;
+  private final Map<String, KotlinAnnotationArgumentInfo> arguments;
 
   private KotlinAnnotationInfo(
-      KotlinTypeReference annotationType, Map<String, KmAnnotationArgument<?>> arguments) {
+      KotlinTypeReference annotationType, Map<String, KotlinAnnotationArgumentInfo> arguments) {
     this.annotationType = annotationType;
     this.arguments = arguments;
   }
 
-  private static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
+  static KotlinAnnotationInfo create(KmAnnotation annotation, DexItemFactory factory) {
     return new KotlinAnnotationInfo(
         KotlinTypeReference.fromBinaryName(annotation.getClassName(), factory),
-        annotation.getArguments());
+        KotlinAnnotationArgumentInfo.create(annotation.getArguments(), factory));
   }
 
   static List<KotlinAnnotationInfo> create(List<KmAnnotation> annotations, DexItemFactory factory) {
@@ -60,13 +60,20 @@
       return;
     }
     String classifier = DescriptorUtils.descriptorToKotlinClassifier(renamedDescriptor);
-    KmAnnotation annotation = new KmAnnotation(classifier, arguments);
-    visitorProvider.get(annotation);
+    Map<String, KmAnnotationArgument<?>> rewrittenArguments = new LinkedHashMap<>();
+    arguments.forEach(
+        (key, arg) -> {
+          KmAnnotationArgument<?> rewrittenArg = arg.rewrite(appView, namingLens);
+          if (rewrittenArg != null) {
+            rewrittenArguments.put(key, rewrittenArg);
+          }
+        });
+    visitorProvider.get(new KmAnnotation(classifier, rewrittenArguments));
   }
 
   @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     annotationType.trace(definitionSupplier);
-    // TODO(b/155053894): Trace annotation arguments.
+    arguments.forEach((ignored, arg) -> arg.trace(definitionSupplier));
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 9987457..c32bcc4 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -27,6 +27,7 @@
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
 import kotlinx.metadata.KmType;
+import kotlinx.metadata.jvm.JvmClassExtensionVisitor;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
 import kotlinx.metadata.jvm.JvmMethodSignature;
 import kotlinx.metadata.jvm.KotlinClassHeader;
@@ -48,6 +49,7 @@
   private final KotlinVersionRequirementInfo versionRequirements;
   private final KotlinTypeReference anonymousObjectOrigin;
   private final String packageName;
+  private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
 
   private KotlinClassInfo(
       int flags,
@@ -62,7 +64,8 @@
       List<String> enumEntries,
       KotlinVersionRequirementInfo versionRequirements,
       KotlinTypeReference anonymousObjectOrigin,
-      String packageName) {
+      String packageName,
+      KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
     this.flags = flags;
     this.name = name;
     this.moduleName = moduleName;
@@ -76,6 +79,7 @@
     this.versionRequirements = versionRequirements;
     this.anonymousObjectOrigin = anonymousObjectOrigin;
     this.packageName = packageName;
+    this.localDelegatedProperties = localDelegatedProperties;
   }
 
   public static KotlinClassInfo create(
@@ -125,7 +129,9 @@
         kmClass.getEnumEntries(),
         KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
         getAnonymousObjectOrigin(kmClass, factory),
-        packageName);
+        packageName,
+        KotlinLocalDelegatedPropertyInfo.create(
+            JvmExtensionsKt.getLocalDelegatedProperties(kmClass), factory, reporter));
   }
 
   private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -266,15 +272,19 @@
     // TODO(b/154347404): Understand enum entries.
     kmClass.getEnumEntries().addAll(enumEntries);
     versionRequirements.rewrite(kmClass::visitVersionRequirement);
-    JvmExtensionsKt.setModuleName(kmClass, moduleName);
+    JvmClassExtensionVisitor extensionVisitor =
+        (JvmClassExtensionVisitor) kmClass.visitExtensions(JvmClassExtensionVisitor.TYPE);
+    extensionVisitor.visitModuleName(moduleName);
     if (anonymousObjectOrigin != null) {
       String renamedAnon =
           anonymousObjectOrigin.toRenamedBinaryNameOrDefault(appView, namingLens, null);
       if (renamedAnon != null) {
-        JvmExtensionsKt.setAnonymousObjectOriginName(kmClass, renamedAnon);
+        extensionVisitor.visitAnonymousObjectOriginName(renamedAnon);
       }
     }
-
+    localDelegatedProperties.rewrite(
+        extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+    extensionVisitor.visitEnd();
     KotlinClassMetadata.Class.Writer writer = new KotlinClassMetadata.Class.Writer();
     kmClass.accept(writer);
     return writer.write().getHeader();
@@ -293,6 +303,7 @@
     forEachApply(superTypes, type -> type::trace, definitionSupplier);
     forEachApply(sealedSubClasses, sealed -> sealed::trace, definitionSupplier);
     forEachApply(nestedClasses, nested -> nested::trace, definitionSupplier);
+    localDelegatedProperties.trace(definitionSupplier);
     // TODO(b/154347404): trace enum entries.
     if (anonymousObjectOrigin != null) {
       anonymousObjectOrigin.trace(definitionSupplier);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
new file mode 100644
index 0000000..21fb43a
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinContractInfo.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmContract;
+import kotlinx.metadata.KmContractVisitor;
+import kotlinx.metadata.KmEffect;
+
+public class KotlinContractInfo implements EnqueuerMetadataTraceable {
+
+  private static final KotlinContractInfo NO_EFFECT = new KotlinContractInfo(ImmutableList.of());
+
+  private final List<KotlinEffectInfo> effects;
+
+  private KotlinContractInfo(List<KotlinEffectInfo> effects) {
+    this.effects = effects;
+  }
+
+  static KotlinContractInfo create(
+      KmContract kmContract, DexItemFactory factory, Reporter reporter) {
+    if (kmContract == null) {
+      return NO_EFFECT;
+    }
+    List<KmEffect> effects = kmContract.getEffects();
+    if (effects.isEmpty()) {
+      return NO_EFFECT;
+    }
+    ImmutableList.Builder<KotlinEffectInfo> builder = ImmutableList.builder();
+    for (KmEffect effect : effects) {
+      builder.add(KotlinEffectInfo.create(effect, factory, reporter));
+    }
+    return new KotlinContractInfo(builder.build());
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(effects, effect -> effect::trace, definitionSupplier);
+  }
+
+  public void rewrite(
+      KmVisitorProviders.KmContractVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    if (this == NO_EFFECT) {
+      return;
+    }
+    KmContractVisitor kmContractVisitor = visitorProvider.get();
+    for (KotlinEffectInfo effect : effects) {
+      effect.rewrite(kmContractVisitor::visitEffect, appView, namingLens);
+    }
+    kmContractVisitor.visitEnd();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
new file mode 100644
index 0000000..d3ce846
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectExpressionInfo.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmEffectExpressionVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmConstantValue;
+import kotlinx.metadata.KmEffectExpression;
+import kotlinx.metadata.KmEffectExpressionVisitor;
+
+public class KotlinEffectExpressionInfo implements EnqueuerMetadataTraceable {
+
+  private static final List<KotlinEffectExpressionInfo> NO_EXPRESSIONS = ImmutableList.of();
+  private static final KotlinEffectExpressionInfo NO_EXPRESSION =
+      new KotlinEffectExpressionInfo(0, 0, null, null, NO_EXPRESSIONS, NO_EXPRESSIONS);
+
+  private final int flags;
+  private final Integer parameterIndex;
+  private final KmConstantValue constantValue;
+  private final KotlinTypeInfo isInstanceType;
+  private final List<KotlinEffectExpressionInfo> andArguments;
+  private final List<KotlinEffectExpressionInfo> orArguments;
+
+  private KotlinEffectExpressionInfo(
+      int flags,
+      Integer parameterIndex,
+      KmConstantValue constantValue,
+      KotlinTypeInfo isInstanceType,
+      List<KotlinEffectExpressionInfo> andArguments,
+      List<KotlinEffectExpressionInfo> orArguments) {
+    this.flags = flags;
+    this.parameterIndex = parameterIndex;
+    this.constantValue = constantValue;
+    this.isInstanceType = isInstanceType;
+    this.andArguments = andArguments;
+    this.orArguments = orArguments;
+  }
+
+  static KotlinEffectExpressionInfo create(
+      KmEffectExpression effectExpression, DexItemFactory factory, Reporter reporter) {
+    if (effectExpression == null) {
+      return NO_EXPRESSION;
+    }
+    return new KotlinEffectExpressionInfo(
+        effectExpression.getFlags(),
+        effectExpression.getParameterIndex(),
+        effectExpression.getConstantValue(),
+        KotlinTypeInfo.create(effectExpression.isInstanceType(), factory, reporter),
+        create(effectExpression.getAndArguments(), factory, reporter),
+        create(effectExpression.getOrArguments(), factory, reporter));
+  }
+
+  static List<KotlinEffectExpressionInfo> create(
+      List<KmEffectExpression> effectExpressions, DexItemFactory factory, Reporter reporter) {
+    if (effectExpressions.isEmpty()) {
+      return NO_EXPRESSIONS;
+    }
+    ImmutableList.Builder<KotlinEffectExpressionInfo> builder = ImmutableList.builder();
+    for (KmEffectExpression effectExpression : effectExpressions) {
+      builder.add(create(effectExpression, factory, reporter));
+    }
+    return builder.build();
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    if (this == NO_EXPRESSION) {
+      return;
+    }
+    if (isInstanceType != null) {
+      isInstanceType.trace(definitionSupplier);
+    }
+    forEachApply(andArguments, arg -> arg::trace, definitionSupplier);
+    forEachApply(orArguments, arg -> arg::trace, definitionSupplier);
+  }
+
+  public void rewrite(
+      KmEffectExpressionVisitorProvider provider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    if (this == NO_EXPRESSION) {
+      return;
+    }
+    KmEffectExpressionVisitor visitor = provider.get();
+    visitor.visit(flags, parameterIndex);
+    if (constantValue != null) {
+      visitor.visitConstantValue(constantValue.getValue());
+    }
+    if (isInstanceType != null) {
+      isInstanceType.rewrite(visitor::visitIsInstanceType, appView, namingLens);
+    }
+    for (KotlinEffectExpressionInfo andArgument : andArguments) {
+      andArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+    }
+    for (KotlinEffectExpressionInfo orArgument : orArguments) {
+      orArgument.rewrite(visitor::visitAndArgument, appView, namingLens);
+    }
+    visitor.visitEnd();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
new file mode 100644
index 0000000..e38f158
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEffectInfo.java
@@ -0,0 +1,66 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmEffectVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import java.util.List;
+import kotlinx.metadata.KmEffect;
+import kotlinx.metadata.KmEffectInvocationKind;
+import kotlinx.metadata.KmEffectType;
+import kotlinx.metadata.KmEffectVisitor;
+
+public class KotlinEffectInfo implements EnqueuerMetadataTraceable {
+
+  private final KmEffectType type;
+  private final KmEffectInvocationKind invocationKind;
+  private final List<KotlinEffectExpressionInfo> constructorArguments;
+  private final KotlinEffectExpressionInfo conclusion;
+
+  public KotlinEffectInfo(
+      KmEffectType type,
+      KmEffectInvocationKind invocationKind,
+      List<KotlinEffectExpressionInfo> constructorArguments,
+      KotlinEffectExpressionInfo conclusion) {
+    this.type = type;
+    this.invocationKind = invocationKind;
+    this.constructorArguments = constructorArguments;
+    this.conclusion = conclusion;
+  }
+
+  static KotlinEffectInfo create(KmEffect effect, DexItemFactory factory, Reporter reporter) {
+    return new KotlinEffectInfo(
+        effect.getType(),
+        effect.getInvocationKind(),
+        KotlinEffectExpressionInfo.create(effect.getConstructorArguments(), factory, reporter),
+        KotlinEffectExpressionInfo.create(effect.getConclusion(), factory, reporter));
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(constructorArguments, arg -> arg::trace, definitionSupplier);
+    conclusion.trace(definitionSupplier);
+  }
+
+  void rewrite(
+      KmEffectVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    KmEffectVisitor kmEffectVisitor = visitorProvider.get(type, invocationKind);
+    conclusion.rewrite(kmEffectVisitor::visitConclusionOfConditionalEffect, appView, namingLens);
+    for (KotlinEffectExpressionInfo constructorArgument : constructorArguments) {
+      constructorArgument.rewrite(kmEffectVisitor::visitConstructorArgument, appView, namingLens);
+    }
+    kmEffectVisitor.visitEnd();
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
index d71d574..dfcbac5 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinFunctionInfo.java
@@ -39,6 +39,8 @@
   private final KotlinTypeReference lambdaClassOrigin;
   // Information about version requirements.
   private final KotlinVersionRequirementInfo versionRequirements;
+  // Kotlin contract information.
+  private final KotlinContractInfo contract;
   // A value describing if any of the parameters are crossinline.
   private final boolean crossInlineParameter;
 
@@ -52,6 +54,7 @@
       KotlinJvmMethodSignatureInfo signature,
       KotlinTypeReference lambdaClassOrigin,
       KotlinVersionRequirementInfo versionRequirements,
+      KotlinContractInfo contract,
       boolean crossInlineParameter) {
     this.flags = flags;
     this.name = name;
@@ -62,6 +65,7 @@
     this.signature = signature;
     this.lambdaClassOrigin = lambdaClassOrigin;
     this.versionRequirements = versionRequirements;
+    this.contract = contract;
     this.crossInlineParameter = crossInlineParameter;
   }
 
@@ -90,6 +94,7 @@
         KotlinJvmMethodSignatureInfo.create(JvmExtensionsKt.getSignature(kmFunction), factory),
         getlambdaClassOrigin(kmFunction, factory),
         KotlinVersionRequirementInfo.create(kmFunction.getVersionRequirements()),
+        KotlinContractInfo.create(kmFunction.getContract(), factory, reporter),
         isCrossInline);
   }
 
@@ -141,6 +146,7 @@
         extensionVisitor.visitLambdaClassOriginName(lambdaClassOriginName);
       }
     }
+    contract.rewrite(kmFunction::visitContract, appView, namingLens);
   }
 
   @Override
@@ -175,5 +181,6 @@
     if (lambdaClassOrigin != null) {
       lambdaClassOrigin.trace(definitionSupplier);
     }
+    contract.trace(definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
new file mode 100644
index 0000000..cab770f
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinLocalDelegatedPropertyInfo.java
@@ -0,0 +1,64 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin;
+
+import static com.android.tools.r8.utils.FunctionUtils.forEachApply;
+
+import com.android.tools.r8.graph.AppView;
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.kotlin.KmVisitorProviders.KmPropertyVisitorProvider;
+import com.android.tools.r8.naming.NamingLens;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.EnqueuerMetadataTraceable;
+import com.android.tools.r8.utils.Reporter;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import kotlinx.metadata.KmProperty;
+
+public class KotlinLocalDelegatedPropertyInfo implements EnqueuerMetadataTraceable {
+
+  private static final KotlinLocalDelegatedPropertyInfo EMPTY_DELEGATED_PROPERTIES =
+      new KotlinLocalDelegatedPropertyInfo(ImmutableList.of());
+
+  private final List<KotlinPropertyInfo> propertyInfos;
+
+  private KotlinLocalDelegatedPropertyInfo(List<KotlinPropertyInfo> propertyInfos) {
+    this.propertyInfos = propertyInfos;
+  }
+
+  static KotlinLocalDelegatedPropertyInfo create(
+      List<KmProperty> kmProperties, DexItemFactory factory, Reporter reporter) {
+    if (kmProperties == null || kmProperties.size() == 0) {
+      return EMPTY_DELEGATED_PROPERTIES;
+    }
+    ImmutableList.Builder<KotlinPropertyInfo> builder = ImmutableList.builder();
+    for (KmProperty kmProperty : kmProperties) {
+      KotlinPropertyInfo kotlinPropertyInfo =
+          KotlinPropertyInfo.create(kmProperty, factory, reporter);
+      // For ordinary properties, we place these on the fields and methods, but these are hooked in,
+      // and do not have any jvm signatures:
+      assert kotlinPropertyInfo.getFieldSignature() == null;
+      assert kotlinPropertyInfo.getGetterSignature() == null;
+      assert kotlinPropertyInfo.getSetterSignature() == null;
+      builder.add(kotlinPropertyInfo);
+    }
+    return new KotlinLocalDelegatedPropertyInfo(builder.build());
+  }
+
+  @Override
+  public void trace(DexDefinitionSupplier definitionSupplier) {
+    forEachApply(propertyInfos, prop -> prop::trace, definitionSupplier);
+  }
+
+  public void rewrite(
+      KmPropertyVisitorProvider visitorProvider,
+      AppView<AppInfoWithLiveness> appView,
+      NamingLens namingLens) {
+    for (KotlinPropertyInfo propertyInfo : propertyInfos) {
+      propertyInfo.rewrite(visitorProvider, null, null, null, appView, namingLens);
+    }
+  }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
index ba814e9..680bd5c 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataEnqueuerExtension.java
@@ -121,11 +121,6 @@
     }
 
     @Override
-    public DexEncodedMethod definitionFor(DexMethod method) {
-      throw new Unreachable("Should not be called");
-    }
-
-    @Override
     public DexClass definitionFor(DexType type) {
       // TODO(b/157700128) Metadata cannot at this point keep anything alive. Therefore, if a type
       //  has been pruned it may still be referenced, so we do an early check here to ensure it will
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
index ce0ba80..dfddc1d 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataRewriter.java
@@ -83,27 +83,33 @@
       KotlinClassHeader header, String packageName) {
     List<DexAnnotationElement> elements = new ArrayList<>();
     elements.add(
-        new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
-    elements.add(
         new DexAnnotationElement(
             kotlin.metadata.metadataVersion, createIntArray(header.getMetadataVersion())));
     elements.add(
         new DexAnnotationElement(
             kotlin.metadata.bytecodeVersion, createIntArray(header.getBytecodeVersion())));
     elements.add(
+        new DexAnnotationElement(kotlin.metadata.kind, DexValueInt.create(header.getKind())));
+    elements.add(
         new DexAnnotationElement(kotlin.metadata.data1, createStringArray(header.getData1())));
     elements.add(
         new DexAnnotationElement(kotlin.metadata.data2, createStringArray(header.getData2())));
-    elements.add(
-        new DexAnnotationElement(
-            kotlin.metadata.extraString,
-            new DexValueString(factory.createString(header.getExtraString()))));
-    elements.add(
-        new DexAnnotationElement(
-            kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
-    elements.add(
-        new DexAnnotationElement(
-            kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+    if (packageName != null && !packageName.isEmpty()) {
+      elements.add(
+          new DexAnnotationElement(
+              kotlin.metadata.packageName, new DexValueString(factory.createString(packageName))));
+    }
+    if (!header.getExtraString().isEmpty()) {
+      elements.add(
+          new DexAnnotationElement(
+              kotlin.metadata.extraString,
+              new DexValueString(factory.createString(header.getExtraString()))));
+    }
+    if (header.getExtraInt() != 0) {
+      elements.add(
+          new DexAnnotationElement(
+              kotlin.metadata.extraInt, DexValueInt.create(header.getExtraInt())));
+    }
     DexEncodedAnnotation encodedAnnotation =
         new DexEncodedAnnotation(
             factory.kotlinMetadataType, elements.toArray(DexAnnotationElement.EMPTY_ARRAY));
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
index bc124c6..1a51644 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMetadataWriter.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.utils.Action;
 import com.android.tools.r8.utils.StringUtils;
 import java.io.PrintStream;
+import java.util.Collection;
 import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
@@ -20,9 +21,13 @@
 import kotlinx.metadata.InconsistentKotlinMetadataException;
 import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
 import kotlinx.metadata.KmClass;
 import kotlinx.metadata.KmConstructor;
+import kotlinx.metadata.KmContract;
 import kotlinx.metadata.KmDeclarationContainer;
+import kotlinx.metadata.KmEffect;
+import kotlinx.metadata.KmEffectExpression;
 import kotlinx.metadata.KmFlexibleTypeUpperBound;
 import kotlinx.metadata.KmFunction;
 import kotlinx.metadata.KmLambda;
@@ -174,7 +179,7 @@
       String indent,
       String typeDescription,
       StringBuilder sb,
-      List<T> items,
+      Collection<T> items,
       BiConsumer<String, T> appendItem) {
     if (items.isEmpty()) {
       sb.append(typeDescription).append("[]");
@@ -424,6 +429,18 @@
               sb,
               nextIndent -> appendValueParameters(nextIndent, sb, function.getValueParameters()));
           appendKmVersionRequirement(newIndent, sb, function.getVersionRequirements());
+          KmContract contract = function.getContract();
+          if (contract == null) {
+            appendKeyValue(newIndent, "contract", sb, "null");
+          } else {
+            appendKeyValue(
+                newIndent,
+                "contract",
+                sb,
+                nextIndent -> {
+                  appendKmContract(nextIndent, sb, contract);
+                });
+          }
           JvmMethodSignature signature = JvmExtensionsKt.getSignature(function);
           appendKeyValue(
               newIndent, "signature", sb, signature != null ? signature.asString() : "null");
@@ -753,13 +770,47 @@
               sb,
               nextIndent -> {
                 Map<String, KmAnnotationArgument<?>> arguments = kmAnnotation.getArguments();
-                for (String key : arguments.keySet()) {
-                  appendKeyValue(nextIndent, key, sb, arguments.get(key).toString());
-                }
+                appendKmList(
+                    nextIndent,
+                    "{ key: String, value: KmAnnotationArgument<?> }",
+                    sb,
+                    arguments.keySet(),
+                    (nextNextIndent, key) -> {
+                      appendKmSection(
+                          nextNextIndent,
+                          "",
+                          sb,
+                          nextNextNextIndent -> {
+                            appendKeyValue(
+                                nextNextNextIndent,
+                                key,
+                                sb,
+                                nextNextNextNextIndent -> {
+                                  appendKmArgument(nextNextNextIndent, sb, arguments.get(key));
+                                });
+                          });
+                    });
               });
         });
   }
 
+  private static void appendKmArgument(
+      String indent, StringBuilder sb, KmAnnotationArgument<?> annotationArgument) {
+    if (annotationArgument instanceof KmAnnotationArgument.ArrayValue) {
+      List<KmAnnotationArgument<?>> value = ((ArrayValue) annotationArgument).getValue();
+      appendKmList(
+          indent,
+          "ArrayValue",
+          sb,
+          value,
+          (newIndent, annoArg) -> {
+            appendKmArgument(newIndent, sb, annoArg);
+          });
+    } else {
+      sb.append(annotationArgument.toString());
+    }
+  }
+
   private static void appendKmVersionRequirement(
       String indent, StringBuilder sb, List<KmVersionRequirement> kmVersionRequirements) {
     appendKeyValue(
@@ -799,4 +850,123 @@
               });
         });
   }
+
+  private static void appendKmContract(String indent, StringBuilder sb, KmContract contract) {
+    appendKmSection(
+        indent,
+        "KmContract",
+        sb,
+        newIndent -> {
+          appendKeyValue(
+              newIndent,
+              "effects",
+              sb,
+              nextIndent ->
+                  appendKmList(
+                      nextIndent,
+                      "KmEffect",
+                      sb,
+                      contract.getEffects(),
+                      (nextNextIndent, effect) -> appendKmEffect(nextNextIndent, sb, effect)));
+        });
+  }
+
+  private static void appendKmEffect(String indent, StringBuilder sb, KmEffect effect) {
+    appendKmSection(
+        indent,
+        "KmEffect",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "type", sb, effect.getType().toString());
+          appendKeyValue(
+              newIndent,
+              "invocationKind",
+              sb,
+              effect.getInvocationKind() == null ? "null" : effect.getInvocationKind().toString());
+          appendKeyValue(
+              newIndent,
+              "constructorArguments",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmEffectExpression",
+                    sb,
+                    effect.getConstructorArguments(),
+                    (nextNextIndent, expression) -> {
+                      appendKmEffectExpression(nextNextIndent, sb, expression);
+                    });
+              });
+          KmEffectExpression conclusion = effect.getConclusion();
+          if (conclusion == null) {
+            appendKeyValue(newIndent, "conclusion", sb, "null");
+          } else {
+            appendKeyValue(
+                newIndent,
+                "conclusion",
+                sb,
+                nextIndent -> appendKmEffectExpression(nextIndent, sb, conclusion));
+          }
+        });
+  }
+
+  private static void appendKmEffectExpression(
+      String indent, StringBuilder sb, KmEffectExpression expression) {
+    appendKmSection(
+        indent,
+        "KmEffectExpression",
+        sb,
+        newIndent -> {
+          appendKeyValue(newIndent, "flags", sb, expression.getFlags() + "");
+          appendKeyValue(
+              newIndent,
+              "foo",
+              sb,
+              expression.getParameterIndex() == null
+                  ? "null"
+                  : expression.getParameterIndex() + "");
+          appendKeyValue(
+              newIndent,
+              "constantValue",
+              sb,
+              expression.getConstantValue() == null
+                  ? "null"
+                  : expression.getConstantValue().toString());
+          appendKeyValue(
+              newIndent,
+              "isInstanceType",
+              sb,
+              nextIndent -> {
+                appendKmType(nextIndent, sb, expression.isInstanceType());
+              });
+          appendKeyValue(
+              newIndent,
+              "andArguments",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmEffectExpression",
+                    sb,
+                    expression.getAndArguments(),
+                    (nextNextIndent, expr) -> {
+                      appendKmEffectExpression(nextNextIndent, sb, expr);
+                    });
+              });
+          appendKeyValue(
+              newIndent,
+              "orArguments",
+              sb,
+              nextIndent -> {
+                appendKmList(
+                    nextIndent,
+                    "KmEffectExpression",
+                    sb,
+                    expression.getOrArguments(),
+                    (nextNextIndent, expr) -> {
+                      appendKmEffectExpression(nextNextIndent, sb, expr);
+                    });
+              });
+        });
+  }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
index 8f0268a..3fab975 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPackageInfo.java
@@ -22,16 +22,22 @@
 import java.util.function.Consumer;
 import kotlinx.metadata.KmPackage;
 import kotlinx.metadata.jvm.JvmExtensionsKt;
+import kotlinx.metadata.jvm.JvmPackageExtensionVisitor;
 
 // Holds information about a KmPackage object.
 public class KotlinPackageInfo implements EnqueuerMetadataTraceable {
 
   private final String moduleName;
   private final KotlinDeclarationContainerInfo containerInfo;
+  private final KotlinLocalDelegatedPropertyInfo localDelegatedProperties;
 
-  private KotlinPackageInfo(String moduleName, KotlinDeclarationContainerInfo containerInfo) {
+  private KotlinPackageInfo(
+      String moduleName,
+      KotlinDeclarationContainerInfo containerInfo,
+      KotlinLocalDelegatedPropertyInfo localDelegatedProperties) {
     this.moduleName = moduleName;
     this.containerInfo = containerInfo;
+    this.localDelegatedProperties = localDelegatedProperties;
   }
 
   public static KotlinPackageInfo create(
@@ -51,7 +57,9 @@
     return new KotlinPackageInfo(
         JvmExtensionsKt.getModuleName(kmPackage),
         KotlinDeclarationContainerInfo.create(
-            kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode));
+            kmPackage, methodMap, fieldMap, factory, reporter, keepByteCode),
+        KotlinLocalDelegatedPropertyInfo.create(
+            JvmExtensionsKt.getLocalDelegatedProperties(kmPackage), factory, reporter));
   }
 
   public void rewrite(
@@ -66,11 +74,17 @@
         clazz,
         appView,
         namingLens);
-    JvmExtensionsKt.setModuleName(kmPackage, moduleName);
+    JvmPackageExtensionVisitor extensionVisitor =
+        (JvmPackageExtensionVisitor) kmPackage.visitExtensions(JvmPackageExtensionVisitor.TYPE);
+    localDelegatedProperties.rewrite(
+        extensionVisitor::visitLocalDelegatedProperty, appView, namingLens);
+    extensionVisitor.visitModuleName(moduleName);
+    extensionVisitor.visitEnd();
   }
 
   @Override
   public void trace(DexDefinitionSupplier definitionSupplier) {
     containerInfo.trace(definitionSupplier);
+    localDelegatedProperties.trace(definitionSupplier);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
index 4d14c67..c718992 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinPropertyInfo.java
@@ -129,6 +129,18 @@
     return this;
   }
 
+  public KotlinJvmFieldSignatureInfo getFieldSignature() {
+    return fieldSignature;
+  }
+
+  public KotlinJvmMethodSignatureInfo getGetterSignature() {
+    return getterSignature;
+  }
+
+  public KotlinJvmMethodSignatureInfo getSetterSignature() {
+    return setterSignature;
+  }
+
   void rewrite(
       KmVisitorProviders.KmPropertyVisitorProvider visitorProvider,
       DexEncodedField field,
diff --git a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
index 0c223b1..9b659a0 100644
--- a/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ClassNameMinifier.java
@@ -17,13 +17,11 @@
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.InnerClassAttribute;
-import com.android.tools.r8.naming.signature.GenericSignatureRewriter;
 import com.android.tools.r8.shaking.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.ProguardPackageNameList;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
-import com.android.tools.r8.utils.IteratorUtils;
 import com.android.tools.r8.utils.Timing;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
@@ -146,13 +144,6 @@
     }
     timing.end();
 
-    timing.begin("rename-generic");
-    new GenericSignatureRewriter(appView, renaming)
-        .run(
-            () -> IteratorUtils.filter(classes.iterator(), DexClass::isProgramClass),
-            executorService);
-    timing.end();
-
     timing.begin("rename-arrays");
     appView.dexItemFactory().forAllTypes(this::renameArrayTypeIfNeeded);
     timing.end();
diff --git a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
index bbefa18..060f171 100644
--- a/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/InterfaceMethodNameMinifier.java
@@ -20,6 +20,7 @@
 import com.google.common.base.Equivalence;
 import com.google.common.base.Equivalence.Wrapper;
 import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
 import java.io.PrintStream;
 import java.util.ArrayList;
 import java.util.Collection;
@@ -110,16 +111,13 @@
       this.iface = iface;
     }
 
-    DexString getReservedName(DexMethod method) {
+    DexString getReservedName(DexEncodedMethod method) {
       // If an interface is kept and we are using applymapping, the renamed name for this method
       // is tracked on this level.
       if (appView.options().getProguardConfiguration().hasApplyMappingFile()) {
-        DexEncodedMethod encodedMethod = appView.definitionFor(method);
-        if (encodedMethod != null) {
-          DexString reservedName = minifierState.getReservedName(encodedMethod, iface);
-          if (reservedName != null) {
-            return reservedName;
-          }
+        DexString reservedName = minifierState.getReservedName(method, iface);
+        if (reservedName != null) {
+          return reservedName;
         }
       }
       // Otherwise, we just search the hierarchy for the first identity reservation since
@@ -131,18 +129,18 @@
                   Set<DexString> reservedNamesFor =
                       minifierState
                           .getReservationState(reservationType)
-                          .getReservedNamesFor(method);
+                          .getReservedNamesFor(method.getReference());
                   assert reservedNamesFor == null || !reservedNamesFor.isEmpty();
-                  if (reservedNamesFor != null && reservedNamesFor.contains(method.name)) {
+                  if (reservedNamesFor != null && reservedNamesFor.contains(method.getName())) {
                     return true;
                   }
                 }
                 return null;
               });
-      return isReserved == null ? null : method.name;
+      return isReserved == null ? null : method.getName();
     }
 
-    void reserveName(DexString reservedName, DexMethod method) {
+    void reserveName(DexString reservedName, DexEncodedMethod method) {
       forAll(
           s -> {
             s.reservationTypes.forEach(
@@ -153,13 +151,13 @@
           });
     }
 
-    boolean isAvailable(DexString candidate, DexMethod method) {
+    boolean isAvailable(DexString candidate, DexEncodedMethod method) {
       Boolean result =
           forAny(
               s -> {
                 for (DexType resType : s.reservationTypes) {
                   MethodNamingState<?> state = minifierState.getNamingState(resType);
-                  if (!state.isAvailable(candidate, method)) {
+                  if (!state.isAvailable(candidate, method.getReference())) {
                     return false;
                   }
                 }
@@ -168,15 +166,11 @@
       return result == null ? true : result;
     }
 
-    void addRenaming(DexString newName, DexMethod method) {
+    void addRenaming(DexString newName, DexEncodedMethod method) {
       forAll(
-          s -> {
-            s.reservationTypes.forEach(
-                resType -> {
-                  MethodNamingState<?> state = minifierState.getNamingState(resType);
-                  state.addRenaming(newName, method);
-                });
-          });
+          s ->
+              s.reservationTypes.forEach(
+                  resType -> minifierState.getNamingState(resType).addRenaming(newName, method)));
     }
 
     <T> void forAll(Consumer<InterfaceReservationState> action) {
@@ -241,17 +235,18 @@
   class InterfaceMethodGroupState implements Comparable<InterfaceMethodGroupState> {
 
     private final Set<DexCallSite> callSites = new HashSet<>();
-    private final Map<DexMethod, Set<InterfaceReservationState>> methodStates = new HashMap<>();
-    private final List<DexMethod> callSiteCollidingMethods = new ArrayList<>();
+    private final Map<DexEncodedMethod, Set<InterfaceReservationState>> methodStates =
+        new HashMap<>();
+    private final List<DexEncodedMethod> callSiteCollidingMethods = new ArrayList<>();
 
-    void addState(DexMethod method, InterfaceReservationState interfaceState) {
+    void addState(DexEncodedMethod method, InterfaceReservationState interfaceState) {
       methodStates.computeIfAbsent(method, m -> new HashSet<>()).add(interfaceState);
     }
 
     void appendMethodGroupState(InterfaceMethodGroupState state) {
       callSites.addAll(state.callSites);
       callSiteCollidingMethods.addAll(state.callSiteCollidingMethods);
-      for (DexMethod key : state.methodStates.keySet()) {
+      for (DexEncodedMethod key : state.methodStates.keySet()) {
         methodStates.computeIfAbsent(key, k -> new HashSet<>()).addAll(state.methodStates.get(key));
       }
     }
@@ -269,14 +264,14 @@
       // It is perfectly fine to have multiple reserved names inside a group. If we have an identity
       // reservation, we have to prioritize that over the others, otherwise we just propose the
       // first ordered reserved name since we do not allow overwriting the name.
-      List<DexMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
-      sortedMethods.sort(DexMethod::slowCompareTo);
+      List<DexEncodedMethod> sortedMethods = Lists.newArrayList(methodStates.keySet());
+      sortedMethods.sort((x, y) -> x.getReference().slowCompareTo(y.getReference()));
       DexString reservedName = null;
-      for (DexMethod method : sortedMethods) {
+      for (DexEncodedMethod method : sortedMethods) {
         for (InterfaceReservationState state : methodStates.get(method)) {
           DexString stateReserved = state.getReservedName(method);
-          if (stateReserved == method.name) {
-            return method.name;
+          if (stateReserved == method.getName()) {
+            return method.getName();
           } else if (stateReserved != null) {
             reservedName = stateReserved;
           }
@@ -321,7 +316,7 @@
           });
     }
 
-    void forEachState(BiConsumer<DexMethod, InterfaceReservationState> action) {
+    void forEachState(BiConsumer<DexEncodedMethod, InterfaceReservationState> action) {
       forAnyState(
           (s, i) -> {
             action.accept(s, i);
@@ -329,9 +324,10 @@
           });
     }
 
-    <T> T forAnyState(BiFunction<DexMethod, InterfaceReservationState, T> callback) {
+    <T> T forAnyState(BiFunction<DexEncodedMethod, InterfaceReservationState, T> callback) {
       T returnValue;
-      for (Map.Entry<DexMethod, Set<InterfaceReservationState>> entry : methodStates.entrySet()) {
+      for (Map.Entry<DexEncodedMethod, Set<InterfaceReservationState>> entry :
+          methodStates.entrySet()) {
         for (InterfaceReservationState state : entry.getValue()) {
           returnValue = callback.apply(entry.getKey(), state);
           if (returnValue != null) {
@@ -342,7 +338,7 @@
       return null;
     }
 
-    boolean containsReservation(DexMethod method, DexType reservationType) {
+    boolean containsReservation(DexEncodedMethod method, DexType reservationType) {
       Set<InterfaceReservationState> states = methodStates.get(method);
       if (states != null) {
         for (InterfaceReservationState state : states) {
@@ -363,12 +359,14 @@
 
   private final AppView<AppInfoWithLiveness> appView;
   private final Equivalence<DexMethod> equivalence;
+  private final Equivalence<DexEncodedMethod> definitionEquivalence;
   private final MethodNameMinifier.State minifierState;
 
   private final Map<DexCallSite, DexString> callSiteRenamings = new IdentityHashMap<>();
 
   /** A map from DexMethods to all the states linked to interfaces they appear in. */
-  private final Map<Wrapper<DexMethod>, InterfaceMethodGroupState> globalStateMap = new HashMap<>();
+  private final Map<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> globalStateMap =
+      new HashMap<>();
 
   /** A map for caching all interface states. */
   private final Map<DexType, InterfaceReservationState> interfaceStateMap = new HashMap<>();
@@ -380,9 +378,21 @@
         appView.options().getProguardConfiguration().isOverloadAggressively()
             ? MethodSignatureEquivalence.get()
             : MethodJavaSignatureEquivalence.get();
+    this.definitionEquivalence =
+        new Equivalence<DexEncodedMethod>() {
+          @Override
+          protected boolean doEquivalent(DexEncodedMethod method, DexEncodedMethod other) {
+            return equivalence.equivalent(method.getReference(), other.getReference());
+          }
+
+          @Override
+          protected int doHash(DexEncodedMethod method) {
+            return equivalence.hash(method.getReference());
+          }
+        };
   }
 
-  private Comparator<Wrapper<DexMethod>> getDefaultInterfaceMethodOrdering() {
+  private Comparator<Wrapper<DexEncodedMethod>> getDefaultInterfaceMethodOrdering() {
     return Comparator.comparing(globalStateMap::get);
   }
 
@@ -419,10 +429,10 @@
       InterfaceReservationState inheritanceState = interfaceStateMap.get(iface.type);
       assert inheritanceState != null;
       for (DexEncodedMethod method : iface.methods()) {
-        Wrapper<DexMethod> key = equivalence.wrap(method.method);
+        Wrapper<DexEncodedMethod> key = definitionEquivalence.wrap(method);
         globalStateMap
             .computeIfAbsent(key, k -> new InterfaceMethodGroupState())
-            .addState(method.method, inheritanceState);
+            .addState(method, inheritanceState);
       }
     }
     timing.end();
@@ -439,11 +449,11 @@
     // Note that if the input does not use multi-interface lambdas unificationParent will remain
     // empty.
     timing.begin("Union-find");
-    DisjointSets<Wrapper<DexMethod>> unification = new DisjointSets<>();
+    DisjointSets<Wrapper<DexEncodedMethod>> unification = new DisjointSets<>();
 
     liveCallSites.forEach(
         callSite -> {
-          Set<Wrapper<DexMethod>> callSiteMethods = new HashSet<>();
+          Set<Wrapper<DexEncodedMethod>> callSiteMethods = new HashSet<>();
           // Don't report errors, as the set of call sites is a conservative estimate, and can
           // refer to interfaces which has been removed.
           Set<DexEncodedMethod> implementedMethods =
@@ -452,7 +462,7 @@
             return;
           }
           for (DexEncodedMethod method : implementedMethods) {
-            Wrapper<DexMethod> wrapped = equivalence.wrap(method.method);
+            Wrapper<DexEncodedMethod> wrapped = definitionEquivalence.wrap(method);
             InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
             assert groupState != null : wrapped;
             groupState.addCallSite(callSite);
@@ -474,16 +484,15 @@
               assert iface.isInterface();
               for (DexEncodedMethod implementedMethod : implementedMethods) {
                 for (DexEncodedMethod virtualMethod : iface.virtualMethods()) {
-                  boolean differentName =
-                      !implementedMethod.method.name.equals(virtualMethod.method.name);
+                  boolean differentName = implementedMethod.getName() != virtualMethod.getName();
                   if (differentName
                       && MethodJavaSignatureEquivalence.getEquivalenceIgnoreName()
                           .equivalent(implementedMethod.method, virtualMethod.method)) {
                     InterfaceMethodGroupState interfaceMethodGroupState =
                         globalStateMap.computeIfAbsent(
-                            equivalence.wrap(implementedMethod.method),
+                            definitionEquivalence.wrap(implementedMethod),
                             k -> new InterfaceMethodGroupState());
-                    interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod.method);
+                    interfaceMethodGroupState.callSiteCollidingMethods.add(virtualMethod);
                   }
                 }
               }
@@ -491,9 +500,9 @@
           }
           if (callSiteMethods.size() > 1) {
             // Implemented interfaces have different protos. Unify them.
-            Wrapper<DexMethod> mainKey = callSiteMethods.iterator().next();
-            Wrapper<DexMethod> representative = unification.findOrMakeSet(mainKey);
-            for (Wrapper<DexMethod> key : callSiteMethods) {
+            Wrapper<DexEncodedMethod> mainKey = callSiteMethods.iterator().next();
+            Wrapper<DexEncodedMethod> representative = unification.findOrMakeSet(mainKey);
+            for (Wrapper<DexEncodedMethod> key : callSiteMethods) {
               unification.unionWithMakeSet(representative, key);
             }
           }
@@ -504,14 +513,15 @@
     // We now have roots for all unions. Add all of the states for the groups to the method state
     // for the unions to allow consistent naming across different protos.
     timing.begin("States for union");
-    Map<Wrapper<DexMethod>, Set<Wrapper<DexMethod>>> unions = unification.collectSets();
+    Map<Wrapper<DexEncodedMethod>, Set<Wrapper<DexEncodedMethod>>> unions =
+        unification.collectSets();
 
-    for (Wrapper<DexMethod> wrapped : unions.keySet()) {
+    for (Wrapper<DexEncodedMethod> wrapped : unions.keySet()) {
       InterfaceMethodGroupState groupState = globalStateMap.get(wrapped);
       assert groupState != null;
 
-      for (Wrapper<DexMethod> groupedMethod : unions.get(wrapped)) {
-        DexMethod method = groupedMethod.get();
+      for (Wrapper<DexEncodedMethod> groupedMethod : unions.get(wrapped)) {
+        DexEncodedMethod method = groupedMethod.get();
         assert method != null;
         groupState.appendMethodGroupState(globalStateMap.get(groupedMethod));
       }
@@ -522,7 +532,7 @@
     // Filter out the groups that is included both in the unification and in the map. We sort the
     // methods by the number of dependent states, so that we use short names for method that are
     // referenced in many places.
-    List<Wrapper<DexMethod>> interfaceMethodGroups =
+    List<Wrapper<DexEncodedMethod>> interfaceMethodGroups =
         globalStateMap.keySet().stream()
             .filter(unification::isRepresentativeOrNotPresent)
             .sorted(
@@ -540,8 +550,8 @@
     timing.begin("Reserve in groups");
     // It is important that this entire phase is run before given new names, to ensure all
     // reservations are propagated to all naming states.
-    List<Wrapper<DexMethod>> nonReservedMethodGroups = new ArrayList<>();
-    for (Wrapper<DexMethod> interfaceMethodGroup : interfaceMethodGroups) {
+    List<Wrapper<DexEncodedMethod>> nonReservedMethodGroups = new ArrayList<>();
+    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : interfaceMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       assert groupState != null;
       DexString reservedName = groupState.getReservedName();
@@ -559,7 +569,7 @@
     timing.end();
 
     timing.begin("Rename in groups");
-    for (Wrapper<DexMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       assert groupState != null;
       assert groupState.getReservedName() == null;
@@ -567,11 +577,11 @@
       assert newName != null;
       Set<String> loggingFilter = appView.options().extensiveInterfaceMethodMinifierLoggingFilter;
       if (!loggingFilter.isEmpty()) {
-        Set<DexMethod> sourceMethods = groupState.methodStates.keySet();
+        Set<DexEncodedMethod> sourceMethods = groupState.methodStates.keySet();
         if (sourceMethods.stream()
-            .map(DexMethod::toSourceString)
+            .map(DexEncodedMethod::toSourceString)
             .anyMatch(loggingFilter::contains)) {
-          print(interfaceMethodGroup.get(), sourceMethods, System.out);
+          print(interfaceMethodGroup.get().getReference(), sourceMethods, System.out);
         }
       }
       for (DexCallSite callSite : groupState.callSites) {
@@ -582,20 +592,20 @@
 
     // After all naming is completed for callsites, we must ensure to rename all interface methods
     // that can collide with the callsite method name.
-    for (Wrapper<DexMethod> interfaceMethodGroup : nonReservedMethodGroups) {
+    for (Wrapper<DexEncodedMethod> interfaceMethodGroup : nonReservedMethodGroups) {
       InterfaceMethodGroupState groupState = globalStateMap.get(interfaceMethodGroup);
       if (groupState.callSiteCollidingMethods.isEmpty()) {
         continue;
       }
-      DexMethod key = interfaceMethodGroup.get();
-      MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.holder);
+      DexEncodedMethod key = interfaceMethodGroup.get();
+      MethodNamingState<?> keyNamingState = minifierState.getNamingState(key.getHolderType());
       DexString existingRenaming = keyNamingState.newOrReservedNameFor(key);
       assert existingRenaming != null;
-      for (DexMethod collidingMethod : groupState.callSiteCollidingMethods) {
+      for (DexEncodedMethod collidingMethod : groupState.callSiteCollidingMethods) {
         DexString newNameInGroup = newNameInGroup(collidingMethod, keyNamingState, groupState);
         minifierState.putRenaming(collidingMethod, newNameInGroup);
         MethodNamingState<?> methodNamingState =
-            minifierState.getNamingState(collidingMethod.holder);
+            minifierState.getNamingState(collidingMethod.getReference().holder);
         methodNamingState.addRenaming(newNameInGroup, collidingMethod);
         keyNamingState.addRenaming(newNameInGroup, collidingMethod);
       }
@@ -605,11 +615,11 @@
     timing.end(); // end compute timing
   }
 
-  private DexString assignNewName(DexMethod method, InterfaceMethodGroupState groupState) {
+  private DexString assignNewName(DexEncodedMethod method, InterfaceMethodGroupState groupState) {
     assert groupState.getReservedName() == null;
     assert groupState.methodStates.containsKey(method);
-    assert groupState.containsReservation(method, method.holder);
-    MethodNamingState<?> namingState = minifierState.getNamingState(method.holder);
+    assert groupState.containsReservation(method, method.getHolderType());
+    MethodNamingState<?> namingState = minifierState.getNamingState(method.getHolderType());
     // Check if the name is available in all states.
     DexString newName =
         namingState.newOrReservedNameFor(
@@ -619,7 +629,9 @@
   }
 
   private DexString newNameInGroup(
-      DexMethod method, MethodNamingState<?> namingState, InterfaceMethodGroupState groupState) {
+      DexEncodedMethod method,
+      MethodNamingState<?> namingState,
+      InterfaceMethodGroupState groupState) {
     // Check if the name is available in all states.
     return namingState.nextName(method, (candidate, ignore) -> groupState.isAvailable(candidate));
   }
@@ -655,11 +667,11 @@
             });
   }
 
-  private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
-    Set<Wrapper<DexMethod>> unifiedMethods = new HashSet<>(groups);
+  private boolean verifyAllCallSitesAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
+    Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
     Set<DexCallSite> unifiedSeen = new HashSet<>();
     Set<DexCallSite> seen = new HashSet<>();
-    for (Map.Entry<Wrapper<DexMethod>, InterfaceMethodGroupState> state :
+    for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
         globalStateMap.entrySet()) {
       for (DexCallSite callSite : state.getValue().callSites) {
         seen.add(callSite);
@@ -674,13 +686,13 @@
     return true;
   }
 
-  private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexMethod>> groups) {
-    Set<Wrapper<DexMethod>> unifiedMethods = new HashSet<>(groups);
-    Set<DexMethod> unifiedSeen = new HashSet<>();
-    Set<DexMethod> seen = new HashSet<>();
-    for (Map.Entry<Wrapper<DexMethod>, InterfaceMethodGroupState> state :
+  private boolean verifyAllMethodsAreRepresentedIn(List<Wrapper<DexEncodedMethod>> groups) {
+    Set<Wrapper<DexEncodedMethod>> unifiedMethods = new HashSet<>(groups);
+    Set<DexEncodedMethod> unifiedSeen = Sets.newIdentityHashSet();
+    Set<DexEncodedMethod> seen = Sets.newIdentityHashSet();
+    for (Map.Entry<Wrapper<DexEncodedMethod>, InterfaceMethodGroupState> state :
         globalStateMap.entrySet()) {
-      for (DexMethod method : state.getValue().methodStates.keySet()) {
+      for (DexEncodedMethod method : state.getValue().methodStates.keySet()) {
         seen.add(method);
         if (unifiedMethods.contains(state.getKey())) {
           boolean added = unifiedSeen.add(method);
@@ -693,12 +705,12 @@
     return true;
   }
 
-  private void print(DexMethod method, Set<DexMethod> sourceMethods, PrintStream out) {
+  private void print(DexMethod method, Set<DexEncodedMethod> sourceMethods, PrintStream out) {
     out.println("-----------------------------------------------------------------------");
     out.println("assignNameToInterfaceMethod(`" + method.toSourceString() + "`)");
     out.println("-----------------------------------------------------------------------");
     out.println("Source methods:");
-    for (DexMethod sourceMethod : sourceMethods) {
+    for (DexEncodedMethod sourceMethod : sourceMethods) {
       out.println("  " + sourceMethod.toSourceString());
     }
     out.println("States:");
diff --git a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
index cddbea8..58a4dc6 100644
--- a/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
+++ b/src/main/java/com/android/tools/r8/naming/MemberNamingStrategy.java
@@ -15,7 +15,7 @@
 public interface MemberNamingStrategy {
 
   DexString next(
-      DexMethod method,
+      DexEncodedMethod method,
       InternalNamingState internalState,
       BiPredicate<DexString, DexMethod> isAvailable);
 
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
index cdb9166..7b3e763 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNameMinifier.java
@@ -89,8 +89,8 @@
   // from the method name minifier to the interface method name minifier.
   class State {
 
-    void putRenaming(DexMethod key, DexString value) {
-      renaming.put(key, value);
+    void putRenaming(DexEncodedMethod key, DexString value) {
+      renaming.put(key.getReference(), value);
     }
 
     MethodReservationState<?> getReservationState(DexType type) {
@@ -221,21 +221,21 @@
   }
 
   private void assignNameToMethod(
-      DexClass holder, DexEncodedMethod encodedMethod, MethodNamingState<?> state) {
-    if (encodedMethod.accessFlags.isConstructor()) {
+      DexClass holder, DexEncodedMethod method, MethodNamingState<?> state) {
+    if (method.isInitializer()) {
       return;
     }
     // The strategy may have an explicit naming for this member which we query first. It may be that
     // the strategy will return the identity name, for which we have to look into a previous
     // renaming tracked by the state.
-    DexString newName = strategy.getReservedName(encodedMethod, holder);
-    if (newName == null || newName == encodedMethod.method.name) {
-      newName = state.newOrReservedNameFor(encodedMethod.method);
+    DexString newName = strategy.getReservedName(method, holder);
+    if (newName == null || newName == method.getName()) {
+      newName = state.newOrReservedNameFor(method);
     }
-    if (encodedMethod.method.name != newName) {
-      renaming.put(encodedMethod.method, newName);
+    if (method.getName() != newName) {
+      renaming.put(method.getReference(), newName);
     }
-    state.addRenaming(newName, encodedMethod.method);
+    state.addRenaming(newName, method);
   }
 
   private void reserveNamesInClasses() {
@@ -280,7 +280,7 @@
       for (DexEncodedMethod method : shuffleMethods(holder.methods(), appView.options())) {
         DexString reservedName = strategy.getReservedName(method, holder);
         if (reservedName != null) {
-          state.reserveName(reservedName, method.method);
+          state.reserveName(reservedName, method);
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
index 85c2f0f..5aa368b 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodNamingState.java
@@ -3,6 +3,7 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MethodNamingState.InternalNewNameState;
@@ -44,37 +45,38 @@
         this, this.keyTransform, this.namingStrategy, frontierReservationState);
   }
 
-  DexString newOrReservedNameFor(DexMethod method) {
+  DexString newOrReservedNameFor(DexEncodedMethod method) {
     return newOrReservedNameFor(method, this::isAvailable);
   }
 
-  DexString newOrReservedNameFor(DexMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
-    DexString newName = getAssignedName(method);
+  DexString newOrReservedNameFor(
+      DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+    DexString newName = getAssignedName(method.getReference());
     if (newName != null) {
       return newName;
     }
-    Set<DexString> reservedNamesFor = reservationState.getReservedNamesFor(method);
+    Set<DexString> reservedNamesFor = reservationState.getReservedNamesFor(method.getReference());
     // Reservations with applymapping can cause multiple reserved names added to the frontier. In
     // that case, the strategy will return the correct one.
     if (reservedNamesFor != null && reservedNamesFor.size() == 1) {
       DexString candidate = reservedNamesFor.iterator().next();
-      if (isAvailable(candidate, method)) {
+      if (isAvailable(candidate, method.getReference())) {
         return candidate;
       }
     }
     return nextName(method, isAvailable);
   }
 
-  DexString nextName(DexMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
-    InternalNewNameState internalState = getOrCreateInternalState(method);
+  DexString nextName(DexEncodedMethod method, BiPredicate<DexString, DexMethod> isAvailable) {
+    InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
     DexString newName = namingStrategy.next(method, internalState, isAvailable);
     assert newName != null;
     return newName;
   }
 
-  void addRenaming(DexString newName, DexMethod method) {
-    InternalNewNameState internalState = getOrCreateInternalState(method);
-    internalState.addRenaming(newName, method);
+  void addRenaming(DexString newName, DexEncodedMethod method) {
+    InternalNewNameState internalState = getOrCreateInternalState(method.getReference());
+    internalState.addRenaming(newName, method.getReference());
   }
 
   boolean isAvailable(DexString candidate, DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
index 2ce35f9..b963b3f 100644
--- a/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
+++ b/src/main/java/com/android/tools/r8/naming/MethodReservationState.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.naming;
 
+import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.naming.MethodReservationState.InternalReservationState;
@@ -36,9 +37,9 @@
     return new MethodReservationState<>(this, this.keyTransform);
   }
 
-  void reserveName(DexString reservedName, DexMethod method) {
+  void reserveName(DexString reservedName, DexEncodedMethod method) {
     try {
-      getOrCreateInternalState(method).reserveName(method, reservedName);
+      getOrCreateInternalState(method.getReference()).reserveName(method, reservedName);
     } catch (AssertionError err) {
       throw new RuntimeException(
           String.format(
@@ -91,13 +92,14 @@
       return originalToReservedNames.get(MethodSignatureEquivalence.get().wrap(method));
     }
 
-    void reserveName(DexMethod method, DexString name) {
+    void reserveName(DexEncodedMethod method, DexString name) {
       if (reservedNames == null) {
         assert originalToReservedNames == null;
         originalToReservedNames = new HashMap<>();
         reservedNames = new HashSet<>();
       }
-      final Wrapper<DexMethod> wrapped = MethodSignatureEquivalence.get().wrap(method);
+      final Wrapper<DexMethod> wrapped =
+          MethodSignatureEquivalence.get().wrap(method.getReference());
       originalToReservedNames.computeIfAbsent(wrapped, ignore -> new HashSet<>()).add(name);
       reservedNames.add(name);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/Minifier.java b/src/main/java/com/android/tools/r8/naming/Minifier.java
index d0f0381..182de8d 100644
--- a/src/main/java/com/android/tools/r8/naming/Minifier.java
+++ b/src/main/java/com/android/tools/r8/naming/Minifier.java
@@ -225,16 +225,15 @@
 
     @Override
     public DexString next(
-        DexMethod method,
+        DexEncodedMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
-      assert checkAllowMemberRenaming(method.holder);
-      DexEncodedMethod encodedMethod = appView.definitionFor(method);
-      boolean isDirectOrStatic = encodedMethod.isDirectMethod() || encodedMethod.isStatic();
+      assert checkAllowMemberRenaming(method.getHolderType());
+      boolean isDirectOrStatic = method.isDirectMethod() || method.isStatic();
       DexString candidate;
       do {
         candidate = getNextName(internalState, isDirectOrStatic);
-      } while (!isAvailable.test(candidate, method));
+      } while (!isAvailable.test(candidate, method.getReference()));
       return candidate;
     }
 
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
index e720b30..515d21f 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapMinifier.java
@@ -188,11 +188,11 @@
       Set<DexReference> notMappedReferences,
       SubtypingInfo subtypingInfo) {
     ClassNamingForMapApplier classNaming = seedMapper.getClassNaming(type);
-    DexClass dexClass = appView.definitionFor(type);
+    DexClass clazz = appView.definitionFor(type);
 
     // Keep track of classes that needs to get renamed.
-    if (dexClass != null && (classNaming != null || dexClass.isProgramClass())) {
-      mappedClasses.add(dexClass);
+    if (clazz != null && (classNaming != null || clazz.isProgramClass())) {
+      mappedClasses.add(clazz);
     }
 
     Map<DexReference, MemberNaming> nonPrivateMembers = new IdentityHashMap<>();
@@ -205,7 +205,7 @@
           memberNaming -> addMemberNamings(type, memberNaming, nonPrivateMembers, false));
     } else {
       // We have to ensure we do not rename to an existing member, that cannot be renamed.
-      if (dexClass == null || !appView.options().isMinifying()) {
+      if (clazz == null || !appView.options().isMinifying()) {
         notMappedReferences.add(type);
       } else if (appView.options().isMinifying()
           && appView.rootSet().mayNotBeMinified(type, appView)) {
@@ -222,10 +222,10 @@
           if (!memberNames.containsKey(parentReferenceOnCurrentType)) {
             addMemberNaming(
                 parentReferenceOnCurrentType, parentMembers.get(key), additionalMethodNamings);
-          } else {
-            DexEncodedMethod encodedMethod = appView.definitionFor(parentReferenceOnCurrentType);
-            assert encodedMethod == null
-                || encodedMethod.accessFlags.isStatic()
+          } else if (clazz != null) {
+            DexEncodedMethod method = clazz.lookupMethod(parentReferenceOnCurrentType);
+            assert method == null
+                || method.isStatic()
                 || memberNames
                     .get(parentReferenceOnCurrentType)
                     .getRenamedName()
@@ -245,12 +245,12 @@
       }
     }
 
-    if (dexClass != null) {
+    if (clazz != null) {
       // If a class is marked as abstract it is allowed to not implement methods from interfaces
       // thus the map will not contain a mapping. Also, if an interface is defined in the library
       // and the class is in the program, we have to build up the correct names to reserve them.
-      if (dexClass.isProgramClass() || dexClass.isAbstract()) {
-        addNonPrivateInterfaceMappings(type, nonPrivateMembers, dexClass.interfaces.values);
+      if (clazz.isProgramClass() || clazz.isAbstract()) {
+        addNonPrivateInterfaceMappings(type, nonPrivateMembers, clazz.interfaces.values);
       }
     }
 
@@ -293,8 +293,9 @@
       DexMethod originalMethod = ((MethodSignature) signature).toDexMethod(factory, type);
       addMemberNaming(
           originalMethod, memberNaming, addToAdditionalMaps ? additionalMethodNamings : null);
-      DexEncodedMethod encodedMethod = appView.definitionFor(originalMethod);
-      if (encodedMethod == null || !encodedMethod.accessFlags.isPrivate()) {
+      DexClass holder = appView.definitionForHolder(originalMethod);
+      DexEncodedMethod definition = originalMethod.lookupOnClass(holder);
+      if (definition == null || !definition.accessFlags.isPrivate()) {
         nonPrivateMembers.put(originalMethod, memberNaming);
       }
     } else {
@@ -466,12 +467,12 @@
 
     @Override
     public DexString next(
-        DexMethod reference,
+        DexEncodedMethod method,
         InternalNamingState internalState,
         BiPredicate<DexString, DexMethod> isAvailable) {
+      DexMethod reference = method.getReference();
       DexClass holder = appView.definitionForHolder(reference);
       assert holder != null;
-      DexEncodedMethod method = holder.lookupMethod(reference);
       DexString reservedName = getReservedName(method, reference.name, holder);
       DexString nextName;
       if (reservedName != null) {
@@ -482,7 +483,7 @@
       } else {
         assert !mappedNames.containsKey(reference);
         assert appView.rootSet().mayBeMinified(reference, appView);
-        nextName = super.next(reference, internalState, isAvailable);
+        nextName = super.next(method, internalState, isAvailable);
       }
       assert nextName == reference.name || !method.isInitializer();
       assert nextName == reference.name || !holder.isAnnotation();
diff --git a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
index eef8234..1f66108 100644
--- a/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/signature/GenericSignatureRewriter.java
@@ -13,14 +13,13 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.naming.NamingLens;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringDiagnostic;
 import com.android.tools.r8.utils.ThreadUtils;
-import com.google.common.collect.Maps;
 import java.lang.reflect.GenericSignatureFormatError;
-import java.util.Map;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BiConsumer;
@@ -32,23 +31,24 @@
 public class GenericSignatureRewriter {
 
   private final AppView<?> appView;
-  private final Map<DexType, DexString> renaming;
+  private final NamingLens namingLens;
   private final InternalOptions options;
   private final Reporter reporter;
 
-  public GenericSignatureRewriter(AppView<?> appView) {
-    this(appView, Maps.newIdentityHashMap());
-  }
-
-  public GenericSignatureRewriter(AppView<?> appView, Map<DexType, DexString> renaming) {
+  public GenericSignatureRewriter(AppView<?> appView, NamingLens namingLens) {
     this.appView = appView;
-    this.renaming = renaming;
+    this.namingLens = namingLens;
     this.options = appView.options();
     this.reporter = options.reporter;
   }
 
   public void run(Iterable<? extends DexProgramClass> classes, ExecutorService executorService)
       throws ExecutionException {
+    // Rewrite signature annotations for applications that are minified or if we have liveness
+    // information, since we could have pruned types.
+    if (namingLens.isIdentityLens() && !appView.appInfo().hasLiveness()) {
+      return;
+    }
     // Classes may not be the same as appInfo().classes() if applymapping is used on classpath
     // arguments. If that is the case, the ProguardMapMinifier will pass in all classes that is
     // either ProgramClass or has a mapping. This is then transitively called inside the
@@ -204,7 +204,7 @@
       if (appView.appInfo().hasLiveness() && appView.withLiveness().appInfo().wasPruned(type)) {
         type = appView.dexItemFactory().objectType;
       }
-      DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
+      DexString renamedDescriptor = namingLens.lookupDescriptor(type);
       if (parserPosition == ParserPosition.CLASS_SUPER_OR_INTERFACE_ANNOTATION
           && currentClassContext != null) {
         // We may have merged the type down to the current class type.
@@ -251,16 +251,14 @@
                       getClassBinaryNameFromDescriptor(enclosingDescriptor)
                           + DescriptorUtils.INNER_CLASS_SEPARATOR
                           + name));
-      String enclosingRenamedBinaryName =
-          getClassBinaryNameFromDescriptor(
-              renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
       type = appView.graphLense().lookupType(type);
-      DexString renamedDescriptor = renaming.get(type);
-      if (renamedDescriptor != null) {
+      String renamedDescriptor = namingLens.lookupDescriptor(type).toString();
+      if (!renamedDescriptor.equals(type.toDescriptorString())) {
         // TODO(b/147504070): If this is a merged class equal to the class context, do not add.
         // Pick the renamed inner class from the fully renamed binary name.
-        String fullRenamedBinaryName =
-            getClassBinaryNameFromDescriptor(renamedDescriptor.toString());
+        String fullRenamedBinaryName = getClassBinaryNameFromDescriptor(renamedDescriptor);
+        String enclosingRenamedBinaryName =
+            getClassBinaryNameFromDescriptor(namingLens.lookupDescriptor(enclosingType).toString());
         int innerClassPos = enclosingRenamedBinaryName.length() + 1;
         if (innerClassPos < fullRenamedBinaryName.length()) {
           renamedSignature.append(fullRenamedBinaryName.substring(innerClassPos));
diff --git a/src/main/java/com/android/tools/r8/relocator/Relocator.java b/src/main/java/com/android/tools/r8/relocator/Relocator.java
index aaf83b1..1e8420e 100644
--- a/src/main/java/com/android/tools/r8/relocator/Relocator.java
+++ b/src/main/java/com/android/tools/r8/relocator/Relocator.java
@@ -87,8 +87,7 @@
       SimplePackagesRewritingMapper packageRemapper = new SimplePackagesRewritingMapper(appView);
       NamingLens namingLens = packageRemapper.compute(command.getMapping());
 
-      new GenericSignatureRewriter(appView, packageRemapper.getTypeMappings())
-          .run(appInfo.classes(), executor);
+      new GenericSignatureRewriter(appView, namingLens).run(appInfo.classes(), executor);
 
       new CfApplicationWriter(
               app,
diff --git a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
index f5a3bbf..5b97077 100644
--- a/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
+++ b/src/main/java/com/android/tools/r8/shaking/AnnotationMatchResult.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.DexAnnotation;
+import java.util.List;
 
 public abstract class AnnotationMatchResult {
 
@@ -30,14 +31,14 @@
 
   static class ConcreteAnnotationMatchResult extends AnnotationMatchResult {
 
-    private final DexAnnotation matchedAnnotation;
+    private final List<DexAnnotation> matchedAnnotations;
 
-    public ConcreteAnnotationMatchResult(DexAnnotation matchedAnnotation) {
-      this.matchedAnnotation = matchedAnnotation;
+    public ConcreteAnnotationMatchResult(List<DexAnnotation> matchedAnnotations) {
+      this.matchedAnnotations = matchedAnnotations;
     }
 
-    public DexAnnotation getMatchedAnnotation() {
-      return matchedAnnotation;
+    public List<DexAnnotation> getMatchedAnnotations() {
+      return matchedAnnotations;
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
index 87bfd9a..f617c31 100644
--- a/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
+++ b/src/main/java/com/android/tools/r8/shaking/AppInfoWithLiveness.java
@@ -12,6 +12,7 @@
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexClassAndMethod;
 import com.android.tools.r8.graph.DexClasspathClass;
+import com.android.tools.r8.graph.DexDefinition;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -1338,22 +1339,17 @@
     return isInstantiatedDirectly(clazz) || isPinned(clazz.type) || isInstantiatedInterface(clazz);
   }
 
-  public boolean isPinnedNotProgramOrLibraryOverride(DexReference reference) {
-    if (isPinned(reference)) {
+  public boolean isPinnedNotProgramOrLibraryOverride(DexDefinition definition) {
+    if (isPinned(definition.toReference())) {
       return true;
     }
-    if (reference.isDexMethod()) {
-      DexEncodedMethod method = definitionFor(reference.asDexMethod());
-      return method == null
-          || !method.isProgramMethod(this)
-          || method.isLibraryMethodOverride().isPossiblyTrue();
-    } else {
-      assert reference.isDexType();
-      DexClass clazz = definitionFor(reference.asDexType());
-      return clazz == null
-          || clazz.isNotProgramClass()
-          || isInstantiatedInterface(clazz.asProgramClass());
+    if (definition.isDexEncodedMethod()) {
+      DexEncodedMethod method = definition.asDexEncodedMethod();
+      return !method.isProgramMethod(this) || method.isLibraryMethodOverride().isPossiblyTrue();
     }
+    assert definition.isDexClass();
+    DexClass clazz = definition.asDexClass();
+    return clazz.isNotProgramClass() || isInstantiatedInterface(clazz.asProgramClass());
   }
 
   public SubtypingInfo computeSubtypingInfo() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
index 4e69cd2..ccda8e3 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassInlineRule.java
@@ -39,13 +39,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules,
@@ -59,13 +59,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
@@ -74,13 +74,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
index 43bfe4b..ee57043 100644
--- a/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ClassMergingRule.java
@@ -34,9 +34,21 @@
 
     @Override
     public ClassMergingRule build() {
-      return new ClassMergingRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules, type);
+      return new ClassMergingRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
     }
   }
 
@@ -46,20 +58,31 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       Type type) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
     this.type = type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
index 8288bde..540db63 100644
--- a/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/ConsequentRootSetBuilder.java
@@ -24,7 +24,7 @@
     if (enqueuer.getMode().isInitialTreeShaking()
         && annotationMatchResult.isConcreteAnnotationMatchResult()) {
       enqueuer.retainAnnotationForFinalTreeShaking(
-          annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotation());
+          annotationMatchResult.asConcreteAnnotationMatchResult().getMatchedAnnotations());
     }
   }
 }
diff --git a/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
index 6d6f424..bc33778 100644
--- a/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ConstantArgumentRule.java
@@ -27,13 +27,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules);
@@ -44,13 +44,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index a0a1924..76c9e4c 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2496,7 +2496,7 @@
             (type, subTypeConsumer, lambdaConsumer) ->
                 objectAllocationInfoCollection.forEachInstantiatedSubType(
                     type, subTypeConsumer, lambdaConsumer, appInfo),
-            reference -> keepInfo.isPinned(reference, appInfo))
+            definition -> keepInfo.isPinned(definition.toReference(), appInfo))
         .forEach(
             target ->
                 markVirtualDispatchTargetAsLive(
@@ -3376,10 +3376,10 @@
     desugaredLambdaImplementationMethods.clear();
   }
 
-  void retainAnnotationForFinalTreeShaking(DexAnnotation annotation) {
+  void retainAnnotationForFinalTreeShaking(List<DexAnnotation> annotations) {
     assert mode.isInitialTreeShaking();
     if (annotationRemoverBuilder != null) {
-      annotationRemoverBuilder.retainAnnotation(annotation);
+      annotations.forEach(annotationRemoverBuilder::retainAnnotation);
     }
   }
 
@@ -4161,12 +4161,6 @@
       this.enqueuer = enqueuer;
     }
 
-    @Deprecated
-    @Override
-    public DexEncodedMethod definitionFor(DexMethod method) {
-      return enqueuer.definitionFor(method);
-    }
-
     @Override
     public DexClass definitionFor(DexType type) {
       return enqueuer.definitionFor(type);
diff --git a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
index aa37771..527565f 100644
--- a/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
+++ b/src/main/java/com/android/tools/r8/shaking/IfRuleClassPartEquivalence.java
@@ -12,7 +12,7 @@
 
   @Override
   protected boolean doEquivalent(ProguardIfRule p1, ProguardIfRule p2) {
-    if (!Objects.equals(p1.getClassAnnotation(), p2.getClassAnnotation())) {
+    if (!p1.getClassAnnotations().equals(p2.getClassAnnotations())) {
       return false;
     }
     if (!p1.getClassAccessFlags().equals(p2.getClassAccessFlags())
@@ -26,7 +26,7 @@
     if (p1.getInheritanceIsExtends() != p2.getInheritanceIsExtends()) {
       return false;
     }
-    if (!Objects.equals(p1.getInheritanceAnnotation(), p2.getInheritanceAnnotation())) {
+    if (!p1.getInheritanceAnnotations().equals(p2.getInheritanceAnnotations())) {
       return false;
     }
     if (!Objects.equals(p1.getInheritanceClassName(), p2.getInheritanceClassName())) {
@@ -40,17 +40,13 @@
 
   @Override
   protected int doHash(ProguardIfRule rule) {
-    int result = (rule.getClassAnnotation() != null ? rule.getClassAnnotation().hashCode() : 0);
+    int result = rule.getClassAnnotations().hashCode();
     result = 3 * result + rule.getClassAccessFlags().hashCode();
     result = 3 * result + rule.getNegatedClassAccessFlags().hashCode();
     result = 3 * result + (rule.getClassTypeNegated() ? 1 : 0);
     result = 3 * result + (rule.getClassType() != null ? rule.getClassType().hashCode() : 0);
     result = 3 * result + rule.getClassNames().hashCode();
-    result =
-        3 * result
-            + (rule.getInheritanceAnnotation() != null
-                ? rule.getInheritanceAnnotation().hashCode()
-                : 0);
+    result = 3 * result + rule.getInheritanceAnnotations().hashCode();
     result =
         3 * result
             + (rule.getInheritanceClassName() != null
diff --git a/src/main/java/com/android/tools/r8/shaking/InlineRule.java b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
index ca746a1..41b6b33 100644
--- a/src/main/java/com/android/tools/r8/shaking/InlineRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/InlineRule.java
@@ -41,9 +41,21 @@
 
     @Override
     public InlineRule build() {
-      return new InlineRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules, type);
+      return new InlineRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
     }
   }
 
@@ -53,20 +65,31 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       Type type) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
     this.type = type;
   }
 
@@ -83,13 +106,13 @@
     ProguardCheckDiscardRule.Builder builder = ProguardCheckDiscardRule.builder();
     builder.setOrigin(checkDiscardOrigin);
     builder.setSource(null);
-    builder.setClassAnnotation(getClassAnnotation());
+    builder.addClassAnnotations(getClassAnnotations());
     builder.setClassAccessFlags(getClassAccessFlags());
     builder.setNegatedClassAccessFlags(getNegatedClassAccessFlags());
     builder.setClassTypeNegated(getClassTypeNegated());
     builder.setClassType(getClassType());
     builder.setClassNames(getClassNames());
-    builder.setInheritanceAnnotation(getInheritanceAnnotation());
+    builder.addInheritanceAnnotations(getInheritanceAnnotations());
     builder.setInheritanceIsExtends(getInheritanceIsExtends());
     builder.setMemberRules(getMemberRules());
     return builder.build();
diff --git a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java b/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
index dde4631..c26832b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/MemberValuePropagationRule.java
@@ -35,9 +35,21 @@
 
     @Override
     public MemberValuePropagationRule build() {
-      return new MemberValuePropagationRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules, type);
+      return new MemberValuePropagationRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type);
     }
   }
 
@@ -47,20 +59,31 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       Type type) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
     this.type = type;
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
index 01b7a5e..654bc3e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeMayHaveSideEffectsRule.java
@@ -27,13 +27,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules);
@@ -44,13 +44,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
index 5f8e860..ed151aa 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeNoSideEffectRule.java
@@ -23,9 +23,20 @@
 
     @Override
     public ProguardAssumeNoSideEffectRule build() {
-      return new ProguardAssumeNoSideEffectRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardAssumeNoSideEffectRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
     }
   }
 
@@ -33,19 +44,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
index 8b8f4dd..97d901e 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardAssumeValuesRule.java
@@ -23,9 +23,20 @@
 
     @Override
     public ProguardAssumeValuesRule build() {
-      return new ProguardAssumeValuesRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardAssumeValuesRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
     }
   }
 
@@ -33,19 +44,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   /**
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
index 664cb25..82b81a4 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardCheckDiscardRule.java
@@ -23,9 +23,20 @@
 
     @Override
     public ProguardCheckDiscardRule build() {
-      return new ProguardCheckDiscardRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardCheckDiscardRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
     }
   }
 
@@ -33,19 +44,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
index 9ae4986..2bf270a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardClassSpecification.java
@@ -9,6 +9,7 @@
 import com.android.tools.r8.position.TextRange;
 import com.android.tools.r8.utils.StringUtils;
 import com.google.common.collect.ImmutableList;
+import java.util.Iterator;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.Objects;
@@ -22,13 +23,15 @@
     protected Position start;
     protected Position end;
     protected String source;
-    protected ProguardTypeMatcher classAnnotation;
+    private final ImmutableList.Builder<ProguardTypeMatcher> classAnnotations =
+        ImmutableList.builder();
     protected ProguardAccessFlags classAccessFlags = new ProguardAccessFlags();
     protected ProguardAccessFlags negatedClassAccessFlags = new ProguardAccessFlags();
     protected boolean classTypeNegated = false;
     protected ProguardClassType classType = ProguardClassType.UNSPECIFIED;
     protected ProguardClassNameList classNames;
-    protected ProguardTypeMatcher inheritanceAnnotation;
+    private final ImmutableList.Builder<ProguardTypeMatcher> inheritanceAnnotations =
+        ImmutableList.builder();
     protected ProguardTypeMatcher inheritanceClassName;
     protected boolean inheritanceIsExtends = false;
     protected List<ProguardMemberRule> memberRules = new LinkedList<>();
@@ -105,12 +108,13 @@
       this.inheritanceClassName = inheritanceClassName;
     }
 
-    public ProguardTypeMatcher getInheritanceAnnotation() {
-      return inheritanceAnnotation;
+    public void addInheritanceAnnotations(List<ProguardTypeMatcher> inheritanceAnnotations) {
+      assert inheritanceAnnotations != null;
+      this.inheritanceAnnotations.addAll(inheritanceAnnotations);
     }
 
-    public void setInheritanceAnnotation(ProguardTypeMatcher inheritanceAnnotation) {
-      this.inheritanceAnnotation = inheritanceAnnotation;
+    public List<ProguardTypeMatcher> buildInheritanceAnnotations() {
+      return inheritanceAnnotations.build();
     }
 
     public ProguardClassNameList getClassNames() {
@@ -155,12 +159,17 @@
       negatedClassAccessFlags = flags;
     }
 
-    public ProguardTypeMatcher getClassAnnotation() {
-      return classAnnotation;
+    public void addClassAnnotation(ProguardTypeMatcher classAnnotation) {
+      classAnnotations.add(classAnnotation);
     }
 
-    public void setClassAnnotation(ProguardTypeMatcher classAnnotation) {
-      this.classAnnotation = classAnnotation;
+    public void addClassAnnotations(List<ProguardTypeMatcher> classAnnotations) {
+      assert classAnnotations != null;
+      this.classAnnotations.addAll(classAnnotations);
+    }
+
+    public List<ProguardTypeMatcher> buildClassAnnotations() {
+      return classAnnotations.build();
     }
 
     protected void matchAllSpecification() {
@@ -172,13 +181,13 @@
   private final Origin origin;
   private final Position position;
   private final String source;
-  private final ProguardTypeMatcher classAnnotation;
+  private final List<ProguardTypeMatcher> classAnnotations;
   private final ProguardAccessFlags classAccessFlags;
   private final ProguardAccessFlags negatedClassAccessFlags;
   private final boolean classTypeNegated;
   private final ProguardClassType classType;
   private final ProguardClassNameList classNames;
-  private final ProguardTypeMatcher inheritanceAnnotation;
+  private final List<ProguardTypeMatcher> inheritanceAnnotations;
   private final ProguardTypeMatcher inheritanceClassName;
   private final boolean inheritanceIsExtends;
   private final List<ProguardMemberRule> memberRules;
@@ -187,13 +196,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
@@ -202,15 +211,15 @@
     assert source != null || origin != Origin.unknown();
     this.origin = origin;
     this.position = position;
-    this.source =source;
-    this.classAnnotation = classAnnotation;
+    this.source = source;
+    this.classAnnotations = classAnnotations;
     this.classAccessFlags = classAccessFlags;
     this.negatedClassAccessFlags = negatedClassAccessFlags;
     this.classTypeNegated = classTypeNegated;
     this.classType = classType;
     assert classType != null;
     this.classNames = classNames;
-    this.inheritanceAnnotation = inheritanceAnnotation;
+    this.inheritanceAnnotations = inheritanceAnnotations;
     this.inheritanceClassName = inheritanceClassName;
     this.inheritanceIsExtends = inheritanceIsExtends;
     this.memberRules = memberRules;
@@ -248,8 +257,8 @@
     return inheritanceClassName;
   }
 
-  public ProguardTypeMatcher getInheritanceAnnotation() {
-    return inheritanceAnnotation;
+  public List<ProguardTypeMatcher> getInheritanceAnnotations() {
+    return inheritanceAnnotations;
   }
 
   public ProguardClassNameList getClassNames() {
@@ -272,8 +281,8 @@
     return negatedClassAccessFlags;
   }
 
-  public ProguardTypeMatcher getClassAnnotation() {
-    return classAnnotation;
+  public List<ProguardTypeMatcher> getClassAnnotations() {
+    return classAnnotations;
   }
 
   @Override
@@ -289,7 +298,7 @@
     if (inheritanceIsExtends != that.inheritanceIsExtends) {
       return false;
     }
-    if (!Objects.equals(classAnnotation, that.classAnnotation)) {
+    if (!Objects.equals(classAnnotations, that.classAnnotations)) {
       return false;
     }
     if (!classAccessFlags.equals(that.classAccessFlags)) {
@@ -304,7 +313,7 @@
     if (!classNames.equals(that.classNames)) {
       return false;
     }
-    if (!Objects.equals(inheritanceAnnotation, that.inheritanceAnnotation)) {
+    if (!Objects.equals(inheritanceAnnotations, that.inheritanceAnnotations)) {
       return false;
     }
     if (!Objects.equals(inheritanceClassName, that.inheritanceClassName)) {
@@ -316,13 +325,13 @@
   @Override
   public int hashCode() {
     // Used multiplier 3 to avoid too much overflow when computing hashCode.
-    int result = (classAnnotation != null ? classAnnotation.hashCode() : 0);
+    int result = classAnnotations.hashCode();
     result = 3 * result + classAccessFlags.hashCode();
     result = 3 * result + negatedClassAccessFlags.hashCode();
     result = 3 * result + (classTypeNegated ? 1 : 0);
     result = 3 * result + (classType != null ? classType.hashCode() : 0);
     result = 3 * result + classNames.hashCode();
-    result = 3 * result + (inheritanceAnnotation != null ? inheritanceAnnotation.hashCode() : 0);
+    result = 3 * result + inheritanceAnnotations.hashCode();
     result = 3 * result + (inheritanceClassName != null ? inheritanceClassName.hashCode() : 0);
     result = 3 * result + (inheritanceIsExtends ? 1 : 0);
     result = 3 * result + memberRules.hashCode();
@@ -330,9 +339,9 @@
   }
 
   protected StringBuilder append(StringBuilder builder, boolean includeMemberRules) {
+    appendAnnotations(classAnnotations, builder);
     boolean needsSpaceBeforeClassType =
-        StringUtils.appendNonEmpty(builder, "@", classAnnotation, null)
-            | StringUtils.appendNonEmpty(builder, "", classAccessFlags, null)
+        StringUtils.appendNonEmpty(builder, null, classAccessFlags, null)
             | StringUtils.appendNonEmpty(
                 builder, "!", negatedClassAccessFlags.toString().replace(" ", " !"), null);
     if (needsSpaceBeforeClassType) {
@@ -345,9 +354,8 @@
     builder.append(' ');
     classNames.writeTo(builder);
     if (hasInheritanceClassName()) {
-      builder.append(' ').append(inheritanceIsExtends ? "extends" : "implements");
-      StringUtils.appendNonEmpty(builder, "@", inheritanceAnnotation, null);
-      builder.append(' ');
+      builder.append(' ').append(inheritanceIsExtends ? "extends" : "implements").append(' ');
+      appendAnnotations(inheritanceAnnotations, builder);
       builder.append(inheritanceClassName);
     }
     if (includeMemberRules && !memberRules.isEmpty()) {
@@ -362,6 +370,18 @@
     return builder;
   }
 
+  private static void appendAnnotations(
+      List<ProguardTypeMatcher> annotations, StringBuilder builder) {
+    if (!annotations.isEmpty()) {
+      Iterator<ProguardTypeMatcher> annotationIterator = annotations.iterator();
+      builder.append('@').append(annotationIterator.next());
+      while (annotationIterator.hasNext()) {
+        builder.append(" @").append(annotationIterator.next());
+      }
+      builder.append(' ');
+    }
+  }
+
   /**
    * Short String representation without member rules.
    */
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
index aee4a83..58bbdae 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationParser.java
@@ -897,7 +897,7 @@
         ProguardClassSpecification.Builder<C, B> builder,
         boolean allowValueSpecification)
         throws ProguardRuleParserException {
-      parseClassFlagsAndAnnotations(builder);
+      parseClassAnnotationsAndFlags(builder);
       parseClassType(builder);
       builder.setClassNames(parseClassNames());
       parseInheritance(builder);
@@ -965,6 +965,18 @@
       }
     }
 
+    private List<ProguardTypeMatcher> parseAnnotationList() throws ProguardRuleParserException {
+      List<ProguardTypeMatcher> annotations = null;
+      ProguardTypeMatcher current;
+      while ((current = parseAnnotation()) != null) {
+        if (annotations == null) {
+          annotations = new ArrayList<>(2);
+        }
+        annotations.add(current);
+      }
+      return annotations != null ? annotations : Collections.emptyList();
+    }
+
     private ProguardTypeMatcher parseAnnotation() throws ProguardRuleParserException {
       skipWhitespace();
       int startPosition = position;
@@ -988,16 +1000,14 @@
       return acceptChar('!');
     }
 
-    private void parseClassFlagsAndAnnotations(ProguardClassSpecification.Builder builder)
+    private void parseClassAnnotationsAndFlags(ProguardClassSpecification.Builder<?, ?> builder)
         throws ProguardRuleParserException {
+      // We allow interleaving the class annotations and class flags for compatibility with
+      // Proguard, although this should not be possible according to the grammar.
       while (true) {
-        skipWhitespace();
         ProguardTypeMatcher annotation = parseAnnotation();
         if (annotation != null) {
-          // TODO(ager): Should we only allow one annotation? It looks that way from the
-          // proguard keep rule description, but that seems like a strange restriction?
-          assert builder.getClassAnnotation() == null;
-          builder.setClassAnnotation(annotation);
+          builder.addClassAnnotation(annotation);
         } else {
           int start = position;
           ProguardAccessFlags flags =
@@ -1025,7 +1035,7 @@
           "Expected [!]interface|@interface|class|enum", origin, getPosition(start));
     }
 
-    private void parseClassType(ProguardClassSpecification.Builder builder) {
+    private void parseClassType(ProguardClassSpecification.Builder<?, ?> builder) {
       skipWhitespace();
       TextPosition start = getPosition();
       if (acceptChar('!')) {
@@ -1049,7 +1059,8 @@
       }
     }
 
-    private void parseInheritance(ProguardClassSpecification.Builder classSpecificationBuilder)
+    private void parseInheritance(
+        ProguardClassSpecification.Builder<?, ?> classSpecificationBuilder)
         throws ProguardRuleParserException {
       skipWhitespace();
       if (acceptString("implements")) {
@@ -1059,7 +1070,7 @@
       } else {
         return;
       }
-      classSpecificationBuilder.setInheritanceAnnotation(parseAnnotation());
+      classSpecificationBuilder.addInheritanceAnnotations(parseAnnotationList());
       classSpecificationBuilder.setInheritanceClassName(ProguardTypeMatcher.create(parseClassName(),
           ClassOrType.CLASS, dexItemFactory));
     }
@@ -1084,8 +1095,7 @@
     private ProguardMemberRule parseMemberRule(boolean allowValueSpecification)
         throws ProguardRuleParserException {
       ProguardMemberRule.Builder ruleBuilder = ProguardMemberRule.builder();
-      skipWhitespace();
-      ruleBuilder.setAnnotation(parseAnnotation());
+      ruleBuilder.setAnnotations(parseAnnotationList());
       parseMemberAccessFlags(ruleBuilder);
       parseMemberPattern(ruleBuilder, allowValueSpecification);
       return ruleBuilder.isValid() ? ruleBuilder.build() : null;
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
index 0e21e66..4a19c6f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardConfigurationRule.java
@@ -27,19 +27,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   public boolean isUsed() {
@@ -124,17 +135,16 @@
   protected Iterable<ProguardWildcard> getWildcards() {
     List<ProguardMemberRule> memberRules = getMemberRules();
     return Iterables.concat(
-        ProguardTypeMatcher.getWildcardsOrEmpty(getClassAnnotation()),
+        ProguardTypeMatcher.getWildcardsOrEmpty(getClassAnnotations()),
         ProguardClassNameList.getWildcardsOrEmpty(getClassNames()),
-        ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceAnnotation()),
+        ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceAnnotations()),
         ProguardTypeMatcher.getWildcardsOrEmpty(getInheritanceClassName()),
         memberRules != null
             ? memberRules.stream()
-                .map(ProguardMemberRule::getWildcards)
-                .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
+                    .map(ProguardMemberRule::getWildcards)
+                    .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
                 ::iterator
-            : Collections::emptyIterator
-    );
+            : Collections::emptyIterator);
   }
 
   @Override
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
index 8ff7670..37c75a1 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIdentifierNameStringRule.java
@@ -22,9 +22,20 @@
 
     @Override
     public ProguardIdentifierNameStringRule build() {
-      return new ProguardIdentifierNameStringRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardIdentifierNameStringRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
     }
   }
 
@@ -32,19 +43,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
index ba9ec8a..c8e185f 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardIfRule.java
@@ -58,13 +58,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules,
@@ -77,22 +77,34 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRule subsequentRule,
       Set<DexReference> preconditions) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules,
-        ProguardKeepRuleType.CONDITIONAL, ProguardKeepRuleModifiers.builder().build());
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules,
+        ProguardKeepRuleType.CONDITIONAL,
+        ProguardKeepRuleModifiers.builder().build());
     this.subsequentRule = subsequentRule;
     this.preconditions = preconditions;
   }
@@ -122,15 +134,13 @@
         getOrigin(),
         getPosition(),
         getSource(),
-        getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
         getClassType(),
         getClassNames().materialize(dexItemFactory),
-        getInheritanceAnnotation() == null
-            ? null
-            : getInheritanceAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
         getInheritanceClassName() == null
             ? null
             : getInheritanceClassName().materialize(dexItemFactory),
@@ -149,15 +159,13 @@
         neverInlineOrigin,
         Position.UNKNOWN,
         null,
-        getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
         getClassType(),
         getClassNames().materialize(dexItemFactory),
-        getInheritanceAnnotation() == null
-            ? null
-            : getInheritanceAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
         getInheritanceClassName() == null
             ? null
             : getInheritanceClassName().materialize(dexItemFactory),
@@ -196,15 +204,13 @@
         neverInlineOrigin,
         Position.UNKNOWN,
         null,
-        getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
         getClassType(),
         getClassNames().materialize(dexItemFactory),
-        getInheritanceAnnotation() == null
-            ? null
-            : getInheritanceAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
         getInheritanceClassName() == null
             ? null
             : getInheritanceClassName().materialize(dexItemFactory),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
index 4633882..305c108 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRule.java
@@ -25,9 +25,22 @@
 
     @Override
     public ProguardKeepRule build() {
-      return new ProguardKeepRule(origin, getPosition(), source, classAnnotation, classAccessFlags,
-          negatedClassAccessFlags, classTypeNegated, classType, classNames, inheritanceAnnotation,
-          inheritanceClassName, inheritanceIsExtends, memberRules, type, modifiersBuilder.build());
+      return new ProguardKeepRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules,
+          type,
+          modifiersBuilder.build());
     }
   }
 
@@ -35,21 +48,34 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRuleType type,
       ProguardKeepRuleModifiers modifiers) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules, type, modifiers);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules,
+        type,
+        modifiers);
   }
 
   /**
@@ -64,15 +90,13 @@
         getOrigin(),
         getPosition(),
         getSource(),
-        getClassAnnotation() == null ? null : getClassAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getClassAnnotations(), dexItemFactory),
         getClassAccessFlags(),
         getNegatedClassAccessFlags(),
         getClassTypeNegated(),
         getClassType(),
         getClassNames() == null ? null : getClassNames().materialize(dexItemFactory),
-        getInheritanceAnnotation() == null
-            ? null
-            : getInheritanceAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getInheritanceAnnotations(), dexItemFactory),
         getInheritanceClassName() == null
             ? null
             : getInheritanceClassName().materialize(dexItemFactory),
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
index 7455469..b87e5a6 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardKeepRuleBase.java
@@ -43,21 +43,32 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
       ProguardKeepRuleType type,
       ProguardKeepRuleModifiers modifiers) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
     this.type = type;
     this.modifiers = modifiers;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
index 1a894d3..3034f6d 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardMemberRule.java
@@ -24,7 +24,7 @@
 
   public static class Builder {
 
-    private ProguardTypeMatcher annotation;
+    private List<ProguardTypeMatcher> annotations = Collections.emptyList();
     private ProguardAccessFlags accessFlags = new ProguardAccessFlags();
     private ProguardAccessFlags negatedAccessFlags = new ProguardAccessFlags();
     private ProguardMemberType ruleType;
@@ -35,8 +35,9 @@
 
     private Builder() {}
 
-    public void setAnnotation(ProguardTypeMatcher annotation) {
-      this.annotation = annotation;
+    public void setAnnotations(List<ProguardTypeMatcher> annotations) {
+      assert annotations != null;
+      this.annotations = annotations;
     }
 
     public ProguardAccessFlags getAccessFlags() {
@@ -91,12 +92,19 @@
 
     public ProguardMemberRule build() {
       assert isValid();
-      return new ProguardMemberRule(annotation, accessFlags, negatedAccessFlags, ruleType, type,
-          name, arguments, returnValue);
+      return new ProguardMemberRule(
+          annotations,
+          accessFlags,
+          negatedAccessFlags,
+          ruleType,
+          type,
+          name,
+          arguments,
+          returnValue);
     }
   }
 
-  private final ProguardTypeMatcher annotation;
+  private final List<ProguardTypeMatcher> annotations;
   private final ProguardAccessFlags accessFlags;
   private final ProguardAccessFlags negatedAccessFlags;
   private final ProguardMemberType ruleType;
@@ -106,7 +114,7 @@
   private final ProguardMemberRuleReturnValue returnValue;
 
   private ProguardMemberRule(
-      ProguardTypeMatcher annotation,
+      List<ProguardTypeMatcher> annotations,
       ProguardAccessFlags accessFlags,
       ProguardAccessFlags negatedAccessFlags,
       ProguardMemberType ruleType,
@@ -114,7 +122,7 @@
       ProguardNameMatcher name,
       List<ProguardTypeMatcher> arguments,
       ProguardMemberRuleReturnValue returnValue) {
-    this.annotation = annotation;
+    this.annotations = annotations;
     this.accessFlags = accessFlags;
     this.negatedAccessFlags = negatedAccessFlags;
     this.ruleType = ruleType;
@@ -131,8 +139,8 @@
     return new Builder();
   }
 
-  public ProguardTypeMatcher getAnnotation() {
-    return annotation;
+  public List<ProguardTypeMatcher> getAnnotations() {
+    return annotations;
   }
 
   public ProguardAccessFlags getAccessFlags() {
@@ -187,7 +195,8 @@
             break;
           }
           // Annotations check.
-          return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
+          return RootSetBuilder.containsAllAnnotations(
+              annotations, field, matchedAnnotationsConsumer);
         }
 
       case FIELD:
@@ -207,7 +216,8 @@
             break;
           }
           // Annotations check
-          return RootSetBuilder.containsAnnotation(annotation, field, matchedAnnotationsConsumer);
+          return RootSetBuilder.containsAllAnnotations(
+              annotations, field, matchedAnnotationsConsumer);
         }
 
       case ALL_METHODS:
@@ -241,7 +251,8 @@
             break;
           }
           // Annotations check.
-          return RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer);
+          return RootSetBuilder.containsAllAnnotations(
+              annotations, method, matchedAnnotationsConsumer);
         }
 
       case METHOD:
@@ -266,7 +277,8 @@
             break;
           }
           // Annotations check.
-          if (!RootSetBuilder.containsAnnotation(annotation, method, matchedAnnotationsConsumer)) {
+          if (!RootSetBuilder.containsAllAnnotations(
+              annotations, method, matchedAnnotationsConsumer)) {
             return false;
           }
           // Parameter types check.
@@ -309,21 +321,20 @@
 
   Iterable<ProguardWildcard> getWildcards() {
     return Iterables.concat(
-        ProguardTypeMatcher.getWildcardsOrEmpty(annotation),
+        ProguardTypeMatcher.getWildcardsOrEmpty(annotations),
         ProguardTypeMatcher.getWildcardsOrEmpty(type),
         ProguardNameMatcher.getWildcardsOrEmpty(name),
         arguments != null
             ? arguments.stream()
-                .map(ProguardTypeMatcher::getWildcards)
-                .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
+                    .map(ProguardTypeMatcher::getWildcards)
+                    .flatMap(it -> StreamSupport.stream(it.spliterator(), false))
                 ::iterator
-            : Collections::emptyIterator
-    );
+            : Collections::emptyIterator);
   }
 
   ProguardMemberRule materialize(DexItemFactory dexItemFactory) {
     return new ProguardMemberRule(
-        getAnnotation() == null ? null : getAnnotation().materialize(dexItemFactory),
+        ProguardTypeMatcher.materializeList(getAnnotations(), dexItemFactory),
         getAccessFlags(),
         getNegatedAccessFlags(),
         getRuleType(),
@@ -345,7 +356,7 @@
 
     ProguardMemberRule that = (ProguardMemberRule) o;
 
-    if (annotation != null ? !annotation.equals(that.annotation) : that.annotation != null) {
+    if (!annotations.equals(that.annotations)) {
       return false;
     }
     if (!accessFlags.equals(that.accessFlags)) {
@@ -368,7 +379,7 @@
 
   @Override
   public int hashCode() {
-    int result = annotation != null ? annotation.hashCode() : 0;
+    int result = annotations.hashCode();
     result = 31 * result + accessFlags.hashCode();
     result = 31 * result + negatedAccessFlags.hashCode();
     result = 31 * result + (ruleType != null ? ruleType.hashCode() : 0);
@@ -381,7 +392,9 @@
   @Override
   public String toString() {
     StringBuilder result = new StringBuilder();
-    ProguardKeepRule.appendNonEmpty(result, "@", annotation, " ");
+    for (ProguardTypeMatcher annotation : annotations) {
+      ProguardKeepRule.appendNonEmpty(result, "@", annotation, " ");
+    }
     ProguardKeepRule.appendNonEmpty(result, null, accessFlags, " ");
     ProguardKeepRule
         .appendNonEmpty(result, null, negatedAccessFlags.toString().replace(" ", " !"), " ");
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
index a4db6a2..19bd876 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardTypeMatcher.java
@@ -36,6 +36,10 @@
     TYPE
   }
 
+  public MatchSpecificType asSpecificTypeMatcher() {
+    return null;
+  }
+
   // Evaluates this matcher on the given type.
   public abstract boolean matches(DexType type);
 
@@ -59,10 +63,30 @@
     return typeMatcher == null ? Collections::emptyIterator : typeMatcher.getWildcards();
   }
 
+  static Iterable<ProguardWildcard> getWildcardsOrEmpty(List<ProguardTypeMatcher> typeMatchers) {
+    List<ProguardWildcard> result = new ArrayList<>();
+    for (ProguardTypeMatcher typeMatcher : typeMatchers) {
+      typeMatcher.getWildcards().forEach(result::add);
+    }
+    return result;
+  }
+
   protected ProguardTypeMatcher materialize(DexItemFactory dexItemFactory) {
     return this;
   }
 
+  public static List<ProguardTypeMatcher> materializeList(
+      List<ProguardTypeMatcher> matchers, DexItemFactory dexItemFactory) {
+    if (matchers.isEmpty()) {
+      return Collections.emptyList();
+    }
+    ImmutableList.Builder<ProguardTypeMatcher> builder = ImmutableList.builder();
+    for (ProguardTypeMatcher matcher : matchers) {
+      builder.add(matcher.materialize(dexItemFactory));
+    }
+    return builder.build();
+  }
+
   @Override
   public abstract String toString();
 
@@ -312,6 +336,11 @@
     }
 
     @Override
+    public MatchSpecificType asSpecificTypeMatcher() {
+      return this;
+    }
+
+    @Override
     public boolean matches(DexType type) {
       return this.type == type;
     }
diff --git a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
index 87200fb..4083ff5 100644
--- a/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ProguardWhyAreYouKeepingRule.java
@@ -23,9 +23,20 @@
 
     @Override
     public ProguardWhyAreYouKeepingRule build() {
-      return new ProguardWhyAreYouKeepingRule(origin, getPosition(), source, classAnnotation,
-          classAccessFlags, negatedClassAccessFlags, classTypeNegated, classType, classNames,
-          inheritanceAnnotation, inheritanceClassName, inheritanceIsExtends, memberRules);
+      return new ProguardWhyAreYouKeepingRule(
+          origin,
+          getPosition(),
+          source,
+          buildClassAnnotations(),
+          classAccessFlags,
+          negatedClassAccessFlags,
+          classTypeNegated,
+          classType,
+          classNames,
+          buildInheritanceAnnotations(),
+          inheritanceClassName,
+          inheritanceIsExtends,
+          memberRules);
     }
   }
 
@@ -33,19 +44,30 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
-    super(origin, position, source, classAnnotation, classAccessFlags, negatedClassAccessFlags,
-        classTypeNegated, classType, classNames, inheritanceAnnotation, inheritanceClassName,
-        inheritanceIsExtends, memberRules);
+    super(
+        origin,
+        position,
+        source,
+        classAnnotations,
+        classAccessFlags,
+        negatedClassAccessFlags,
+        classTypeNegated,
+        classType,
+        classNames,
+        inheritanceAnnotations,
+        inheritanceClassName,
+        inheritanceIsExtends,
+        memberRules);
   }
 
   public static Builder builder() {
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
index 58e03d9..cfcd1fc 100644
--- a/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessClassInitializerRule.java
@@ -40,13 +40,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules,
@@ -60,13 +60,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
@@ -75,13 +75,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
index 8e8e288..2c7d19a 100644
--- a/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/ReprocessMethodRule.java
@@ -40,13 +40,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules,
@@ -60,13 +60,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules,
@@ -75,13 +75,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
index 9abf328..b38d077 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.graph.DexProgramClass.asProgramClassOrNull;
 
+import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
@@ -763,7 +764,7 @@
   }
 
   static AnnotationMatchResult satisfyAnnotation(ProguardConfigurationRule rule, DexClass clazz) {
-    return containsAnnotation(rule.getClassAnnotation(), clazz);
+    return containsAllAnnotations(rule.getClassAnnotations(), clazz);
   }
 
   boolean satisfyInheritanceRule(DexClass clazz, ProguardConfigurationRule rule) {
@@ -795,7 +796,7 @@
       // annotations of `class`.
       if (rule.getInheritanceClassName().matches(clazz.type, appView)) {
         AnnotationMatchResult annotationMatchResult =
-            containsAnnotation(rule.getInheritanceAnnotation(), clazz);
+            containsAllAnnotations(rule.getInheritanceAnnotations(), clazz);
         if (annotationMatchResult != null) {
           handleMatchedAnnotation(annotationMatchResult);
           return true;
@@ -832,7 +833,7 @@
       // annotations of `ifaceClass`.
       if (rule.getInheritanceClassName().matches(iface, appView)) {
         AnnotationMatchResult annotationMatchResult =
-            containsAnnotation(rule.getInheritanceAnnotation(), ifaceClass);
+            containsAllAnnotations(rule.getInheritanceAnnotations(), ifaceClass);
         if (annotationMatchResult != null) {
           handleMatchedAnnotation(annotationMatchResult);
           return true;
@@ -908,17 +909,18 @@
     return false;
   }
 
-  static AnnotationMatchResult containsAnnotation(
-      ProguardTypeMatcher classAnnotation, DexClass clazz) {
-    return containsAnnotation(classAnnotation, clazz.annotations());
+  static AnnotationMatchResult containsAllAnnotations(
+      List<ProguardTypeMatcher> annotationMatchers, DexClass clazz) {
+    return containsAllAnnotations(annotationMatchers, clazz.annotations());
   }
 
-  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> boolean containsAnnotation(
-      ProguardTypeMatcher classAnnotation,
-      DexEncodedMember<D, R> member,
-      Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
+  static <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>>
+      boolean containsAllAnnotations(
+          List<ProguardTypeMatcher> annotationMatchers,
+          DexEncodedMember<D, R> member,
+          Consumer<AnnotationMatchResult> matchedAnnotationsConsumer) {
     AnnotationMatchResult annotationMatchResult =
-        containsAnnotation(classAnnotation, member.annotations());
+        containsAllAnnotations(annotationMatchers, member.annotations());
     if (annotationMatchResult != null) {
       matchedAnnotationsConsumer.accept(annotationMatchResult);
       return true;
@@ -927,7 +929,7 @@
       DexEncodedMethod method = member.asDexEncodedMethod();
       for (int i = 0; i < method.parameterAnnotationsList.size(); i++) {
         annotationMatchResult =
-            containsAnnotation(classAnnotation, method.parameterAnnotationsList.get(i));
+            containsAllAnnotations(annotationMatchers, method.parameterAnnotationsList.get(i));
         if (annotationMatchResult != null) {
           matchedAnnotationsConsumer.accept(annotationMatchResult);
           return true;
@@ -937,14 +939,28 @@
     return false;
   }
 
-  private static AnnotationMatchResult containsAnnotation(
-      ProguardTypeMatcher classAnnotation, DexAnnotationSet annotations) {
-    if (classAnnotation == null) {
+  private static AnnotationMatchResult containsAllAnnotations(
+      List<ProguardTypeMatcher> annotationMatchers, DexAnnotationSet annotations) {
+    if (annotationMatchers.isEmpty()) {
       return AnnotationsIgnoredMatchResult.getInstance();
     }
+    List<DexAnnotation> matchedAnnotations = new ArrayList<>();
+    for (ProguardTypeMatcher annotationMatcher : annotationMatchers) {
+      DexAnnotation matchedAnnotation =
+          getFirstAnnotationThatMatches(annotationMatcher, annotations);
+      if (matchedAnnotation == null) {
+        return null;
+      }
+      matchedAnnotations.add(matchedAnnotation);
+    }
+    return new ConcreteAnnotationMatchResult(matchedAnnotations);
+  }
+
+  private static DexAnnotation getFirstAnnotationThatMatches(
+      ProguardTypeMatcher annotationMatcher, DexAnnotationSet annotations) {
     for (DexAnnotation annotation : annotations.annotations) {
-      if (classAnnotation.matches(annotation.annotation.type)) {
-        return new ConcreteAnnotationMatchResult(annotation);
+      if (annotationMatcher.matches(annotation.getAnnotationType())) {
+        return annotation;
       }
     }
     return null;
@@ -1656,38 +1672,50 @@
         options.getProguardConfiguration() != null
             ? options.getProguardConfiguration().getDontWarnPatterns()
             : ProguardClassFilter.empty();
-    if (dontWarnPatterns.matches(options.itemFactory.objectType)) {
-      return;
-    }
 
     assumeNoSideEffectsWarnings.forEach(
         (originWithPosition, methods) -> {
           boolean waitOrNotifyMethods = methods.stream().anyMatch(this::isWaitOrNotifyMethod);
+          boolean dontWarnObject = dontWarnPatterns.matches(options.itemFactory.objectType);
           StringBuilder message = new StringBuilder();
           message.append(
               "The -assumenosideeffects rule matches methods on `java.lang.Object` with wildcards");
-          if (waitOrNotifyMethods) {
-            message.append(" including the method(s) ");
-            for (int i = 0; i < methods.size(); i++) {
-              if (i > 0) {
-                message.append(i < methods.size() - 1 ? ", " : " and ");
-              }
-              message.append("`");
-              message.append(methods.get(i).toSourceStringWithoutHolder());
-              message.append("`");
+          message.append(" including the method(s) ");
+          for (int i = 0; i < methods.size(); i++) {
+            if (i > 0) {
+              message.append(i < methods.size() - 1 ? ", " : " and ");
             }
-            message.append(". ");
-            message.append("This will most likely cause problems. ");
-          } else {
-            message.append(". ");
-            message.append("This is most likely not intended. ");
+            message.append("`");
+            message.append(methods.get(i).toSourceStringWithoutHolder());
+            message.append("`.");
           }
-          message.append("Consider specifying the methods more precisely.");
-          options.reporter.warning(
+          if (waitOrNotifyMethods) {
+            message.append(" This will most likely cause problems.");
+          } else {
+            message.append(" This is most likely not intended.");
+          }
+          if (waitOrNotifyMethods && !dontWarnObject) {
+            message.append(" Specify the methods more precisely.");
+          } else {
+            message.append(" Consider specifying the methods more precisely.");
+          }
+          Diagnostic diagnostic =
               new StringDiagnostic(
                   message.toString(),
                   originWithPosition.getOrigin(),
-                  originWithPosition.getPosition()));
+                  originWithPosition.getPosition());
+          if (waitOrNotifyMethods) {
+            if (!dontWarnObject) {
+              options.reporter.error(diagnostic);
+            } else {
+              options.reporter.warning(diagnostic);
+            }
+
+          } else {
+            if (!dontWarnObject) {
+              options.reporter.warning(diagnostic);
+            }
+          }
         });
   }
 
diff --git a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
index 1098bcd..dd537bc 100644
--- a/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/UnusedArgumentRule.java
@@ -27,13 +27,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules);
@@ -44,13 +44,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
index bd3b4f8..79d472d 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -997,9 +997,7 @@
                           && !methodPoolForTarget.hasSeen(
                               MethodSignatureEquivalence.get().wrap(method)),
                   Rename.ALWAYS,
-                  appView
-                      .dexItemFactory()
-                      .prependTypeToProto(virtualMethod.holder(), virtualMethod.method.proto));
+                  appView.dexItemFactory().prependHolderToProto(virtualMethod.getReference()));
           makeStatic(resultingDirectMethod);
 
           // Update method pool collection now that we are adding a new public method.
diff --git a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
index aa52ca3..0f2f684 100644
--- a/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
+++ b/src/main/java/com/android/tools/r8/shaking/WhyAreYouNotInliningRule.java
@@ -27,13 +27,13 @@
           origin,
           getPosition(),
           source,
-          classAnnotation,
+          buildClassAnnotations(),
           classAccessFlags,
           negatedClassAccessFlags,
           classTypeNegated,
           classType,
           classNames,
-          inheritanceAnnotation,
+          buildInheritanceAnnotations(),
           inheritanceClassName,
           inheritanceIsExtends,
           memberRules);
@@ -44,13 +44,13 @@
       Origin origin,
       Position position,
       String source,
-      ProguardTypeMatcher classAnnotation,
+      List<ProguardTypeMatcher> classAnnotations,
       ProguardAccessFlags classAccessFlags,
       ProguardAccessFlags negatedClassAccessFlags,
       boolean classTypeNegated,
       ProguardClassType classType,
       ProguardClassNameList classNames,
-      ProguardTypeMatcher inheritanceAnnotation,
+      List<ProguardTypeMatcher> inheritanceAnnotations,
       ProguardTypeMatcher inheritanceClassName,
       boolean inheritanceIsExtends,
       List<ProguardMemberRule> memberRules) {
@@ -58,13 +58,13 @@
         origin,
         position,
         source,
-        classAnnotation,
+        classAnnotations,
         classAccessFlags,
         negatedClassAccessFlags,
         classTypeNegated,
         classType,
         classNames,
-        inheritanceAnnotation,
+        inheritanceAnnotations,
         inheritanceClassName,
         inheritanceIsExtends,
         memberRules);
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 00a2136..35e4614 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -867,7 +867,7 @@
       DexType libraryType,
       DexType invalidSuperType,
       String message,
-      Set<DexMethod> retarget) {
+      Set<DexEncodedMethod> retarget) {
     if (invalidLibraryClasses.add(invalidSuperType)) {
       reporter.warning(
           new InvalidLibrarySuperclassDiagnostic(
@@ -875,7 +875,7 @@
               Reference.classFromDescriptor(libraryType.toDescriptorString()),
               Reference.classFromDescriptor(invalidSuperType.toDescriptorString()),
               message,
-              ListUtils.map(retarget, DexMethod::asMethodReference)));
+              ListUtils.map(retarget, method -> method.getReference().asMethodReference())));
     }
   }
 
@@ -1210,10 +1210,11 @@
 
       public Comparator<DexMethod> interfaceMethodOrdering = null;
 
-      public Comparator<Wrapper<DexMethod>> getInterfaceMethodOrderingOrDefault(
-          Comparator<Wrapper<DexMethod>> comparator) {
+      public Comparator<Wrapper<DexEncodedMethod>> getInterfaceMethodOrderingOrDefault(
+          Comparator<Wrapper<DexEncodedMethod>> comparator) {
         if (interfaceMethodOrdering != null) {
-          return (a, b) -> interfaceMethodOrdering.compare(a.get(), b.get());
+          return (a, b) ->
+              interfaceMethodOrdering.compare(a.get().getReference(), b.get().getReference());
         }
         return comparator;
       }
diff --git a/src/main/java/com/android/tools/r8/utils/WorkList.java b/src/main/java/com/android/tools/r8/utils/WorkList.java
index 3e35802..d8bd420 100644
--- a/src/main/java/com/android/tools/r8/utils/WorkList.java
+++ b/src/main/java/com/android/tools/r8/utils/WorkList.java
@@ -65,6 +65,14 @@
     return !workingList.isEmpty();
   }
 
+  public void markAsSeen(T item) {
+    seen.add(item);
+  }
+
+  public void markAsSeen(Iterable<T> items) {
+    items.forEach(this::markAsSeen);
+  }
+
   public T next() {
     assert hasNext();
     return workingList.removeFirst();
diff --git a/src/test/java/com/android/tools/r8/D8TestBuilder.java b/src/test/java/com/android/tools/r8/D8TestBuilder.java
index edfb00c..1496847 100644
--- a/src/test/java/com/android/tools/r8/D8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/D8TestBuilder.java
@@ -74,12 +74,9 @@
 
   @Override
   public D8TestBuilder enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      // Use P to mimic current Android Studio.
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
-      builder.addDesugaredLibraryConfiguration(
-          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/R8TestBuilder.java b/src/test/java/com/android/tools/r8/R8TestBuilder.java
index 9d0684d..4fc59c6 100644
--- a/src/test/java/com/android/tools/r8/R8TestBuilder.java
+++ b/src/test/java/com/android/tools/r8/R8TestBuilder.java
@@ -561,12 +561,9 @@
 
   @Override
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    if (minAPILevel.getLevel() < AndroidApiLevel.O.getLevel()) {
-      // Use P to mimic current Android Studio.
-      builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
-      builder.addDesugaredLibraryConfiguration(
-          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    if (minApiLevel.getLevel() < AndroidApiLevel.O.getLevel()) {
+      super.enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer);
       builder.setDesugaredLibraryKeepRuleConsumer(keepRuleConsumer);
     }
     return self();
diff --git a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
index 490cf54..ce6c9fb 100644
--- a/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
+++ b/src/test/java/com/android/tools/r8/TestCompilerBuilder.java
@@ -10,7 +10,6 @@
 import com.android.tools.r8.TestBase.Backend;
 import com.android.tools.r8.debug.DebugTestConfig;
 import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase.KeepRuleConsumer;
-import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.testing.AndroidBuildVersion;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -93,8 +92,12 @@
     return self();
   }
 
-  public T allowCheckDiscardedErrors() {
-    return addOptionsModification(options -> options.testing.allowCheckDiscardedErrors = true);
+  public T allowCheckDiscardedErrors(boolean skipReporting) {
+    return addOptionsModification(
+        options -> {
+          options.testing.allowCheckDiscardedErrors = true;
+          options.testing.dontReportFailingCheckDiscarded = skipReporting;
+        });
   }
 
   public CR compile() throws CompilationFailedException {
@@ -390,8 +393,16 @@
   }
 
   public T enableCoreLibraryDesugaring(
-      AndroidApiLevel minAPILevel, KeepRuleConsumer keepRuleConsumer) {
-    throw new Unreachable("Should be overridden or is unsupported.");
+      AndroidApiLevel minApiLevel, KeepRuleConsumer keepRuleConsumer) {
+    assert minApiLevel.getLevel() < AndroidApiLevel.O.getLevel();
+    builder.addDesugaredLibraryConfiguration(
+        StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+    // TODO(b/158543446): This should not be setting an implicit library file. Doing so causes
+    //  inconsistent library setup depending on the api level and makes tests hard to read and
+    //  reason about.
+    // Use P to mimic current Android Studio.
+    builder.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.P));
+    return self();
   }
 
   @Override
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
new file mode 100644
index 0000000..7192449
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithAnonymousClass.java
@@ -0,0 +1,251 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarLambdaWithAnonymousClass extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarLambdaWithAnonymousClass(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  static class Counter {
+    private int count = 0;
+
+    void increment() {
+      count++;
+    }
+
+    int getCount() {
+      return count;
+    }
+  }
+
+  private void checkEnclosingMethod(CodeInspector inspector) {
+    Counter counter = new Counter();
+    inspector.forAllClasses(
+        clazz -> {
+          if (clazz.getFinalName().endsWith("$TestClass$1")
+              || clazz.getFinalName().endsWith("$TestClass$2")) {
+            counter.increment();
+            assertTrue(clazz.isAnonymousClass());
+            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+            ClassSubject testClassSubject =
+                inspector.clazz(DesugarLambdaWithAnonymousClass.TestClass.class);
+            assertEquals(
+                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+            assertThat(
+                testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                isPresent());
+          }
+        });
+    assertEquals(2, counter.getCount());
+  }
+
+  // TODO(158752316): There should be no use of this check.
+  private void checkEnclosingMethodWrong(CodeInspector inspector) {
+    Counter counter = new Counter();
+    inspector.forAllClasses(
+        clazz -> {
+          if (clazz.getFinalName().endsWith("$TestClass$1")
+              || clazz.getFinalName().endsWith("$TestClass$2")) {
+            counter.increment();
+            assertTrue(clazz.isAnonymousClass());
+            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+            ClassSubject testClassSubject =
+                inspector.clazz(DesugarLambdaWithAnonymousClass.TestClass.class);
+            assertEquals(
+                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+            if (enclosingMethod.name.toString().contains("Static")) {
+              assertThat(
+                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                  isPresent());
+            } else {
+              assertThat(
+                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                  not(isPresent()));
+            }
+          }
+        });
+    assertEquals(2, counter.getCount());
+  }
+
+  private void checkArtResult(D8TestRunResult result) {
+    // TODO(158752316): This should neither return null nor fail.
+    if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
+        || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
+      result.assertSuccessWithOutputLines(
+          "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
+    } else {
+      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+    }
+  }
+
+  @BeforeClass
+  public static void checkExpectedJavacNames() throws Exception {
+    CodeInspector inspector =
+        new CodeInspector(
+            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithLocalClass.class));
+    String outer = DesugarLambdaWithLocalClass.class.getTypeName();
+    ClassSubject testClass = inspector.clazz(outer + "$TestClass");
+    assertThat(testClass, isPresent());
+    assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
+    assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$1MyConsumerImpl"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$2MyConsumerImpl"), isPresent());
+  }
+
+  @Test
+  public void testDefault() throws Exception {
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .inspect(this::checkEnclosingMethod)
+          .assertSuccessWithOutputLines(
+              "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Run on Art.
+      checkArtResult(
+          testForD8()
+              .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .inspect(this::checkEnclosingMethodWrong)
+              .run(parameters.getRuntime(), TestClass.class));
+    }
+  }
+
+  @Test
+  public void testCfToCf() throws Exception {
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(DesugarLambdaWithAnonymousClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::checkEnclosingMethodWrong)
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addProgramFiles(jar)
+          .run(parameters.getRuntime(), TestClass.class)
+          // TODO(158752316): This should not fail.
+          .assertFailureWithErrorThatThrows(InternalError.class);
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Compile to DEX without desugaring and run on Art.
+      checkArtResult(
+          testForD8()
+              .addProgramFiles(jar)
+              .setMinApi(parameters.getApiLevel())
+              .disableDesugaring()
+              .compile()
+              .inspect(this::checkEnclosingMethodWrong)
+              .run(parameters.getRuntime(), TestClass.class));
+    }
+  }
+
+  public interface MyConsumer<T> {
+    void accept(T s);
+  }
+
+  public static class StringList extends ArrayList<String> {
+    public void forEachString(MyConsumer<String> consumer) {
+      for (String s : this) {
+        consumer.accept(s);
+      }
+    }
+  }
+
+  public static class TestClass {
+
+    public void test() {
+      StringList list = new StringList();
+
+      list.add("Hello ");
+      list.add("from ");
+      list.add("inside ");
+
+      list.forEachString(
+          s -> {
+            new MyConsumer<String>() {
+              public void accept(String s) {
+                System.out.print(s);
+                if (s.startsWith("inside")) {
+                  if (getClass().getEnclosingMethod() == null) {
+                    System.out.println("<null>");
+                  } else {
+                    System.out.println(getClass().getEnclosingMethod().getName());
+                  }
+                }
+              }
+            }.accept(s);
+          });
+    }
+
+    public static void testStatic() {
+      StringList list = new StringList();
+
+      list.add("Hello ");
+      list.add("from ");
+      list.add("inside ");
+
+      list.forEachString(
+          s -> {
+            new MyConsumer<String>() {
+              public void accept(String s) {
+                System.out.print(s);
+                if (s.startsWith("inside")) {
+                  if (getClass().getEnclosingMethod() == null) {
+                    System.out.println("<null>");
+                  } else {
+                    System.out.println(getClass().getEnclosingMethod().getName());
+                  }
+                }
+              }
+            }.accept(s);
+          });
+    }
+
+    public static void main(String[] args) {
+      new TestClass().test();
+      TestClass.testStatic();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
new file mode 100644
index 0000000..68fd89e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/DesugarLambdaWithLocalClass.java
@@ -0,0 +1,250 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DesugarLambdaWithLocalClass extends TestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private final TestParameters parameters;
+
+  public DesugarLambdaWithLocalClass(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  static class Counter {
+    private int count = 0;
+
+    void increment() {
+      count++;
+    }
+
+    int getCount() {
+      return count;
+    }
+  }
+
+  private void checkEnclosingMethod(CodeInspector inspector) {
+    Counter counter = new Counter();
+    inspector.forAllClasses(
+        clazz -> {
+          if (clazz.getFinalName().endsWith("MyConsumerImpl")) {
+            counter.increment();
+            assertTrue(clazz.isLocalClass());
+            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+            ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+            assertEquals(
+                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+            assertThat(
+                testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                isPresent());
+          }
+        });
+    assertEquals(2, counter.getCount());
+  }
+
+  // TODO(158752316): There should be no use of this check.
+  private void checkEnclosingMethodWrong(CodeInspector inspector) {
+    Counter counter = new Counter();
+    inspector.forAllClasses(
+        clazz -> {
+          if (clazz.getFinalName().endsWith("$TestClass$1MyConsumerImpl")
+              || clazz.getFinalName().endsWith("$TestClass$2MyConsumerImpl")) {
+            counter.increment();
+            assertTrue(clazz.isLocalClass());
+            DexMethod enclosingMethod = clazz.getFinalEnclosingMethod();
+            ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+            assertEquals(
+                testClassSubject, inspector.clazz(enclosingMethod.holder.toSourceString()));
+            if (enclosingMethod.name.toString().contains("Static")) {
+              assertThat(
+                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                  isPresent());
+            } else {
+              assertThat(
+                  testClassSubject.uniqueMethodWithName(enclosingMethod.name.toString()),
+                  not(isPresent()));
+            }
+          }
+        });
+    assertEquals(2, counter.getCount());
+  }
+
+  private void checkArtResult(D8TestRunResult result) {
+    // TODO(158752316): This should neither return null nor fail.
+    if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)
+        || parameters.getRuntime().asDex().getVm().getVersion().isNewerThan(Version.V6_0_1)) {
+      result.assertSuccessWithOutputLines(
+          "Hello from inside <null>", "Hello from inside lambda$testStatic$1");
+    } else {
+      result.assertFailureWithErrorThatThrows(IncompatibleClassChangeError.class);
+    }
+  }
+
+  @BeforeClass
+  public static void checkExpectedJavacNames() throws Exception {
+    CodeInspector inspector =
+        new CodeInspector(
+            ToolHelper.getClassFilesForInnerClasses(DesugarLambdaWithAnonymousClass.class));
+    String outer = DesugarLambdaWithAnonymousClass.class.getTypeName();
+    ClassSubject testClass = inspector.clazz(outer + "$TestClass");
+    assertThat(testClass, isPresent());
+    assertThat(testClass.uniqueMethodWithName("lambda$test$0"), isPresent());
+    assertThat(testClass.uniqueMethodWithName("lambda$testStatic$1"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$1"), isPresent());
+    assertThat(inspector.clazz(outer + "$TestClass$2"), isPresent());
+  }
+
+  @Test
+  public void testDefault() throws Exception {
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addInnerClasses(DesugarLambdaWithLocalClass.class)
+          .run(parameters.getRuntime(), TestClass.class)
+          .inspect(this::checkEnclosingMethod)
+          .assertSuccessWithOutputLines(
+              "Hello from inside lambda$test$0", "Hello from inside lambda$testStatic$1");
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Run on Art.
+      checkArtResult(
+          testForD8()
+              .addInnerClasses(DesugarLambdaWithLocalClass.class)
+              .setMinApi(parameters.getApiLevel())
+              .compile()
+              .inspect(this::checkEnclosingMethodWrong)
+              .run(parameters.getRuntime(), TestClass.class));
+    }
+  }
+
+  @Test
+  public void testCfToCf() throws Exception {
+    // Use D8 to desugar with Java classfile output.
+    Path jar =
+        testForD8(Backend.CF)
+            .addInnerClasses(DesugarLambdaWithLocalClass.class)
+            .setMinApi(parameters.getApiLevel())
+            .compile()
+            .inspect(this::checkEnclosingMethodWrong)
+            .writeToZip();
+
+    if (parameters.getRuntime().isCf()) {
+      // Run on the JVM.
+      testForJvm()
+          .addProgramFiles(jar)
+          .run(parameters.getRuntime(), TestClass.class)
+          // TODO(158752316): This should not fail.
+          .assertFailureWithErrorThatThrows(InternalError.class);
+    } else {
+      assert parameters.getRuntime().isDex();
+      // Compile to DEX without desugaring and run on Art.
+      checkArtResult(
+          testForD8()
+              .addProgramFiles(jar)
+              .setMinApi(parameters.getApiLevel())
+              .disableDesugaring()
+              .compile()
+              .inspect(this::checkEnclosingMethodWrong)
+              .run(parameters.getRuntime(), TestClass.class));
+    }
+  }
+
+  public interface MyConsumer<T> {
+    void accept(T s);
+  }
+
+  public static class StringList extends ArrayList<String> {
+    public void forEachString(MyConsumer<String> consumer) {
+      for (String s : this) {
+        consumer.accept(s);
+      }
+    }
+  }
+
+  public static class TestClass {
+
+    public void test() {
+      StringList list = new StringList();
+
+      list.add("Hello ");
+      list.add("from ");
+      list.add("inside ");
+
+      list.forEachString(
+          s -> {
+            class MyConsumerImpl implements MyConsumer<String> {
+              public void accept(String s) {
+                System.out.print(s);
+                if (s.startsWith("inside")) {
+                  if (getClass().getEnclosingMethod() == null) {
+                    System.out.println("<null>");
+                  } else {
+                    System.out.println(getClass().getEnclosingMethod().getName());
+                  }
+                }
+              }
+            }
+            new MyConsumerImpl().accept(s);
+          });
+    }
+
+    public static void testStatic() {
+      StringList list = new StringList();
+
+      list.add("Hello ");
+      list.add("from ");
+      list.add("inside ");
+
+      list.forEachString(
+          s -> {
+            class MyConsumerImpl implements MyConsumer<String> {
+              public void accept(String s) {
+                System.out.print(s);
+                if (s.startsWith("inside")) {
+                  if (getClass().getEnclosingMethod() == null) {
+                    System.out.println("<null>");
+                  } else {
+                    System.out.println(getClass().getEnclosingMethod().getName());
+                  }
+                }
+              }
+            }
+            new MyConsumerImpl().accept(s);
+          });
+    }
+
+    public static void main(String[] args) {
+      new TestClass().test();
+      TestClass.testStatic();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
new file mode 100644
index 0000000..9d3d498
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToEmulatedDefaultMethodTest.java
@@ -0,0 +1,155 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToEmulatedDefaultMethodTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED = StringUtils.lines("bar", "bar");
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  private final TestParameters parameters;
+
+  public InvokeSuperToEmulatedDefaultMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private boolean needsDefaultInterfaceMethodDesugaring() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeFalse(needsDefaultInterfaceMethodDesugaring());
+    testForRuntime(parameters)
+        .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    assumeTrue(needsDefaultInterfaceMethodDesugaring());
+    testForD8()
+        .addInnerClasses(InvokeSuperToEmulatedDefaultMethodTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel())
+        .compile()
+        .addDesugaredCoreLibraryRunClassPath(this::buildDesugaredLibrary, parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  public interface StringMap extends Map<String, String> {
+
+    @Override
+    default String getOrDefault(Object key, String defaultValue) {
+      // Simple forward that targets the desugared library.
+      return Map.super.getOrDefault(key + "oo", defaultValue);
+    }
+  }
+
+  public interface StringMapIndirection extends StringMap {
+
+    @Override
+    default String getOrDefault(Object key, String defaultValue) {
+      // Simple forward to a program defined default method.
+      return StringMap.super.getOrDefault("f", defaultValue);
+    }
+  }
+
+  public static class StringHashMap implements StringMapIndirection {
+    HashMap<String, String> delegate = new HashMap<>();
+
+    @Override
+    public int size() {
+      return delegate.size();
+    }
+
+    @Override
+    public boolean isEmpty() {
+      return delegate.isEmpty();
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+      return delegate.containsKey(key);
+    }
+
+    @Override
+    public boolean containsValue(Object value) {
+      return delegate.containsValue(value);
+    }
+
+    @Override
+    public String get(Object key) {
+      return delegate.get(key);
+    }
+
+    @Override
+    public String put(String key, String value) {
+      return delegate.put(key, value);
+    }
+
+    @Override
+    public String remove(Object key) {
+      return delegate.remove(key);
+    }
+
+    @Override
+    public void putAll(Map<? extends String, ? extends String> m) {
+      delegate.putAll(m);
+    }
+
+    @Override
+    public void clear() {
+      delegate.clear();
+    }
+
+    @Override
+    public Set<String> keySet() {
+      return delegate.keySet();
+    }
+
+    @Override
+    public Collection<String> values() {
+      return delegate.values();
+    }
+
+    @Override
+    public Set<Entry<String, String>> entrySet() {
+      return delegate.entrySet();
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      StringHashMap map = new StringHashMap();
+      map.put("foo", "bar");
+      System.out.println(map.getOrDefault("foo", "not found"));
+      System.out.println(map.getOrDefault("bar", "not found"));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
new file mode 100644
index 0000000..f63c4d9
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/InvokeSuperToRewrittenDefaultMethodTest.java
@@ -0,0 +1,139 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticType;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.StringResource;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.desugar.desugaredlibrary.DesugaredLibraryTestBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.ExceptionDiagnostic;
+import com.android.tools.r8.utils.StringUtils;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.IntConsumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class InvokeSuperToRewrittenDefaultMethodTest extends DesugaredLibraryTestBase {
+
+  private static final String EXPECTED = StringUtils.lines("Y", "89");
+
+  @Parameterized.Parameters(name = "{0}, old-rt:{1}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
+  }
+
+  private final TestParameters parameters;
+  private final boolean rtWithoutConsumer;
+
+  public InvokeSuperToRewrittenDefaultMethodTest(
+      TestParameters parameters, boolean rtWithoutConsumer) {
+    this.parameters = parameters;
+    this.rtWithoutConsumer = rtWithoutConsumer;
+  }
+
+  private boolean needsDefaultInterfaceMethodDesugaring() {
+    return parameters.isDexRuntime()
+        && parameters.getApiLevel().isLessThan(apiLevelWithDefaultInterfaceMethodsSupport());
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    assumeFalse(needsDefaultInterfaceMethodDesugaring());
+    testForRuntime(parameters)
+        .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testDesugaring() throws Exception {
+    assumeTrue(needsDefaultInterfaceMethodDesugaring());
+    try {
+      testForD8()
+          .addInnerClasses(InvokeSuperToRewrittenDefaultMethodTest.class)
+          .setMinApi(parameters.getApiLevel())
+          .apply(
+              b -> {
+                if (rtWithoutConsumer) {
+                  b.addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.B));
+                  // TODO(b/158543446): Remove this once enableCoreLibraryDesugaring is fixed.
+                  b.getBuilder()
+                      .addDesugaredLibraryConfiguration(
+                          StringResource.fromFile(ToolHelper.DESUGAR_LIB_JSON_FOR_TESTING));
+                } else {
+                  // TODO(b/158543446): Move this out to the shared builder once
+                  //  enableCoreLibraryDesugaring is fixed.
+                  b.enableCoreLibraryDesugaring(parameters.getApiLevel());
+                }
+              })
+          .compileWithExpectedDiagnostics(
+              diagnostics -> {
+                if (rtWithoutConsumer) {
+                  diagnostics.assertOnlyErrors();
+                  // TODO(b/158543011): Should fail with a nice user error for invalid library.
+                  diagnostics.assertErrorsMatch(
+                      allOf(
+                          diagnosticType(ExceptionDiagnostic.class),
+                          diagnosticMessage(containsString("AssertionError"))));
+                } else {
+                  diagnostics.assertNoMessages();
+                }
+              })
+          .addDesugaredCoreLibraryRunClassPath(
+              this::buildDesugaredLibrary, parameters.getApiLevel())
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutput(EXPECTED);
+      assertFalse(rtWithoutConsumer);
+    } catch (CompilationFailedException e) {
+      assertTrue(rtWithoutConsumer);
+    }
+  }
+
+  @FunctionalInterface
+  public interface CharConsumer extends Consumer<Character>, IntConsumer {
+
+    void accept(char c);
+
+    @Override
+    default void accept(int value) {
+      accept((char) value);
+    }
+
+    @Override
+    default void accept(Character character) {
+      accept(character.charValue());
+    }
+
+    @Override
+    default Consumer<Character> andThen(Consumer<? super Character> after) {
+      // Simple forward to the default method of the parent.
+      // Must be rewritten to target the companion class of the rewritten Consumer type.
+      return Consumer.super.andThen(after);
+    }
+  }
+
+  public static class TestClass {
+
+    public static void main(String[] args) {
+      CharConsumer consumer = System.out::println;
+      consumer.andThen((Consumer<? super Character>) c -> System.out.println((int) c)).accept('Y');
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
new file mode 100644
index 0000000..ff07b24
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DesugaredGenericSignatureTest.java
@@ -0,0 +1,129 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.time.LocalDate;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class DesugaredGenericSignatureTest extends DesugaredLibraryTestBase {
+
+  private final TestParameters parameters;
+  private final boolean shrinkDesugaredLibrary;
+  private static final String EXPECTED = StringUtils.lines("1970", "1", "2");
+
+  @Parameters(name = "{1}, shrinkDesugaredLibrary: {0}")
+  public static List<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withDexRuntimes().withAllApiLevels().build(), BooleanUtils.values());
+  }
+
+  public DesugaredGenericSignatureTest(TestParameters parameters, boolean shrinkDesugaredLibrary) {
+    this.parameters = parameters;
+    this.shrinkDesugaredLibrary = shrinkDesugaredLibrary;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForD8()
+        .addInnerClasses(DesugaredGenericSignatureTest.class)
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .setIncludeClassesChecksum(true)
+        .compile()
+        .inspect(this::checkRewrittenSignature)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = createKeepRuleConsumer(parameters);
+    testForR8(parameters.getBackend())
+        .addInnerClasses(DesugaredGenericSignatureTest.class)
+        .addKeepMainRule(Main.class)
+        .addKeepAllClassesRuleWithAllowObfuscation()
+        .addKeepAttributes(
+            ProguardKeepAttributes.SIGNATURE,
+            ProguardKeepAttributes.INNER_CLASSES,
+            ProguardKeepAttributes.ENCLOSING_METHOD)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .enableCoreLibraryDesugaring(parameters.getApiLevel(), keepRuleConsumer)
+        .compile()
+        .inspect(this::checkRewrittenSignature)
+        .addDesugaredCoreLibraryRunClassPath(
+            this::buildDesugaredLibrary,
+            parameters.getApiLevel(),
+            keepRuleConsumer.get(),
+            shrinkDesugaredLibrary)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void checkRewrittenSignature(CodeInspector inspector) {
+    if (!requiresEmulatedInterfaceCoreLibDesugaring(parameters)) {
+      return;
+    }
+    ClassSubject javaTimeBox = inspector.clazz(JavaTimeDateBox.class);
+    assertThat(javaTimeBox, isPresent());
+    ClassSubject box = inspector.clazz(Box.class);
+    assertThat(box, isPresent());
+    String finalBoxDescriptor = box.getFinalDescriptor();
+    assertEquals(
+        "Ljava/lang/Object;"
+            + finalBoxDescriptor.substring(0, finalBoxDescriptor.length() - 1)
+            + "<Lj$/time/LocalDate;>;",
+        javaTimeBox.getFinalSignatureAttribute());
+  }
+
+  public interface Box<T> {
+    T addOne(T t);
+  }
+
+  public static class JavaTimeDateBox implements Box<java.time.LocalDate> {
+
+    @Override
+    @NeverInline
+    public LocalDate addOne(LocalDate localDate) {
+      return localDate.plusDays(1);
+    }
+  }
+
+  public static class Main {
+
+    public static Box<java.time.LocalDate> bar() {
+      return new JavaTimeDateBox();
+    }
+
+    public static void main(String[] args) {
+      LocalDate localDate = bar().addOne(LocalDate.of(1970, 1, 1));
+      System.out.println(localDate.getYear());
+      System.out.println(localDate.getMonthValue());
+      System.out.println(localDate.getDayOfMonth());
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
new file mode 100644
index 0000000..1d66c7f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/desugaredlibrary/DontKeepBootstrapClassesTest.java
@@ -0,0 +1,60 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.desugar.desugaredlibrary;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.NoneRuntime;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.util.Arrays;
+import java.util.function.Consumer;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class DontKeepBootstrapClassesTest extends DesugaredLibraryTestBase {
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  final AndroidApiLevel minApiLevel = AndroidApiLevel.B;
+
+  public DontKeepBootstrapClassesTest(TestParameters parameters) {
+    assertEquals(NoneRuntime.getInstance(), parameters.getRuntime());
+  }
+
+  @Test
+  public void test() throws Exception {
+    KeepRuleConsumer keepRuleConsumer = new PresentKeepRuleConsumer();
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .setMinApi(minApiLevel)
+        .addLibraryClasses(CustomLibClass.class)
+        .enableCoreLibraryDesugaring(minApiLevel, keepRuleConsumer)
+        .compile();
+    assertThat(keepRuleConsumer.get(), containsString("-keep class j$.util.function.Consumer"));
+    // TODO(b/158635415): Don't generate keep rules targeting items outside desugared library.
+    assertThat(keepRuleConsumer.get(), containsString("-keep class java.util"));
+  }
+
+  static class CustomLibClass {
+    public static <T> Consumer<T> id(Consumer<T> fn) {
+      return fn;
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Arrays.asList("A", "B", "C").forEach(CustomLibClass.id(System.out::println));
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
deleted file mode 100644
index 642c263..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialTest.java
+++ /dev/null
@@ -1,80 +0,0 @@
-// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.invokespecial;
-
-import static com.android.tools.r8.utils.DescriptorUtils.javaTypeToDescriptor;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.AsmTestBase;
-import com.android.tools.r8.ByteDataView;
-import com.android.tools.r8.ClassFileConsumer;
-import com.android.tools.r8.CompilationFailedException;
-import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.StringUtils;
-import java.nio.file.Path;
-import org.junit.BeforeClass;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-
-public class InvokeSpecialTest extends AsmTestBase {
-
-  @ClassRule
-  public static TemporaryFolder tempFolder = ToolHelper.getTemporaryFolderForTest();
-
-  private static Path inputJar;
-
-  @BeforeClass
-  public static void setup() throws Exception {
-    inputJar = tempFolder.getRoot().toPath().resolve("input.jar");
-    ClassFileConsumer consumer = new ClassFileConsumer.ArchiveConsumer(inputJar);
-    consumer.accept(
-        ByteDataView.of(ToolHelper.getClassAsBytes(Main.class)),
-        javaTypeToDescriptor(Main.class.getTypeName()),
-        null);
-    consumer.accept(
-        ByteDataView.of(TestClassDump.dump()),
-        javaTypeToDescriptor(TestClass.class.getTypeName()),
-        null);
-    consumer.finished(null);
-  }
-
-  @Test
-  public void testExpectedBehavior() throws Exception {
-    testForJvm()
-        .addProgramClasses(Main.class, TestClass.class)
-        .run(Main.class)
-        .assertSuccessWithOutput(StringUtils.lines("true", "false"));
-  }
-
-  @Test(expected = CompilationFailedException.class)
-  public void testD8Behavior() throws Exception {
-    // TODO(b/110175213): Should succeed with output "true\nfalse\n".
-    testForD8()
-        .addProgramFiles(inputJar)
-        .compileWithExpectedDiagnostics(
-            testDiagnosticMessages ->
-                testDiagnosticMessages.assertErrorMessageThatMatches(
-                    containsString("Failed to compile unsupported use of invokespecial")));
-  }
-
-  @Test
-  public void testDXBehavior() throws Exception {
-    assumeTrue(ToolHelper.artSupported());
-    testForDX()
-        .addProgramFiles(inputJar)
-        .run(Main.class)
-        .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
-  }
-
-  private static String getExpectedOutput() {
-    if (ToolHelper.getDexVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
-      return "VFY: unable to resolve direct method";
-    }
-    return "was expected to be of type direct but instead was found to be of type virtual";
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
new file mode 100644
index 0000000..730ae81
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/graph/invokespecial/InvokeSpecialToVirtualMethodTest.java
@@ -0,0 +1,138 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.graph.invokespecial;
+
+import static com.android.tools.r8.DiagnosticsMatcher.diagnosticMessage;
+import static com.android.tools.r8.utils.DescriptorUtils.getBinaryNameFromJavaType;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
+import com.android.tools.r8.utils.StringUtils;
+import java.io.IOException;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InvokeSpecialToVirtualMethodTest extends TestBase {
+
+  static final String EXPECTED = StringUtils.lines("Bar::foo", "Foo::foo", "Foo::foo", "Foo::foo");
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+  }
+
+  public InvokeSpecialToVirtualMethodTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testJvm() throws Exception {
+    assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramClasses(Base.class, Bar.class, TestClass.class)
+        .addProgramClassFileData(getFooTransform())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test(expected = CompilationFailedException.class)
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(Base.class, Bar.class, TestClass.class)
+        .addProgramClassFileData(getFooTransform())
+        .setMinApi(parameters.getApiLevel())
+        .compileWithExpectedDiagnostics(
+            diagnostics ->
+                diagnostics
+                    .assertOnlyErrors()
+                    .assertErrorsMatch(
+                        diagnosticMessage(containsString("unsupported use of invokespecial"))));
+  }
+
+  @Test
+  public void testDX() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForDX()
+        .addProgramClasses(Base.class, Bar.class, TestClass.class)
+        .addProgramClassFileData(getFooTransform())
+        .setMinApi(parameters.getApiLevel())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertFailureWithErrorThatMatches(containsString(getExpectedOutput()));
+  }
+
+  private String getExpectedOutput() {
+    if (parameters.getRuntime().asDex().getVm().getVersion().isOlderThanOrEqual(Version.V4_4_4)) {
+      return "VFY: unable to resolve direct method";
+    }
+    return "was expected to be of type direct but instead was found to be of type virtual";
+  }
+
+  private byte[] getFooTransform() throws IOException {
+    return transformer(Foo.class)
+        .transformMethodInsnInMethod(
+            "foo",
+            (opcode, owner, name, descriptor, isInterface, visitor) -> {
+              if (Opcodes.INVOKESPECIAL == opcode) {
+                assertEquals(getBinaryNameFromJavaType(Base.class.getName()), owner);
+                assertEquals("foo", name);
+                visitor.visitMethodInsn(
+                    opcode,
+                    getBinaryNameFromJavaType(Foo.class.getName()),
+                    name,
+                    descriptor,
+                    isInterface);
+              } else {
+                visitor.visitMethodInsn(opcode, owner, name, descriptor, isInterface);
+              }
+            })
+        .transform();
+  }
+
+  static class Base {
+    // Base method is never hit.
+    public void foo(int i) {
+      System.out.println("Base::foo");
+    }
+  }
+
+  static class Foo extends Base {
+
+    public void foo(int i) {
+      if (i > 0) {
+        System.out.println("Foo::foo");
+        // Will be converted to invoke-special Foo.foo.
+        super.foo(i - 1);
+      }
+    }
+  }
+
+  static class Bar extends Foo {
+
+    // Subclass override is only hit initially.
+    @Override
+    public void foo(int i) {
+      System.out.println("Bar::foo");
+      super.foo(3);
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Bar().foo(0);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java b/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
deleted file mode 100644
index deb316d..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/Main.java
+++ /dev/null
@@ -1,13 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.invokespecial;
-
-public class Main {
-
-  public static void main(String[] args) {
-    TestClass x = new TestClass();
-    x.m(true);
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
deleted file mode 100644
index c08091e..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClass.java
+++ /dev/null
@@ -1,15 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.invokespecial;
-
-public class TestClass {
-
-  public void m(boolean recurse) {
-    System.out.println(recurse);
-    if (recurse) {
-      m(false);
-    }
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java b/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
deleted file mode 100644
index ae336834..0000000
--- a/src/test/java/com/android/tools/r8/graph/invokespecial/TestClassDump.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.graph.invokespecial;
-
-import org.objectweb.asm.AnnotationVisitor;
-import org.objectweb.asm.ClassWriter;
-import org.objectweb.asm.FieldVisitor;
-import org.objectweb.asm.Label;
-import org.objectweb.asm.MethodVisitor;
-import org.objectweb.asm.Opcodes;
-
-// Generated by running ./tools/asmifier.py build/classes/test/com/android/tools/r8/graph/-
-// invokespecial/TestClass.class, and changing the invoke-virtual TestClass.m() instruction to
-// an invoke-special instruction.
-public class TestClassDump implements Opcodes {
-
-  public static byte[] dump() throws Exception {
-
-    ClassWriter cw = new ClassWriter(0);
-    FieldVisitor fv;
-    MethodVisitor mv;
-    AnnotationVisitor av0;
-
-    cw.visit(
-        V1_8,
-        ACC_PUBLIC + ACC_SUPER,
-        "com/android/tools/r8/graph/invokespecial/TestClass",
-        null,
-        "java/lang/Object",
-        null);
-
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
-      mv.visitCode();
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(1, 1);
-      mv.visitEnd();
-    }
-    {
-      mv = cw.visitMethod(ACC_PUBLIC, "m", "(Z)V", null, null);
-      mv.visitCode();
-      mv.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
-      mv.visitVarInsn(ILOAD, 1);
-      mv.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Z)V", false);
-      mv.visitVarInsn(ILOAD, 1);
-      Label l0 = new Label();
-      mv.visitJumpInsn(IFEQ, l0);
-      mv.visitVarInsn(ALOAD, 0);
-      mv.visitInsn(ICONST_0);
-      // Note: Changed from INVOKEVIRTUAL to INVOKESPECIAL.
-      mv.visitMethodInsn(
-          INVOKESPECIAL, "com/android/tools/r8/graph/invokespecial/TestClass", "m", "(Z)V", false);
-      mv.visitLabel(l0);
-      mv.visitFrame(Opcodes.F_SAME, 0, null, 0, null);
-      mv.visitInsn(RETURN);
-      mv.visitMaxs(2, 2);
-      mv.visitEnd();
-    }
-    cw.visitEnd();
-
-    return cw.toByteArray();
-  }
-}
diff --git a/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
new file mode 100644
index 0000000..b80b932
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/ChromeCompilationBase.java
@@ -0,0 +1,71 @@
+// Copyright (c) 2016, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.utils.FileUtils;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.List;
+import org.hamcrest.Matcher;
+
+public abstract class ChromeCompilationBase extends CompilationTestBase {
+
+  static final String LIBRARY_JAR = "library.jar";
+  static final String PROGRAM_JAR = "program.jar";
+  static final String PROGUARD_CONFIG = "proguard.config";
+
+  final String base;
+
+  public ChromeCompilationBase(int version, boolean minimal) {
+    this.base =
+        "third_party/chrome/"
+            + (minimal ? "monochrome_public_minimal_apks/" : "")
+            + "chrome_"
+            + version
+            + "/";
+  }
+
+  protected List<Path> getKeepRuleFiles() {
+    ImmutableList.Builder<Path> builder = ImmutableList.builder();
+    builder.add(Paths.get(base).resolve(PROGUARD_CONFIG));
+    return builder.build();
+  }
+
+  protected List<Path> getLibraryFiles() throws IOException {
+    assert verifyNoLibraryJarsInProguardConfig();
+    List<Path> result = ImmutableList.of(Paths.get(base).resolve(LIBRARY_JAR));
+    assert result.stream().allMatch(path -> path.toFile().exists());
+    return result;
+  }
+
+  protected List<Path> getProgramFiles() throws IOException {
+    assert verifyNoInJarsInProguardConfig();
+    List<Path> result = ImmutableList.of(Paths.get(base).resolve(PROGRAM_JAR));
+    assert result.stream().allMatch(path -> path.toFile().exists());
+    return result;
+  }
+
+  private boolean verifyNoInJarsInProguardConfig() throws IOException {
+    return verifyAllLinesInKeepRuleFilesMatch(not(containsString("-injars")));
+  }
+
+  private boolean verifyNoLibraryJarsInProguardConfig() throws IOException {
+    return verifyAllLinesInKeepRuleFilesMatch(not(containsString("-libraryjars")));
+  }
+
+  private boolean verifyAllLinesInKeepRuleFilesMatch(Matcher<String> matcher) throws IOException {
+    for (Path keepRuleFile : getKeepRuleFiles()) {
+      for (String line : FileUtils.readAllLines(keepRuleFile)) {
+        assertThat(line, matcher);
+      }
+    }
+    return true;
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java b/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
index c4509c4..4a256bb 100644
--- a/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
+++ b/src/test/java/com/android/tools/r8/internal/LibrarySanitizer.java
@@ -17,7 +17,7 @@
 import java.util.List;
 import org.junit.rules.TemporaryFolder;
 
-class LibrarySanitizer {
+public class LibrarySanitizer {
 
   private final Path sanitizedLibrary;
   private final Path sanitizedPgConf;
@@ -26,12 +26,12 @@
   private final List<Path> programFiles = new ArrayList<>();
   private final List<Path> proguardConfigurationFiles = new ArrayList<>();
 
-  LibrarySanitizer(TemporaryFolder temp) {
+  public LibrarySanitizer(TemporaryFolder temp) {
     this.sanitizedLibrary = temp.getRoot().toPath().resolve("sanitized_lib.jar");
     this.sanitizedPgConf = temp.getRoot().toPath().resolve("sanitized.config");
   }
 
-  LibrarySanitizer assertSanitizedProguardConfigurationIsEmpty() throws IOException {
+  public LibrarySanitizer assertSanitizedProguardConfigurationIsEmpty() throws IOException {
     if (sanitizedPgConf.toFile().exists()) {
       List<String> lines = FileUtils.readAllLines(sanitizedPgConf);
       for (String line : lines) {
@@ -41,12 +41,12 @@
     return this;
   }
 
-  LibrarySanitizer addLibraryFiles(List<Path> libraryFiles) {
+  public LibrarySanitizer addLibraryFiles(List<Path> libraryFiles) {
     this.libraryFiles.addAll(libraryFiles);
     return this;
   }
 
-  LibrarySanitizer addProgramFiles(List<Path> programFiles) {
+  public LibrarySanitizer addProgramFiles(List<Path> programFiles) {
     this.programFiles.addAll(programFiles);
     return this;
   }
@@ -56,7 +56,7 @@
     return this;
   }
 
-  Path getSanitizedLibrary() {
+  public Path getSanitizedLibrary() {
     return sanitizedLibrary;
   }
 
@@ -64,7 +64,7 @@
     return sanitizedPgConf;
   }
 
-  LibrarySanitizer sanitize() throws IOException {
+  public LibrarySanitizer sanitize() throws IOException {
     ImmutableList.Builder<String> command =
         new ImmutableList.Builder<String>()
             .add("tools/sanitize_libraries.py")
diff --git a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
index e63b126..b48bec3 100644
--- a/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
+++ b/src/test/java/com/android/tools/r8/internal/YouTubeV1508TreeShakeJarVerificationTest.java
@@ -59,7 +59,6 @@
             .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
             .addKeepRuleFiles(getKeepRuleFiles())
             .addMainDexRuleFiles(getMainDexRuleFiles())
-            .allowCheckDiscardedErrors()
             .allowDiagnosticMessages()
             .allowUnusedProguardConfigurationRules()
             .setMinApi(AndroidApiLevel.H_MR2)
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
similarity index 62%
rename from src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
rename to src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
index 6088600..ddaf85f 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/YouTubeProtoRewritingTest.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ChromeProtoRewritingTest.java
@@ -12,44 +12,45 @@
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.TestParametersCollection;
-import com.android.tools.r8.internal.YouTubeCompilationBase;
+import com.android.tools.r8.internal.ChromeCompilationBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
 @RunWith(Parameterized.class)
-public class YouTubeProtoRewritingTest extends YouTubeCompilationBase {
-
-  private final TestParameters parameters;
+public class ChromeProtoRewritingTest extends ChromeCompilationBase {
 
   @Parameters(name = "{0}")
   public static TestParametersCollection data() {
-    return getTestParameters().withDexRuntimes().build();
+    return getTestParameters().withNoneRuntime().build();
   }
 
-  public YouTubeProtoRewritingTest(TestParameters parameters) {
-    super(14, 19);
-    this.parameters = parameters;
+  public ChromeProtoRewritingTest(TestParameters parameters) {
+    super(200430, false);
   }
 
-  @Ignore
   @Test
   public void test() throws Exception {
     assumeTrue(shouldRunSlowTests());
-
-    testForR8(parameters.getBackend())
+    testForR8(Backend.DEX)
+        .addProgramFiles(getProgramFiles())
+        .addLibraryFiles(getLibraryFiles())
         .addKeepRuleFiles(getKeepRuleFiles())
-        // Retain all protos.
-        .addKeepRules(keepAllProtosRule())
-        // Retain the signature of dynamicMethod() and newMessageInfo().
-        .addKeepRules(keepDynamicMethodSignatureRule(), keepNewMessageInfoSignatureRule())
+        .addKeepRules(
+            keepAllProtosRule(),
+            keepDynamicMethodSignatureRule(),
+            keepNewMessageInfoSignatureRule())
         .allowUnusedProguardConfigurationRules()
+        .enableProtoShrinking(false)
+        .setMinApi(AndroidApiLevel.N)
         .compile()
-        .inspect(
-            inspector ->
-                assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector));
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector);
   }
 }
diff --git a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
index a1ecab4..79ce685 100644
--- a/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
+++ b/src/test/java/com/android/tools/r8/internal/proto/ProtoShrinkingTestBase.java
@@ -79,7 +79,8 @@
 
   static String keepAllProtosRule() {
     return StringUtils.lines(
-        "-keep class * extends com.google.protobuf.GeneratedMessageLite { *; }");
+        "-if class * extends com.google.protobuf.GeneratedMessageLite",
+        "-keep,allowobfuscation class <1> { <init>(...); <fields>; }");
   }
 
   static String keepDynamicMethodSignatureRule() {
diff --git a/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
new file mode 100644
index 0000000..5596fc7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/internal/proto/YouTubeV1508ProtoRewritingTest.java
@@ -0,0 +1,67 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.internal.proto;
+
+import static com.android.tools.r8.ToolHelper.shouldRunSlowTests;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.assertRewrittenProtoSchemasMatch;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepAllProtosRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepDynamicMethodSignatureRule;
+import static com.android.tools.r8.internal.proto.ProtoShrinkingTestBase.keepNewMessageInfoSignatureRule;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.internal.LibrarySanitizer;
+import com.android.tools.r8.internal.YouTubeCompilationBase;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class YouTubeV1508ProtoRewritingTest extends YouTubeCompilationBase {
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public YouTubeV1508ProtoRewritingTest(TestParameters parameters) {
+    super(15, 8);
+  }
+
+  @Test
+  public void test() throws Exception {
+    assumeTrue(shouldRunSlowTests());
+
+    LibrarySanitizer librarySanitizer =
+        new LibrarySanitizer(temp)
+            .addProgramFiles(getProgramFiles())
+            .addLibraryFiles(getLibraryFiles())
+            .sanitize()
+            .assertSanitizedProguardConfigurationIsEmpty();
+
+    testForR8(Backend.DEX)
+        .addProgramFiles(getProgramFiles())
+        .addLibraryFiles(librarySanitizer.getSanitizedLibrary())
+        .addKeepRuleFiles(getKeepRuleFiles())
+        .addKeepRules(
+            keepAllProtosRule(),
+            keepDynamicMethodSignatureRule(),
+            keepNewMessageInfoSignatureRule())
+        .addMainDexRuleFiles(getMainDexRuleFiles())
+        .allowCheckDiscardedErrors(true)
+        .allowDiagnosticMessages()
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(AndroidApiLevel.H_MR2)
+        .compile()
+        .inspect(this::inspect);
+  }
+
+  private void inspect(CodeInspector inspector) throws Exception {
+    assertRewrittenProtoSchemasMatch(new CodeInspector(getProgramFiles()), inspector);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java
new file mode 100644
index 0000000..adba889
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/analysis/sideeffect/ArrayContentsDependOnEnvironmentTest.java
@@ -0,0 +1,72 @@
+// Copyright (c) 2019, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.analysis.sideeffect;
+
+import com.android.tools.r8.AssumeNoSideEffects;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ArrayContentsDependOnEnvironmentTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public ArrayContentsDependOnEnvironmentTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void test() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(ArrayContentsDependOnEnvironmentTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableAssumeNoSideEffectsAnnotations()
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("42");
+  }
+
+  static class TestClass {
+
+    static int f = 42;
+
+    public static void main(String[] args) {
+      // Trigger A.<clinit>(), this will set A.values to [42].
+      A.empty();
+      // Unset f, such that the below will read -1 if we failed to trigger class initialization.
+      f = -1;
+      // Print 42.
+      System.out.println(A.values[0]);
+    }
+  }
+
+  static class A {
+
+    static long[] values = new long[] {getFourtyTwo()};
+
+    @AssumeNoSideEffects
+    @NeverInline
+    static long getFourtyTwo() {
+      return TestClass.f;
+    }
+
+    static void empty() {
+      // Intentionally empty.
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
new file mode 100644
index 0000000..24894bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize.library;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BooleanParseBooleanTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public BooleanParseBooleanTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue(parameters.isDexRuntime());
+    testForD8()
+        .addProgramClasses(TestClass.class)
+        .release()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(TestClass.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+    assertThat(testClassSubject, isPresent());
+
+    MethodSubject testOptimizedMethodSubject =
+        testClassSubject.uniqueMethodWithName("testOptimized");
+    assertThat(testOptimizedMethodSubject, isPresent());
+    assertTrue(
+        testOptimizedMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeStatic)
+            .map(InstructionSubject::getMethod)
+            .map(DexMethod::toSourceString)
+            .noneMatch(method -> method.contains("parseBoolean")));
+
+    MethodSubject testNotOptimizedMethodSubject =
+        testClassSubject.uniqueMethodWithName("testNotOptimized");
+    assertThat(testNotOptimizedMethodSubject, isPresent());
+    assertEquals(
+        1,
+        testNotOptimizedMethodSubject
+            .streamInstructions()
+            .filter(InstructionSubject::isInvokeStatic)
+            .map(InstructionSubject::getMethod)
+            .map(DexMethod::toSourceString)
+            .filter(method -> method.contains("parseBoolean"))
+            .count());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      testOptimized();
+      testNotOptimized();
+    }
+
+    @NeverInline
+    static void testOptimized() {
+      System.out.println(Boolean.parseBoolean("true"));
+      System.out.println(Boolean.parseBoolean("tRuE"));
+      System.out.println(Boolean.parseBoolean("TRUE"));
+      System.out.println(Boolean.parseBoolean("false"));
+      System.out.println(Boolean.parseBoolean("fAlSe"));
+      System.out.println(Boolean.parseBoolean("FALSE"));
+    }
+
+    @NeverInline
+    static void testNotOptimized() {
+      System.out.println(Boolean.parseBoolean(unknown()));
+    }
+
+    static String unknown() {
+      return System.currentTimeMillis() >= 0 ? "true" : "false";
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
new file mode 100644
index 0000000..11c47de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteAnnotationTest.java
@@ -0,0 +1,185 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isRenamed;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmTypeAliasSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import kotlinx.metadata.KmAnnotation;
+import kotlinx.metadata.KmAnnotationArgument;
+import kotlinx.metadata.KmAnnotationArgument.ArrayValue;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteAnnotationTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED =
+      StringUtils.lines(
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Bar",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "UP",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "LEFT",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "RIGHT",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "DOWN",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "UP",
+          "Top most",
+          "class com.android.tools.r8.kotlin.metadata.annotation_lib.Foo",
+          "DOWN",
+          "com.android.tools.r8.kotlin.metadata.annotation_lib.Foo");
+  private static final String PKG_LIB = PKG + ".annotation_lib";
+  private static final String PKG_APP = PKG + ".annotation_app";
+  private static final String FOO_ORIGINAL_NAME = PKG_LIB + ".Foo";
+  private static final String FOO_FINAL_NAME = "a.b.c";
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewriteAnnotationTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(libJars.get(targetVersion))
+            /// Keep the annotations
+            .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassAndEnum")
+            .addKeepClassAndMembersRules(PKG_LIB + ".AnnoWithClassArr")
+            .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** kept(); } ")
+            .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** message(); } ")
+            // .addKeepRules("-keep class " + PKG_LIB + ".Nested { *** kept; *** getKept(); } ")
+            // Keep Foo but rename to test arguments
+            .addKeepClassAndMembersRulesWithAllowObfuscation(FOO_ORIGINAL_NAME)
+            .addApplyMapping(FOO_ORIGINAL_NAME + " -> " + FOO_FINAL_NAME + ":")
+            // Keep Direction but rename the enum
+            .addKeepClassAndMembersRules(PKG_LIB + ".Direction")
+            // Keep Bar and Baz and Quux because we are directly reflecting on them
+            .addKeepClassAndMembersRules(PKG_LIB + ".Bar")
+            .addKeepClassAndMembersRules(PKG_LIB + ".Baz")
+            .addKeepClassAndMembersRules(PKG_LIB + ".Quux")
+            // Keep the static class for the type alias
+            .addKeepClassAndMembersRules(PKG_LIB + ".LibKt")
+            .addKeepAttributes(
+                ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS,
+                ProguardKeepAttributes.RUNTIME_VISIBLE_PARAMETER_ANNOTATIONS,
+                ProguardKeepAttributes.RUNTIME_VISIBLE_TYPE_ANNOTATIONS)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(
+            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
+        .addProgramFiles(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED.replace(FOO_ORIGINAL_NAME, FOO_FINAL_NAME));
+  }
+
+  private void inspect(CodeInspector inspector) {
+    // Assert that foo is renamed.
+    ClassSubject foo = inspector.clazz(PKG_LIB + ".Foo");
+    assertThat(foo, isRenamed());
+    assertEquals(FOO_FINAL_NAME, foo.getFinalName());
+    // Assert that bar exists and is not renamed.
+    ClassSubject bar = inspector.clazz(PKG_LIB + ".Bar");
+    assertThat(bar, isPresent());
+    assertThat(bar, isNotRenamed());
+    // Check that the annotation type on the type alias has been renamed
+    inspectTypeAliasAnnotation(inspector, foo, bar);
+  }
+
+  private void inspectTypeAliasAnnotation(
+      CodeInspector inspector, ClassSubject foo, ClassSubject bar) {
+    ClassSubject libKt = inspector.clazz(PKG_LIB + ".LibKt");
+    assertThat(libKt, isPresent());
+    assertThat(libKt.getKmPackage(), isPresent());
+    KmTypeAliasSubject qux = libKt.getKmPackage().kmTypeAliasWithUniqueName("Qux");
+    assertThat(qux, isPresent());
+    assertEquals(1, qux.annotations().size());
+    KmAnnotation annotation = qux.annotations().get(0);
+    assertEquals(
+        DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB) + "/AnnoWithClassArr",
+        annotation.getClassName());
+    Map<String, KmAnnotationArgument<?>> arguments = annotation.getArguments();
+    assertEquals(1, arguments.size());
+    ArrayValue classes = (ArrayValue) arguments.get("classes");
+    assertEquals(
+        "KClassValue(value=" + foo.getFinalBinaryName() + ")",
+        classes.getValue().get(0).toString());
+    assertEquals(
+        "KClassValue(value=" + bar.getFinalBinaryName() + ")",
+        classes.getValue().get(1).toString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
index c6dd4fd..1ebdddb 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteCrossinlineAnonFunctionTest.java
@@ -76,7 +76,6 @@
     Path libJar =
         testForR8(parameters.getBackend())
             .addProgramFiles(libJars.get(targetVersion))
-            // Allow renaming A to ensure that we rename in the flexible upper bound type.
             .addKeepAllClassesRule()
             .addKeepAllAttributes()
             .compile()
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
index 50007fb..badbf02 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDelegatedPropertyTest.java
@@ -5,16 +5,14 @@
 package com.android.tools.r8.kotlin.metadata;
 
 import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.MatcherAssert.assertThat;
-import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.TestParameters;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
-import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
 import com.android.tools.r8.utils.DescriptorUtils;
 import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import java.nio.file.Path;
 import java.util.Collection;
 import java.util.HashMap;
@@ -27,33 +25,15 @@
 @RunWith(Parameterized.class)
 public class MetadataRewriteDelegatedPropertyTest extends KotlinMetadataTestBase {
 
-  private static final String PKG_LIB = PKG + ".delegated_property_lib";
   private static final String PKG_APP = PKG + ".delegated_property_app";
   private static final String EXPECTED_MAIN =
       StringUtils.lines(
-          "foo has been assigned to 'customDelegate' in"
-              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
-          "foo has been read in CustomDelegate from 'customDelegate' in"
-              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
-          "foo",
-          "read-only has been read in CustomReadOnlyDelegate from 'customReadOnlyDelegate' in"
-              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
-          "read-only",
-          "Generating lazy string",
-          "42",
-          "Hello World!",
-          "Hello World!",
-          "Jane Doe",
-          "42",
-          "Checking property for image",
-          "Checking property for text",
-          "image_id",
-          "text_id");
-  private static final String EXPECTED_REFLECT =
-      StringUtils.lines(
-          "foo has been assigned to 'customDelegate' in"
-              + " com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates",
-          "foo");
+          "Initial string has been read in CustomDelegate from 'x'",
+          "Initial string has been read in CustomDelegate from 'x'",
+          "New value has been read in CustomDelegate from 'x'",
+          "New value has been read in CustomDelegate from 'x'",
+          "null",
+          "New value has been read in CustomDelegate from 'x'");
 
   @Parameterized.Parameters(name = "{0} target: {1}")
   public static Collection<Object[]> data() {
@@ -68,107 +48,47 @@
   }
 
   private final TestParameters parameters;
-  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private static Map<KotlinTargetVersion, Path> jars = new HashMap<>();
 
   @BeforeClass
-  public static void createLibJar() throws Exception {
+  public static void createJar() throws Exception {
     for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
       Path baseLibJar =
           kotlinc(KOTLINC, targetVersion)
               .addSourceFiles(
-                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
               .compile();
-      libJars.put(targetVersion, baseLibJar);
+      jars.put(targetVersion, baseLibJar);
     }
   }
 
   @Test
   public void smokeTest() throws Exception {
-    Path libJar = libJars.get(targetVersion);
-    Path output =
-        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
-            .addClasspathFiles(libJar)
-            .addSourceFiles(
-                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compile();
     testForJvm()
-        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
-        .addClasspath(output)
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar())
+        .addClasspath(jars.get(targetVersion))
         .run(parameters.getRuntime(), PKG_APP + ".MainKt")
         .assertSuccessWithOutput(EXPECTED_MAIN);
   }
 
-  @Test
-  public void smokeTestReflect() throws Exception {
-    Path libJar = libJars.get(targetVersion);
-    Path output =
-        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
-            .addClasspathFiles(libJar)
-            .addSourceFiles(
-                getKotlinFileInTest(
-                    DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compile();
-    testForJvm()
-        .addRunClasspathFiles(
-            ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar(), libJar)
-        .addClasspath(output)
-        .run(parameters.getRuntime(), PKG_APP + ".Main_reflectKt")
-        .assertSuccessWithOutput(EXPECTED_REFLECT);
-  }
 
   @Test
   public void testMetadataForLib() throws Exception {
-    Path libJar =
+    Path outputJar =
         testForR8(parameters.getBackend())
             .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
-            .addProgramFiles(libJars.get(targetVersion))
-            .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
-            .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
-            .addKeepRules("-keep class " + PKG_LIB + ".User { *; }")
-            .addKeepRules("-keep class " + PKG_LIB + ".ProvidedDelegates { *; }")
+            .addProgramFiles(jars.get(targetVersion))
+            .addKeepAllClassesRule()
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
             .compile()
-            // TODO(b/157988734): When we start modeling localDelegatedProperties, inspect the code.
+            .inspect(
+                inspector ->
+                    assertEqualMetadata(new CodeInspector(jars.get(targetVersion)), inspector))
             .writeToZip();
-    Path output =
-        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
-            .addClasspathFiles(libJar)
-            .addSourceFiles(
-                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compile();
     testForJvm()
-        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
-        .addClasspath(output)
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), ToolHelper.getKotlinReflectJar())
+        .addClasspath(outputJar)
         .run(parameters.getRuntime(), PKG_APP + ".MainKt")
         .assertSuccessWithOutput(EXPECTED_MAIN);
   }
-
-  @Test
-  public void testMetadataForReflect() throws Exception {
-    Path libJar =
-        testForR8(parameters.getBackend())
-            .addClasspathFiles(ToolHelper.getKotlinStdlibJar())
-            .addProgramFiles(libJars.get(targetVersion))
-            .addKeepRules("-keep class " + PKG_LIB + ".Delegates { *; }")
-            .addKeepRules("-keep class " + PKG_LIB + ".Resource { *; }")
-            .addKeepRules("-keep class " + PKG_LIB + ".CustomDelegate { *; }")
-            .compile()
-            .writeToZip();
-    ProcessResult result =
-        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
-            .addClasspathFiles(libJar)
-            .addSourceFiles(
-                getKotlinFileInTest(
-                    DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main_reflect"))
-            .setOutputPath(temp.newFolder().toPath())
-            .compileRaw();
-    assertEquals(1, result.exitCode);
-    assertThat(
-        result.stderr,
-        containsString(
-            "unsupported [reference to the synthetic extension property for a Java get/set"
-                + " method]"));
-  }
 }
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
new file mode 100644
index 0000000..788e64b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteDoNotEmitValuesIfEmpty.java
@@ -0,0 +1,81 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.graph.DexAnnotationElement;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.codeinspector.AnnotationSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FoundClassSubject;
+import com.google.common.collect.Sets;
+import java.util.Collection;
+import java.util.Set;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteDoNotEmitValuesIfEmpty extends KotlinMetadataTestBase {
+
+  private final Set<String> nullableFieldKeys = Sets.newHashSet("pn", "xs", "xi");
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  private final TestParameters parameters;
+
+  public MetadataRewriteDoNotEmitValuesIfEmpty(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testKotlinStdLib() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramFiles(ToolHelper.getKotlinStdlibJar())
+        .setMinApi(parameters.getApiLevel())
+        .addKeepAllClassesRule()
+        .addKeepKotlinMetadata()
+        .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+        .compile()
+        .inspect(this::inspectEmptyValuesAreNotPresent);
+  }
+
+  private void inspectEmptyValuesAreNotPresent(CodeInspector inspector) {
+    boolean seenNullableField = false;
+    boolean seenMetadataWithoutNullableField = false;
+    for (FoundClassSubject clazz : inspector.allClasses()) {
+      AnnotationSubject annotation = clazz.annotation("kotlin.Metadata");
+      if (annotation.isPresent()) {
+        boolean seenNullableFieldForAnnotation = false;
+        for (DexAnnotationElement element : annotation.getAnnotation().elements) {
+          if (nullableFieldKeys.contains(element.name.toString())) {
+            if (element.value.isDexValueInt()) {
+              assertNotEquals(0, element.value.asDexValueInt().value);
+            } else {
+              String value = element.value.asDexValueString().value.toString();
+              assertNotEquals("", value);
+            }
+            seenNullableField = true;
+            seenNullableFieldForAnnotation = true;
+          }
+        }
+        seenMetadataWithoutNullableField |= !seenNullableFieldForAnnotation;
+      }
+    }
+    assertTrue(seenNullableField);
+    assertTrue(seenMetadataWithoutNullableField);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
new file mode 100644
index 0000000..2d3834b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteJvmStaticTest.java
@@ -0,0 +1,156 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.kotlin.metadata.jvmstatic_app.MainJava;
+import com.android.tools.r8.shaking.ProguardKeepAttributes;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmFunctionSubject;
+import com.android.tools.r8.utils.codeinspector.KmPropertySubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewriteJvmStaticTest extends KotlinMetadataTestBase {
+
+  private static final String EXPECTED =
+      StringUtils.lines("Hello, Hello", "Calling func...", "Foo");
+  private static final String PKG_LIB = PKG + ".jvmstatic_lib";
+  private static final String PKG_APP = PKG + ".jvmstatic_app";
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public MetadataRewriteJvmStaticTest(TestParameters parameters) {
+    // We are testing static methods on interfaces which requires java 8.
+    super(KotlinTargetVersion.JAVA_8);
+    this.parameters = parameters;
+  }
+
+  private static Path kotlincLibJar = Paths.get("");
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    kotlincLibJar =
+        kotlinc(KOTLINC, KotlinTargetVersion.JAVA_8)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+            .compile();
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(kotlincLibJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), kotlincLibJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void smokeTestJava() throws Exception {
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), kotlincLibJar)
+        .addProgramClassFileData(MainJava.dump())
+        .run(parameters.getRuntime(), MainJava.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadata() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(kotlincLibJar)
+            .addKeepAllClassesRule()
+            .addKeepAttributes(ProguardKeepAttributes.RUNTIME_VISIBLE_ANNOTATIONS)
+            .compile()
+            .inspect(this::inspect)
+            .writeToZip();
+    testKotlin(libJar);
+    testJava(libJar);
+  }
+
+  private void testKotlin(Path libJar) throws Exception {
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void testJava(Path libJar) throws Exception {
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addProgramClassFileData(MainJava.dump())
+        .run(parameters.getRuntime(), MainJava.class)
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  private void inspect(CodeInspector inspector) {
+    inspectLib(inspector);
+    inspectInterfaceWithCompanion(inspector);
+  }
+
+  private void inspectLib(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(PKG_LIB + ".Lib");
+    assertThat(clazz, isPresent());
+    KmClassSubject kmClass = clazz.getKmClass();
+    assertThat(kmClass, isPresent());
+    KmFunctionSubject staticFun = kmClass.kmFunctionWithUniqueName("staticFun");
+    assertThat(staticFun, isPresent());
+    assertEquals("staticFun(Lkotlin/jvm/functions/Function0;)V", staticFun.signature().asString());
+    KmPropertySubject staticProp = kmClass.kmPropertyWithUniqueName("staticProp");
+    assertThat(staticProp, isPresent());
+  }
+
+  private void inspectInterfaceWithCompanion(CodeInspector inspector) {
+    ClassSubject itf = inspector.clazz(PKG_LIB + ".InterfaceWithCompanion");
+    assertThat(itf, isPresent());
+    MethodSubject greet = itf.uniqueMethodWithName("greet");
+    assertThat(greet, isPresent());
+
+    ClassSubject itfCompanion = inspector.clazz(PKG_LIB + ".InterfaceWithCompanion$Companion");
+    assertThat(itfCompanion, isPresent());
+    KmClassSubject kmClass = itfCompanion.getKmClass();
+    KmFunctionSubject greetKm = kmClass.kmFunctionWithUniqueName("greet");
+    assertThat(greetKm, isPresent());
+    assertEquals("greet(Ljava/lang/String;)V", greetKm.signature().asString());
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
new file mode 100644
index 0000000..39e9d12
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewritePrunedObjectsTest.java
@@ -0,0 +1,117 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata;
+
+import static com.android.tools.r8.KotlinCompilerTool.KOTLINC;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.KotlinTargetVersion;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class MetadataRewritePrunedObjectsTest extends KotlinMetadataTestBase {
+
+  private final String EXPECTED = StringUtils.lines("42");
+  private static final String PKG_LIB = PKG + ".pruned_lib";
+  private static final String PKG_APP = PKG + ".pruned_app";
+
+  private static Map<KotlinTargetVersion, Path> libJars = new HashMap<>();
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0} target: {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withCfRuntimes().build(), KotlinTargetVersion.values());
+  }
+
+  public MetadataRewritePrunedObjectsTest(
+      TestParameters parameters, KotlinTargetVersion targetVersion) {
+    super(targetVersion);
+    this.parameters = parameters;
+  }
+
+  @BeforeClass
+  public static void createLibJar() throws Exception {
+    for (KotlinTargetVersion targetVersion : KotlinTargetVersion.values()) {
+      Path baseLibJar =
+          kotlinc(KOTLINC, targetVersion)
+              .addSourceFiles(
+                  getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_LIB), "lib"))
+              .compile();
+      libJars.put(targetVersion, baseLibJar);
+    }
+  }
+
+  @Test
+  public void smokeTest() throws Exception {
+    Path libJar = libJars.get(targetVersion);
+    Path output =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compile();
+    testForJvm()
+        .addRunClasspathFiles(ToolHelper.getKotlinStdlibJar(), libJar)
+        .addClasspath(output)
+        .run(parameters.getRuntime(), PKG_APP + ".MainKt")
+        .assertSuccessWithOutput(EXPECTED);
+  }
+
+  @Test
+  public void testMetadataForLib() throws Exception {
+    Path libJar =
+        testForR8(parameters.getBackend())
+            .addProgramFiles(libJars.get(targetVersion))
+            .addKeepRules("-keep class " + PKG_LIB + ".Sub { *** kept(); }")
+            .addKeepRuntimeVisibleAnnotations()
+            .noMinification()
+            .compile()
+            .inspect(this::checkPruned)
+            .writeToZip();
+    // TODO(b/158766557): This should work.
+    ProcessResult mainResult =
+        kotlinc(parameters.getRuntime().asCf(), KOTLINC, targetVersion)
+            .addClasspathFiles(libJar)
+            .addSourceFiles(
+                getKotlinFileInTest(DescriptorUtils.getBinaryNameFromJavaType(PKG_APP), "main"))
+            .setOutputPath(temp.newFolder().toPath())
+            .compileRaw();
+    assertEquals(1, mainResult.exitCode);
+    assertThat(
+        mainResult.stderr,
+        containsString("cannot access 'com.android.tools.r8.kotlin.metadata.pruned_lib.Base'"));
+  }
+
+  private void checkPruned(CodeInspector inspector) {
+    ClassSubject base = inspector.clazz(PKG_LIB + ".Base");
+    assertThat(base, not(isPresent()));
+    ClassSubject sub = inspector.clazz(PKG_LIB + ".Sub");
+    assertThat(sub, isPresent());
+    KmClassSubject kmClass = sub.getKmClass();
+    assertThat(kmClass, isPresent());
+    // TODO(b/158766557): Assert that things are indeed removed.
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
new file mode 100644
index 0000000..d7ed237
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_app/main.kt
@@ -0,0 +1,47 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.annotation_app
+
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassAndEnum
+import com.android.tools.r8.kotlin.metadata.annotation_lib.AnnoWithClassArr
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Bar
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Baz
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Nested
+import com.android.tools.r8.kotlin.metadata.annotation_lib.Quux
+import kotlin.reflect.full.memberProperties
+import kotlin.reflect.full.primaryConstructor
+import kotlin.reflect.full.valueParameters
+
+fun main() {
+  Bar::class.primaryConstructor?.annotations?.get(0)?.printAnnoWithClassArr()
+  Baz::class.annotations.get(0).printAnnoWithClassArr()
+  Baz::class.annotations.get(1).printAnnoWithClassAndEnum()
+  Baz::prop.annotations.get(0).printAnnoWithClassAndEnum()
+  Baz::baz.annotations.get(0).printAnnoWithClassAndEnum()
+  Baz::baz.valueParameters.get(0).annotations.get(0).printAnnoWithClassAndEnum()
+  // We cannot reflect on annotations on typealiases:
+  // https://youtrack.jetbrains.com/issue/KT-21489
+  Quux::methodWithTypeAnnotations
+      .returnType.arguments.get(0).type?.annotations?.get(0)?.printAnnoWithClassAndEnum()
+  val nested = Quux::methodWithNestedAnnotations.returnType.arguments[0].type?.annotations?.get(0) as Nested
+  println(nested.message)
+  nested.kept.printAnnoWithClassAndEnum()
+  if (nested::class::memberProperties.get().any { it.name.equals("notKept") }) {
+    println("com.android.tools.r8.kotlin.metadata.annotation_lib.Foo")
+  } else {
+    println("a.b.c")
+  }
+}
+
+fun Annotation.printAnnoWithClassArr() {
+  val annoWithClassArr = this as AnnoWithClassArr
+  annoWithClassArr.classes.forEach { println(it) }
+}
+
+fun Annotation.printAnnoWithClassAndEnum() {
+  val annoWithClassAndEnum = this as AnnoWithClassAndEnum
+  println(annoWithClassAndEnum.clazz)
+  println(annoWithClassAndEnum.direction)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
new file mode 100644
index 0000000..0c6ff6c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/annotation_lib/lib.kt
@@ -0,0 +1,61 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.annotation_lib
+
+import kotlin.reflect.KClass
+
+enum class Direction {
+  UP, RIGHT, DOWN, LEFT
+}
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION,
+        AnnotationTarget.VALUE_PARAMETER,
+        AnnotationTarget.PROPERTY,
+        AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassAndEnum(val clazz : KClass<*>, val direction : Direction)
+
+@Target(AnnotationTarget.CLASS, AnnotationTarget.CONSTRUCTOR, AnnotationTarget.TYPEALIAS,
+        AnnotationTarget.TYPE)
+@Retention(AnnotationRetention.RUNTIME)
+annotation class AnnoWithClassArr(val classes: Array<KClass<*>>)
+
+class Foo
+
+class Bar @AnnoWithClassArr([Foo::class]) constructor()
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+@AnnoWithClassAndEnum(Foo::class, Direction.UP) class Baz {
+
+  @AnnoWithClassAndEnum(Foo::class, Direction.LEFT) val prop : Int = 0
+
+  @AnnoWithClassAndEnum(Foo::class, Direction.RIGHT) fun baz(@AnnoWithClassAndEnum(Foo::class, Direction.DOWN) foo: Int): Int {
+    return 1
+  }
+}
+
+@AnnoWithClassArr([Foo::class, Bar::class])
+typealias Qux = Foo
+
+annotation class AnnoNotKept
+
+@Target(AnnotationTarget.TYPE)
+annotation class Nested(
+  val message: String,
+  val kept: AnnoWithClassAndEnum,
+  val notKept: AnnoNotKept
+)
+
+class Quux {
+
+  fun methodWithTypeAnnotations() : Array<@AnnoWithClassAndEnum(Foo::class, Direction.UP) Int> {
+    return arrayOf(1)
+  }
+
+  fun methodWithNestedAnnotations() : Array<@Nested("Top most", AnnoWithClassAndEnum(Foo::class, Direction.DOWN), AnnoNotKept()) Int> {
+    return arrayOf(1)
+  }
+}
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
index 7bfd637..a35754f 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main.kt
@@ -3,29 +3,57 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.kotlin.metadata.delegated_property_app
 
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.ProvidedDelegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.User
+import kotlin.reflect.KMutableProperty1
+import kotlin.reflect.KProperty
+import kotlin.reflect.full.declaredMemberProperties
+import kotlin.reflect.jvm.isAccessible
+
+class Resource(private var s : String = "Initial string") {
+
+  override fun toString(): String {
+    return s;
+  }
+}
+
+object CustomDelegate {
+
+  private var resource : Resource = Resource()
+
+  operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
+    println("$resource has been read in CustomDelegate from '${property.name}'")
+    return resource;
+  }
+
+  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
+    println("$value has been assigned to '${property.name}'")
+    this.resource = resource;
+  }
+}
+
+open class Base {
+
+  fun doSomethingOnBarRef() : Resource {
+    var x by CustomDelegate
+    val propRef = x.javaClass.kotlin.declaredMemberProperties.first() as KMutableProperty1<Resource, String>
+    propRef.isAccessible = true
+    propRef.set(x, "New value")
+    propRef.get(x)
+    // Getting the delegate is not yet supported and will return null. We are printing the value
+    // allowing us to observe if the behavior changes.
+    println(propRef.getDelegate(x))
+    return x
+  }
+}
+
+object Impl : Base() {
+  operator fun invoke(): Impl {
+    return this
+  }
+}
+
 
 fun main() {
-
-  val delegates = Delegates()
-  delegates.customDelegate = Resource("foo");
-  println(delegates.customDelegate)
-  println(delegates.customReadOnlyDelegate)
-  println(delegates.lazyString)
-  println(delegates.localDelegatedProperties { Resource("Hello World!") })
-
-  val user = User(mapOf(
-    "name" to "Jane Doe",
-    "age"  to 42
-  ))
-
-  println(user.name)
-  println(user.age)
-
-  val providedDelegates = ProvidedDelegates()
-  println(providedDelegates.image)
-  println(providedDelegates.text)
+  val impl = Impl()
+  impl.doSomethingOnBarRef()
 }
+
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
deleted file mode 100644
index 0369a86..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_app/main_reflect.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-package com.android.tools.r8.kotlin.metadata.delegated_property_app
-
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.CustomDelegate
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Delegates
-import com.android.tools.r8.kotlin.metadata.delegated_property_lib.Resource
-import kotlin.reflect.KMutableProperty0
-import kotlin.reflect.jvm.isAccessible
-
-fun main() {
-  val delegates = Delegates()
-  delegates.customDelegate = Resource("foo");
-  println(delegates::customDelegate.getResource())
-}
-
-inline fun KMutableProperty0<*>.getResource(): Resource {
-  isAccessible = true
-  return (getDelegate() as CustomDelegate).resource
-}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
deleted file mode 100644
index 54c58df..0000000
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/delegated_property_lib/lib.kt
+++ /dev/null
@@ -1,88 +0,0 @@
-// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-package com.android.tools.r8.kotlin.metadata.delegated_property_lib
-
-import kotlin.properties.ReadOnlyProperty
-import kotlin.reflect.KProperty
-
-class Resource(private var s : String = "") {
-
-  override fun toString(): String {
-    return s;
-  }
-}
-
-class CustomDelegate(var resource: Resource = Resource()) {
-
-  operator fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
-    println("$resource has been read in CustomDelegate from '" +
-            "${property.name}' in ${thisRef?.javaClass?.typeName}")
-    return resource;
-  }
-
-  operator fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
-    println("$value has been assigned to '${property.name}'" +
-            " in ${thisRef?.javaClass?.typeName}")
-    this.resource = value
-  }
-}
-
-class CustomReadOnlyDelegate(private var resource : Resource = Resource("read-only"))
-  : ReadOnlyProperty<Any?, Resource> {
-  override fun getValue(thisRef: Any?, property: KProperty<*>): Resource {
-    println("$resource has been read in CustomReadOnlyDelegate" +
-            " from '${property.name}' in ${thisRef?.javaClass?.typeName}")
-    return resource;
-  }
-}
-
-class Delegates {
-
-  var customDelegate : Resource by CustomDelegate()
-  val customReadOnlyDelegate : Resource by CustomReadOnlyDelegate()
-  val lazyString : String by lazy {
-    println("Generating lazy string")
-    "42"
-  }
-
-  fun localDelegatedProperties(compute: () -> Resource) : Resource {
-    val foo by lazy(compute)
-    println(foo)
-    return foo
-  }
-}
-class User(val map : Map<String, Any?>) {
-  val name : String by map
-  val age: Int by map
-}
-
-class ResourceDelegate(val r : Resource): ReadOnlyProperty<ProvidedDelegates, Resource> {
-  override fun getValue(thisRef: ProvidedDelegates, property: KProperty<*>): Resource {
-    return r
-  }
-}
-
-class ResourceLoader(val id: Resource) {
-  operator fun provideDelegate(
-    thisRef: ProvidedDelegates,
-    prop: KProperty<*>
-  ): ReadOnlyProperty<ProvidedDelegates, Resource> {
-    checkProperty(prop.name)
-    return ResourceDelegate(id)
-  }
-
-  private fun checkProperty(name: String) {
-    println("Checking property for " + name)
-  }
-}
-
-class ProvidedDelegates {
-  fun bindResource(id: String): ResourceLoader {
-    return ResourceLoader(Resource(id))
-  }
-
-  val image by bindResource("image_id")
-  val text by bindResource("text_id")
-}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java
new file mode 100644
index 0000000..cfae3ef
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava.java
@@ -0,0 +1,130 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.jvmstatic_app;
+
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Handle;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+import org.objectweb.asm.Type;
+
+public class MainJava implements Opcodes {
+
+  // The java code cannot reference the kotlin-code when running in gradle, so we have it here
+  // as a dump.
+
+  // public static void main(String[] args) {
+  //   InterfaceWithCompanion.greet("Hello");
+  //   Lib.staticFun(() -> true);
+  //   Lib.setStaticProp("Foo");
+  //   System.out.println(Lib.getStaticProp());
+  // }
+
+  public static byte[] dump() {
+
+    ClassWriter classWriter = new ClassWriter(0);
+    MethodVisitor methodVisitor;
+
+    classWriter.visit(
+        V1_8,
+        ACC_PUBLIC | ACC_SUPER,
+        "com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava",
+        null,
+        "java/lang/Object",
+        null);
+
+    classWriter.visitInnerClass(
+        "java/lang/invoke/MethodHandles$Lookup",
+        "java/lang/invoke/MethodHandles",
+        "Lookup",
+        ACC_PUBLIC | ACC_FINAL | ACC_STATIC);
+
+    {
+      methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitVarInsn(ALOAD, 0);
+      methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(1, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+      methodVisitor.visitCode();
+      methodVisitor.visitLdcInsn("Hello");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/InterfaceWithCompanion",
+          "greet",
+          "(Ljava/lang/String;)V",
+          true);
+      methodVisitor.visitInvokeDynamicInsn(
+          "invoke",
+          "()Lkotlin/jvm/functions/Function0;",
+          new Handle(
+              Opcodes.H_INVOKESTATIC,
+              "java/lang/invoke/LambdaMetafactory",
+              "metafactory",
+              "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;",
+              false),
+          new Object[] {
+            Type.getType("()Ljava/lang/Object;"),
+            new Handle(
+                Opcodes.H_INVOKESTATIC,
+                "com/android/tools/r8/kotlin/metadata/jvmstatic_app/MainJava",
+                "lambda$main$0",
+                "()Ljava/lang/Boolean;",
+                false),
+            Type.getType("()Ljava/lang/Boolean;")
+          });
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+          "staticFun",
+          "(Lkotlin/jvm/functions/Function0;)V",
+          false);
+      methodVisitor.visitLdcInsn("Foo");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+          "setStaticProp",
+          "(Ljava/lang/String;)V",
+          false);
+      methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC,
+          "com/android/tools/r8/kotlin/metadata/jvmstatic_lib/Lib",
+          "getStaticProp",
+          "()Ljava/lang/String;",
+          false);
+      methodVisitor.visitMethodInsn(
+          INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
+      methodVisitor.visitInsn(RETURN);
+      methodVisitor.visitMaxs(2, 1);
+      methodVisitor.visitEnd();
+    }
+    {
+      methodVisitor =
+          classWriter.visitMethod(
+              ACC_PRIVATE | ACC_STATIC | ACC_SYNTHETIC,
+              "lambda$main$0",
+              "()Ljava/lang/Boolean;",
+              null,
+              null);
+      methodVisitor.visitCode();
+      methodVisitor.visitInsn(ICONST_1);
+      methodVisitor.visitMethodInsn(
+          INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
+      methodVisitor.visitInsn(ARETURN);
+      methodVisitor.visitMaxs(1, 0);
+      methodVisitor.visitEnd();
+    }
+    classWriter.visitEnd();
+
+    return classWriter.toByteArray();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt
new file mode 100644
index 0000000..6bd1218
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_app/main.kt
@@ -0,0 +1,15 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.jvmstatic_app
+
+import com.android.tools.r8.kotlin.metadata.jvmstatic_lib.InterfaceWithCompanion
+import com.android.tools.r8.kotlin.metadata.jvmstatic_lib.Lib
+
+fun main() {
+  InterfaceWithCompanion.greet("Hello")
+  Lib.staticFun { true }
+  Lib.staticProp = "Foo"
+  println(Lib.staticProp)
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt
new file mode 100644
index 0000000..cc0f1fb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/jvmstatic_lib/lib.kt
@@ -0,0 +1,25 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.jvmstatic_lib
+
+interface InterfaceWithCompanion {
+  companion object {
+    @JvmStatic fun greet(username: String) {
+      println("Hello, $username")
+    }
+  }
+}
+
+object Lib {
+
+  @JvmStatic
+  fun staticFun(func : () -> Boolean) {
+    println("Calling func...")
+    func()
+  }
+
+  @JvmStatic
+  var staticProp : String = ""
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
new file mode 100644
index 0000000..fb35a60
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_app/main.kt
@@ -0,0 +1,11 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.pruned_app
+
+import com.android.tools.r8.kotlin.metadata.pruned_lib.Sub
+
+fun main() {
+  println(Sub().kept())
+}
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
new file mode 100644
index 0000000..3ceaa4d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/pruned_lib/lib.kt
@@ -0,0 +1,20 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.kotlin.metadata.pruned_lib
+
+// The Base class will be removed during
+open class Base
+
+class Sub : Base() {
+
+  fun notKept() : Boolean {
+    return true
+  }
+
+  fun kept() : Int {
+    return 42
+  }
+}
+
diff --git a/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java b/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java
new file mode 100644
index 0000000..e07f8f1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b158432019/StaticizerSyntheticUseTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.regress.b158432019;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class StaticizerSyntheticUseTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public StaticizerSyntheticUseTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testRuntime() throws Exception {
+    testForRuntime(parameters)
+        .addProgramClasses(Singleton.class, Main.class, Sam.class, A.class)
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Foo", "0");
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters.getBackend())
+        .addProgramClasses(Singleton.class, Main.class, Sam.class, A.class)
+        .addKeepClassAndMembersRules(Main.class)
+        .setMinApi(parameters.getApiLevel())
+        .noMinification()
+        .enableInliningAnnotations()
+        .enableNeverClassInliningAnnotations()
+        .run(parameters.getRuntime(), Main.class)
+        .assertSuccessWithOutputLines("Foo", "Foo", "0");
+  }
+
+  @NeverClassInline
+  public static class Singleton {
+
+    public static final Singleton singleton = new Singleton();
+
+    @NeverInline
+    public void foo() {
+      System.out.println("Foo");
+    }
+  }
+
+  public interface Sam<T> {
+    T foo(boolean bar);
+  }
+
+  public static class A {
+
+    private int instanceVar = 0;
+
+    public void caller() {
+      // Ensure that the Singleton.foo method is processed to have the generated lambda being
+      // processed - due to all callees being processed.
+      otherChainFirst();
+      Sam<Integer> f =
+          b -> {
+            if (b && instanceVar == 0) {
+              Singleton.singleton.foo();
+            }
+            return instanceVar;
+          };
+      System.out.println(f.foo(System.nanoTime() > 0));
+    }
+
+    public void otherChainFirst() {
+      otherChainSecond();
+    }
+
+    public void otherChainSecond() {
+      Singleton.singleton.foo();
+    }
+  }
+
+  public static class Main {
+
+    public static void main(String[] args) {
+      new A().caller();
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
index 4cada28..222c6a8 100644
--- a/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/ProguardConfigurationParserTest.java
@@ -5,6 +5,7 @@
 
 import static com.android.tools.r8.DiagnosticsChecker.checkDiagnostics;
 import static com.android.tools.r8.shaking.ProguardConfigurationSourceStrings.createConfigurationForTesting;
+import static com.android.tools.r8.utils.BooleanUtils.intValue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.core.StringContains.containsString;
 import static org.junit.Assert.assertEquals;
@@ -17,6 +18,8 @@
 import static org.junit.Assert.fail;
 
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.ToolHelper.ProcessResult;
 import com.android.tools.r8.graph.ClassAccessFlags;
@@ -34,10 +37,12 @@
 import com.android.tools.r8.utils.FileUtils;
 import com.android.tools.r8.utils.InternalOptions.PackageObfuscationMode;
 import com.android.tools.r8.utils.KeepingDiagnosticHandler;
+import com.android.tools.r8.utils.ListUtils;
 import com.android.tools.r8.utils.Reporter;
 import com.android.tools.r8.utils.StringUtils;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -47,6 +52,8 @@
 import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
+import java.util.Map;
+import java.util.Optional;
 import java.util.function.BiConsumer;
 import java.util.function.Function;
 import java.util.function.Supplier;
@@ -55,6 +62,8 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
 
 class EmptyMainClassForProguardTests {
 
@@ -62,6 +71,7 @@
   }
 }
 
+@RunWith(Parameterized.class)
 public class ProguardConfigurationParserTest extends TestBase {
 
   private static final String VALID_PROGUARD_DIR = "src/test/proguard/valid/";
@@ -151,6 +161,13 @@
   private List<String> lineSeparators = ImmutableList.of("\n", "\r\n");
   private List<Character> quotes = ImmutableList.of('"', '\'');
 
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withNoneRuntime().build();
+  }
+
+  public ProguardConfigurationParserTest(TestParameters parameters) {}
+
   @Before
   public void reset() {
     handler = new KeepingDiagnosticHandler();
@@ -187,7 +204,7 @@
   }
 
   @Test
-  public void parseMultipleNamePatterns() throws Exception {
+  public void parseMultipleNamePatterns() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(MULTIPLE_NAME_PATTERNS_FILE));
@@ -210,7 +227,7 @@
   }
 
   @Test
-  public void parseNonJavaIdentifiers() throws Exception {
+  public void parseNonJavaIdentifiers() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, new Reporter());
@@ -250,8 +267,8 @@
     assertEquals(0x03, matches);
   }
 
-  private void testDontXXX(String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern)
-      throws Exception {
+  private void testDontXXX(
+      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -265,13 +282,13 @@
   }
 
   @Test
-  public void testDontXXX() throws Exception {
+  public void testDontXXX() {
     testDontXXX("warn", ProguardConfiguration::getDontWarnPatterns);
     testDontXXX("note", ProguardConfiguration::getDontNotePatterns);
   }
 
   private void testDontXXXMultiple(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -288,13 +305,13 @@
   }
 
   @Test
-  public void testDontWarnMultiple() throws Exception {
+  public void testDontWarnMultiple() {
     testDontXXXMultiple("warn", ProguardConfiguration::getDontWarnPatterns);
     testDontXXXMultiple("note", ProguardConfiguration::getDontNotePatterns);
   }
 
   private void testDontXXXAllExplicitly(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -308,13 +325,13 @@
   }
 
   @Test
-  public void testDontWarnAllExplicitly() throws Exception {
+  public void testDontWarnAllExplicitly() {
     testDontXXXAllExplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
     testDontXXXAllExplicitly("note", ProguardConfiguration::getDontNotePatterns);
   }
 
   private void testDontXXXAllImplicitly(
-      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) throws Exception {
+      String xxx, Function<ProguardConfiguration, ProguardClassFilter> pattern) {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -328,13 +345,13 @@
   }
 
   @Test
-  public void testDontWarnAllImplicitly() throws Exception {
+  public void testDontWarnAllImplicitly() {
     testDontXXXAllImplicitly("warn", ProguardConfiguration::getDontWarnPatterns);
     testDontXXXAllImplicitly("note", ProguardConfiguration::getDontNotePatterns);
   }
 
   @Test
-  public void parseAccessFlags() throws Exception {
+  public void parseAccessFlags() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ACCESS_FLAGS_FILE));
@@ -380,7 +397,7 @@
   }
 
   @Test
-  public void parseWhyAreYouKeeping() throws Exception {
+  public void parseWhyAreYouKeeping() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(WHY_ARE_YOU_KEEPING_FILE));
@@ -395,7 +412,7 @@
   }
 
   @Test
-  public void parseAssumeNoSideEffects() throws Exception {
+  public void parseAssumeNoSideEffects() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS));
@@ -408,7 +425,7 @@
   }
 
   @Test
-  public void parseAssumeNoSideEffectsWithReturnValue() throws Exception {
+  public void parseAssumeNoSideEffectsWithReturnValue() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_NO_SIDE_EFFECTS_WITH_RETURN_VALUE));
@@ -477,7 +494,7 @@
   }
 
   @Test
-  public void parseAssumeValuesWithReturnValue() throws Exception {
+  public void parseAssumeValuesWithReturnValue() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(ASSUME_VALUES_WITH_RETURN_VALUE));
@@ -546,7 +563,7 @@
   }
 
   @Test
-  public void testAdaptClassStrings() throws Exception {
+  public void testAdaptClassStrings() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -563,7 +580,7 @@
   }
 
   @Test
-  public void testAdaptClassStringsMultiple() throws Exception {
+  public void testAdaptClassStringsMultiple() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -584,7 +601,7 @@
   }
 
   @Test
-  public void testAdaptClassStringsAllExplicitly() throws Exception {
+  public void testAdaptClassStringsAllExplicitly() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -601,7 +618,7 @@
   }
 
   @Test
-  public void testAdaptClassStringsAllImplicitly() throws Exception {
+  public void testAdaptClassStringsAllImplicitly() {
     DexItemFactory dexItemFactory = new DexItemFactory();
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(dexItemFactory, reporter);
@@ -618,7 +635,7 @@
   }
 
   @Test
-  public void testIdentifierNameString() throws Exception {
+  public void testIdentifierNameString() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     String config1 =
@@ -656,14 +673,22 @@
     });
     assertEquals(1, identifierNameStrings.get(2).getClassNames().size());
     assertEquals("*", identifierNameStrings.get(2).getClassNames().toString());
-    identifierNameStrings.get(2).getMemberRules().forEach(memberRule -> {
-      assertEquals(ProguardMemberType.ALL, memberRule.getRuleType());
-      assertTrue(memberRule.getAnnotation().toString().endsWith("IdentifierNameString"));
-    });
+    identifierNameStrings
+        .get(2)
+        .getMemberRules()
+        .forEach(
+            memberRule -> {
+              assertEquals(ProguardMemberType.ALL, memberRule.getRuleType());
+              assertEquals(1, memberRule.getAnnotations().size());
+              assertTrue(
+                  ListUtils.first(memberRule.getAnnotations())
+                      .toString()
+                      .endsWith("IdentifierNameString"));
+            });
   }
 
   @Test
-  public void parseDontobfuscate() throws Exception {
+  public void parseDontobfuscate() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(DONT_OBFUSCATE));
@@ -673,7 +698,7 @@
   }
 
   @Test
-  public void parseRepackageClassesEmpty() throws Exception {
+  public void parseRepackageClassesEmpty() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_1));
@@ -685,7 +710,7 @@
   }
 
   @Test
-  public void parseRepackageClassesNonEmpty() throws Exception {
+  public void parseRepackageClassesNonEmpty() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_2));
@@ -697,7 +722,7 @@
   }
 
   @Test
-  public void parseFlattenPackageHierarchyEmpty() throws Exception {
+  public void parseFlattenPackageHierarchyEmpty() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_3));
@@ -709,7 +734,7 @@
   }
 
   @Test
-  public void parseFlattenPackageHierarchyNonEmpty() throws Exception {
+  public void parseFlattenPackageHierarchyNonEmpty() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(PACKAGE_OBFUSCATION_4));
@@ -721,7 +746,7 @@
   }
 
   @Test
-  public void flattenPackageHierarchyCannotOverrideRepackageClasses() throws Exception {
+  public void flattenPackageHierarchyCannotOverrideRepackageClasses() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_5);
@@ -735,7 +760,7 @@
   }
 
   @Test
-  public void repackageClassesOverridesFlattenPackageHierarchy() throws Exception {
+  public void repackageClassesOverridesFlattenPackageHierarchy() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     Path path = Paths.get(PACKAGE_OBFUSCATION_6);
@@ -749,7 +774,7 @@
   }
 
   @Test
-  public void parseApplyMapping() throws Exception {
+  public void parseApplyMapping() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(APPLY_MAPPING));
@@ -759,7 +784,7 @@
   }
 
   @Test
-  public void parseApplyMappingWithoutFile() throws Exception {
+  public void parseApplyMappingWithoutFile() {
     Path path = Paths.get(APPLY_MAPPING_WITHOUT_FILE);
     try {
       ProguardConfigurationParser parser =
@@ -781,7 +806,7 @@
   }
 
   @Test
-  public void parseIncluding() throws Exception {
+  public void parseIncluding() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(Paths.get(INCLUDING));
@@ -789,7 +814,7 @@
   }
 
   @Test
-  public void parseInvalidIncluding1() throws IOException {
+  public void parseInvalidIncluding1() {
     Path path = Paths.get(INVALID_INCLUDING_1);
     try {
       new ProguardConfigurationParser(new DexItemFactory(), reporter)
@@ -1442,7 +1467,7 @@
   }
 
   @Test
-  public void parseKeepParameterNames() throws Exception {
+  public void parseKeepParameterNames() {
     try {
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1458,7 +1483,7 @@
   }
 
   @Test
-  public void parseKeepParameterNamesWithoutMinification() throws Exception {
+  public void parseKeepParameterNamesWithoutMinification() {
     ProguardConfigurationParser parser =
         new ProguardConfigurationParser(new DexItemFactory(), reporter);
     parser.parse(createConfigurationForTesting(ImmutableList.of(
@@ -1483,7 +1508,7 @@
   }
 
   @Test
-  public void parseShortLine() throws IOException {
+  public void parseShortLine() {
     try {
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -1496,7 +1521,7 @@
   }
 
   @Test
-  public void parseNoLocals() throws IOException {
+  public void parseNoLocals() {
     try {
       ProguardConfigurationParser parser =
           new ProguardConfigurationParser(new DexItemFactory(), reporter);
@@ -2915,4 +2940,239 @@
     parser.parse(proguardConfig);
     verifyParserEndsCleanly();
   }
+
+  @Test
+  public void parseClassAnnotationsAndFlags() {
+    List<String> configurationContents =
+        ImmutableList.of(
+            // 1 annotation without flags.
+            "-keep @Foo class *",
+            // 1 annotation with public flag.
+            "-keep public @Foo class *",
+            "-keep @Foo public class *",
+            // 1 annotation with public final flags.
+            "-keep public final @Foo class *",
+            "-keep public @Foo final class *",
+            "-keep @Foo public final class *",
+            // 2 annotations without flags.
+            "-keep @Foo @Bar class *",
+            // 2 annotations with public flag.
+            "-keep public @Foo @Bar class *",
+            "-keep @Foo public @Bar class *",
+            "-keep @Foo @Bar public class *",
+            // 2 annotations with public final flags.
+            "-keep public final @Foo @Bar class *",
+            "-keep public @Foo final @Bar class *",
+            "-keep public @Foo @Bar final class *",
+            "-keep @Foo public final @Bar class *",
+            "-keep @Foo public @Bar final class *",
+            "-keep @Foo @Bar public final class *");
+    for (String configurationContent : configurationContents) {
+      DexItemFactory dexItemFactory = new DexItemFactory();
+      ProguardConfigurationParser parser =
+          new ProguardConfigurationParser(dexItemFactory, reporter);
+      parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+      verifyParserEndsCleanly();
+
+      ProguardConfiguration configuration = parser.getConfig();
+      assertEquals(1, configuration.getRules().size());
+
+      ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+      assertEquals(configurationContent.contains("final"), rule.getClassAccessFlags().isFinal());
+      assertEquals(configurationContent.contains("public"), rule.getClassAccessFlags().isPublic());
+      assertEquals(
+          1 + intValue(configurationContent.contains("@Bar")), rule.getClassAnnotations().size());
+
+      ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+          rule.getClassAnnotations().get(0).asSpecificTypeMatcher();
+      assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+      if (configurationContent.contains("@Bar")) {
+        ProguardTypeMatcher.MatchSpecificType barAnnotation =
+            rule.getClassAnnotations().get(1).asSpecificTypeMatcher();
+        assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+      }
+    }
+  }
+
+  @Test
+  public void parseFieldAnnotationsAndFlags() {
+    Map<String, Optional<Class<? extends Exception>>> configurationContents =
+        ImmutableMap.<String, Optional<Class<? extends Exception>>>builder()
+            // 1 annotation without flags.
+            .put("-keep class * { @Foo Type FIELD; }", Optional.empty())
+            // 1 annotation with public flag.
+            .put("-keep class * { public @Foo Type FIELD; }", Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo public Type FIELD; }", Optional.empty())
+            // 1 annotation with public final flags.
+            .put(
+                "-keep class * { public final @Foo Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo final Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo public final Type FIELD; }", Optional.empty())
+            //// 2 annotations without flags.
+            .put("-keep class * { @Foo @Bar Type FIELD; }", Optional.empty())
+            //// 2 annotations with public flag.
+            .put(
+                "-keep class * { public @Foo @Bar Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public @Bar Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo @Bar public Type FIELD; }", Optional.empty())
+            //// 2 annotations with public final flags.
+            .put(
+                "-keep class * { public final @Foo @Bar Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo final @Bar Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo @Bar final Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public final @Bar Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public @Bar final Type FIELD; }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo @Bar public final Type FIELD; }", Optional.empty())
+            .build();
+    configurationContents.forEach(
+        (configurationContent, expectedExceptionClass) -> {
+          DexItemFactory dexItemFactory = new DexItemFactory();
+          ProguardConfigurationParser parser =
+              new ProguardConfigurationParser(dexItemFactory, reporter);
+          try {
+            parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+            assertFalse(expectedExceptionClass.isPresent());
+          } catch (Throwable e) {
+            assertTrue(expectedExceptionClass.isPresent());
+            assertEquals(expectedExceptionClass.get(), e.getClass());
+            reset();
+            return;
+          }
+
+          verifyParserEndsCleanly();
+
+          ProguardConfiguration configuration = parser.getConfig();
+          assertEquals(1, configuration.getRules().size());
+
+          ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+          assertEquals(1, rule.getMemberRules().size());
+
+          ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules());
+
+          assertEquals(
+              configurationContent.contains("final"), memberRule.getAccessFlags().isFinal());
+          assertEquals(
+              configurationContent.contains("public"), memberRule.getAccessFlags().isPublic());
+          assertEquals(
+              1 + intValue(configurationContent.contains("@Bar")),
+              memberRule.getAnnotations().size());
+
+          ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+              memberRule.getAnnotations().get(0).asSpecificTypeMatcher();
+          assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+          if (configurationContent.contains("@Bar")) {
+            ProguardTypeMatcher.MatchSpecificType barAnnotation =
+                memberRule.getAnnotations().get(1).asSpecificTypeMatcher();
+            assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+          }
+        });
+  }
+
+  @Test
+  public void parseMethodAnnotationsAndFlags() {
+    Map<String, Optional<Class<? extends Exception>>> configurationContents =
+        ImmutableMap.<String, Optional<Class<? extends Exception>>>builder()
+            // 1 annotation without flags.
+            .put("-keep class * { @Foo Type method(); }", Optional.empty())
+            // 1 annotation with public flag.
+            .put(
+                "-keep class * { public @Foo Type method(); }", Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo public Type method(); }", Optional.empty())
+            // 1 annotation with public final flags.
+            .put(
+                "-keep class * { public final @Foo Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo final Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo public final Type method(); }", Optional.empty())
+            //// 2 annotations without flags.
+            .put("-keep class * { @Foo @Bar Type method(); }", Optional.empty())
+            //// 2 annotations with public flag.
+            .put(
+                "-keep class * { public @Foo @Bar Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public @Bar Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo @Bar public Type method(); }", Optional.empty())
+            //// 2 annotations with public final flags.
+            .put(
+                "-keep class * { public final @Foo @Bar Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo final @Bar Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { public @Foo @Bar final Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public final @Bar Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put(
+                "-keep class * { @Foo public @Bar final Type method(); }",
+                Optional.of(RuntimeException.class))
+            .put("-keep class * { @Foo @Bar public final Type method(); }", Optional.empty())
+            .build();
+    configurationContents.forEach(
+        (configurationContent, expectedExceptionClass) -> {
+          DexItemFactory dexItemFactory = new DexItemFactory();
+          ProguardConfigurationParser parser =
+              new ProguardConfigurationParser(dexItemFactory, reporter);
+          try {
+            parser.parse(createConfigurationForTesting(ImmutableList.of(configurationContent)));
+            assertFalse(expectedExceptionClass.isPresent());
+          } catch (Throwable e) {
+            assertTrue(expectedExceptionClass.isPresent());
+            assertEquals(expectedExceptionClass.get(), e.getClass());
+            reset();
+            return;
+          }
+
+          verifyParserEndsCleanly();
+
+          ProguardConfiguration configuration = parser.getConfig();
+          assertEquals(1, configuration.getRules().size());
+
+          ProguardKeepRule rule = ListUtils.first(configuration.getRules()).asProguardKeepRule();
+          assertEquals(1, rule.getMemberRules().size());
+
+          ProguardMemberRule memberRule = ListUtils.first(rule.getMemberRules());
+
+          assertEquals(
+              configurationContent.contains("final"), memberRule.getAccessFlags().isFinal());
+          assertEquals(
+              configurationContent.contains("public"), memberRule.getAccessFlags().isPublic());
+          assertEquals(
+              1 + intValue(configurationContent.contains("@Bar")),
+              memberRule.getAnnotations().size());
+
+          ProguardTypeMatcher.MatchSpecificType fooAnnotation =
+              memberRule.getAnnotations().get(0).asSpecificTypeMatcher();
+          assertEquals("Foo", fooAnnotation.getSpecificType().toSourceString());
+
+          if (configurationContent.contains("@Bar")) {
+            ProguardTypeMatcher.MatchSpecificType barAnnotation =
+                memberRule.getAnnotations().get(1).asSpecificTypeMatcher();
+            assertEquals("Bar", barAnnotation.getSpecificType().toSourceString());
+          }
+        });
+  }
 }
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java
new file mode 100644
index 0000000..80dab7c
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleClassAnnotationsTest.java
@@ -0,0 +1,102 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleClassAnnotationsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepWithMultipleClassAnnotationsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testA() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+        .addKeepRules(
+            "-keep @" + A.class.getTypeName() + " class * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testAB() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+        .addKeepRules(
+            "-keep @" + A.class.getTypeName() + " @" + B.class.getTypeName() + " class * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testABC() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleClassAnnotationsTest.class)
+        .addKeepRules(
+            "-keep @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " @"
+                + C.class.getTypeName()
+                + " class * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+  }
+
+  @A
+  @B
+  static class TestClass {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface A {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface B {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java
new file mode 100644
index 0000000..25e610b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleInheritanceAnnotationsTest.java
@@ -0,0 +1,108 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleInheritanceAnnotationsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepWithMultipleInheritanceAnnotationsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testA() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+        .addKeepRules(
+            "-keep class * extends @" + A.class.getTypeName() + " * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testAB() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+        .addKeepRules(
+            "-keep class * extends @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testABC() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleInheritanceAnnotationsTest.class)
+        .addKeepRules(
+            "-keep class * extends @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " @"
+                + C.class.getTypeName()
+                + " * {",
+            "  public static void main(java.lang.String[]);",
+            "}")
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+  }
+
+  @A
+  @B
+  static class TestClassBase {}
+
+  static class TestClass extends TestClassBase {
+
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface A {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface B {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.TYPE})
+  @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java
new file mode 100644
index 0000000..cafe2a4
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleMemberAnnotationsTest.java
@@ -0,0 +1,106 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleMemberAnnotationsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepWithMultipleMemberAnnotationsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testA() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @" + A.class.getTypeName() + " public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testAB() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testABC() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleMemberAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " @"
+                + C.class.getTypeName()
+                + " public static void main(java.lang.String[]);",
+            "}")
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+  }
+
+  static class TestClass {
+
+    @A
+    @B
+    public static void main(String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  @interface A {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  @interface B {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.METHOD})
+  @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java
new file mode 100644
index 0000000..795574a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/annotations/KeepWithMultipleParameterAnnotationsTest.java
@@ -0,0 +1,104 @@
+// Copyright (c) 2020, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.shaking.annotations;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class KeepWithMultipleParameterAnnotationsTest extends TestBase {
+
+  private final TestParameters parameters;
+
+  @Parameterized.Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  }
+
+  public KeepWithMultipleParameterAnnotationsTest(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  @Test
+  public void testA() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @" + A.class.getTypeName() + " public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testAB() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " public static void main(java.lang.String[]);",
+            "}")
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutputLines("Hello world!");
+  }
+
+  @Test
+  public void testABC() throws Exception {
+    testForR8(parameters.getBackend())
+        .addInnerClasses(KeepWithMultipleParameterAnnotationsTest.class)
+        .addKeepRules(
+            "-keepclasseswithmembers class * {",
+            "  @"
+                + A.class.getTypeName()
+                + " @"
+                + B.class.getTypeName()
+                + " @"
+                + C.class.getTypeName()
+                + " public static void main(java.lang.String[]);",
+            "}")
+        .allowUnusedProguardConfigurationRules()
+        .setMinApi(parameters.getApiLevel())
+        .compile()
+        .inspect(inspector -> assertTrue(inspector.allClasses().isEmpty()));
+  }
+
+  static class TestClass {
+
+    public static void main(@A @B String[] args) {
+      System.out.println("Hello world!");
+    }
+  }
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.PARAMETER})
+  @interface A {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.PARAMETER})
+  @interface B {}
+
+  @Retention(RetentionPolicy.CLASS)
+  @Target({ElementType.PARAMETER})
+  @interface C {}
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
index 560f6ea..5b0f344 100644
--- a/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
+++ b/src/test/java/com/android/tools/r8/shaking/assumenosideeffects/B152492625.java
@@ -11,23 +11,28 @@
 import static org.hamcrest.CoreMatchers.allOf;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
 
 import com.android.tools.r8.CompilationFailedException;
 import com.android.tools.r8.Diagnostic;
 import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestDiagnosticMessages;
 import com.android.tools.r8.TestParameters;
-import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.origin.Origin;
 import com.android.tools.r8.position.TextPosition;
 import com.android.tools.r8.position.TextRange;
+import com.android.tools.r8.utils.BooleanUtils;
 import com.android.tools.r8.utils.codeinspector.ClassSubject;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.google.common.collect.ImmutableList;
+import java.util.Collection;
+import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Assert;
-import org.junit.Assume;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
@@ -36,14 +41,17 @@
 public class B152492625 extends TestBase {
 
   private final TestParameters parameters;
+  private final boolean dontWarnObject;
 
-  @Parameterized.Parameters(name = "{0}")
-  public static TestParametersCollection data() {
-    return getTestParameters().withAllRuntimesAndApiLevels().build();
+  @Parameterized.Parameters(name = "{0}, dontWarnObject {1}")
+  public static Collection<Object[]> data() {
+    return buildParameters(
+        getTestParameters().withAllRuntimesAndApiLevels().build(), BooleanUtils.values());
   }
 
-  public B152492625(TestParameters parameters) {
+  public B152492625(TestParameters parameters, boolean dontWarnObject) {
     this.parameters = parameters;
+    this.dontWarnObject = dontWarnObject;
   }
 
   private void noCallToWait(CodeInspector inspector) {
@@ -60,14 +68,14 @@
                     }));
   }
 
-  private Matcher<Diagnostic> matchAssumeNoSideEffectsWarningMessage() {
+  private Matcher<Diagnostic> matchAssumeNoSideEffectsMessage() {
     return diagnosticMessage(
         containsString(
             "The -assumenosideeffects rule matches methods on `java.lang.Object` with"
                 + " wildcards"));
   }
 
-  private Matcher<Diagnostic> matchWarningMessageForAllProblematicMethods() {
+  private Matcher<Diagnostic> matchMessageForAllProblematicMethods() {
     return diagnosticMessage(
         allOf(
             containsString("void notify()"),
@@ -77,7 +85,7 @@
             containsString("void wait(long, int)")));
   }
 
-  private Matcher<Diagnostic> matchWarningMessageForWaitMethods() {
+  private Matcher<Diagnostic> matchMessageForWaitMethods() {
     return diagnosticMessage(
         allOf(
             containsString("void wait()"),
@@ -85,6 +93,17 @@
             containsString("void wait(long, int)")));
   }
 
+  private void assertErrorsOrWarnings(
+      TestDiagnosticMessages diagnostics, List<Matcher<Diagnostic>> matchers) {
+    if (dontWarnObject) {
+      diagnostics.assertOnlyWarnings();
+      diagnostics.assertWarningsMatch(matchers);
+    } else {
+      diagnostics.assertOnlyErrors();
+      diagnostics.assertErrorsMatch(matchers);
+    }
+  }
+
   private TextRange textRangeForString(String s) {
     return new TextRange(
         new TextPosition(0, 1, 1), new TextPosition(s.length(), 1, s.length() + 1));
@@ -92,21 +111,27 @@
 
   @Test
   public void testR8AllMatch() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, B.class)
-        .addKeepMainRule(TestClass.class)
-        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
-        .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertOnlyWarnings();
-              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
-              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
-            })
-        .inspect(this::noCallToWait)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello, world");
+    List<Matcher<Diagnostic>> matchers =
+        ImmutableList.of(
+            allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods()));
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+          .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
+          .setMinApi(parameters.getApiLevel())
+          .allowDiagnosticWarningMessages()
+          .compileWithExpectedDiagnostics(
+              diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+          .inspect(this::noCallToWait)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world");
+      assertTrue(dontWarnObject);
+    } catch (CompilationFailedException e) {
+      assertFalse(dontWarnObject);
+    }
   }
 
   @Test
@@ -131,93 +156,124 @@
     String starRule = "-assumenosideeffects class " + B.class.getTypeName() + " { *; }";
     String methodsRule = "-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }";
 
-    testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, B.class)
-        .addKeepMainRule(TestClass.class)
-        .apply(
-            b ->
-                b.getBuilder().addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
-        .apply(
-            b ->
-                b.getBuilder()
-                    .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
-        .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertOnlyWarnings();
-              diagnostics.assertWarningsMatch(
-                  ImmutableList.of(
-                      allOf(
-                          matchAssumeNoSideEffectsWarningMessage(),
-                          matchWarningMessageForAllProblematicMethods(),
-                          diagnosticOrigin(starRuleOrigin),
-                          diagnosticPosition(textRangeForString(starRule))),
-                      allOf(
-                          matchAssumeNoSideEffectsWarningMessage(),
-                          matchWarningMessageForAllProblematicMethods(),
-                          diagnosticOrigin(methodsRuleOrigin),
-                          diagnosticPosition(textRangeForString(methodsRule)))));
-            })
-        .inspect(this::noCallToWait)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello, world");
+    List<Matcher<Diagnostic>> matchers =
+        ImmutableList.of(
+            allOf(
+                matchAssumeNoSideEffectsMessage(),
+                matchMessageForAllProblematicMethods(),
+                diagnosticOrigin(starRuleOrigin),
+                diagnosticPosition(textRangeForString(starRule))),
+            allOf(
+                matchAssumeNoSideEffectsMessage(),
+                matchMessageForAllProblematicMethods(),
+                diagnosticOrigin(methodsRuleOrigin),
+                diagnosticPosition(textRangeForString(methodsRule))));
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+          .apply(
+              b ->
+                  b.getBuilder()
+                      .addProguardConfiguration(ImmutableList.of(starRule), starRuleOrigin))
+          .apply(
+              b ->
+                  b.getBuilder()
+                      .addProguardConfiguration(ImmutableList.of(methodsRule), methodsRuleOrigin))
+          .setMinApi(parameters.getApiLevel())
+          .allowDiagnosticWarningMessages()
+          .compileWithExpectedDiagnostics(
+              diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+          .inspect(this::noCallToWait)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world");
+      assertTrue(dontWarnObject);
+    } catch (CompilationFailedException e) {
+      assertFalse(dontWarnObject);
+    }
   }
 
   @Test
-  public void testR8AllMatchDontWarn() throws Exception {
+  public void testR8NonProblemeticMatchDontWarn() throws Exception {
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
-        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *; }")
-        .addKeepRules("-dontwarn java.lang.Object")
+        .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { hash*(); }")
         .setMinApi(parameters.getApiLevel())
-        .compile()
-        .inspect(this::noCallToWait)
+        .allowDiagnosticWarningMessages(!dontWarnObject)
+        .compileWithExpectedDiagnostics(
+            diagnostics -> {
+              if (dontWarnObject) {
+                diagnostics.assertNoMessages();
+              } else {
+                diagnostics.assertOnlyWarnings();
+                diagnostics.assertWarningsMatch(
+                    allOf(
+                        matchAssumeNoSideEffectsMessage(),
+                        diagnosticMessage(containsString("int hashCode()"))));
+              }
+            })
         .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello, world");
+        // Code fails with exception if wait call is not removed.
+        .assertFailureWithErrorThatThrows(IllegalMonitorStateException.class);
   }
 
   @Test
   public void testR8AllMethodsMatch() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, B.class)
-        .addKeepMainRule(TestClass.class)
-        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
-        .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertOnlyWarnings();
-              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
-              diagnostics.assertWarningsMatch(matchWarningMessageForAllProblematicMethods());
-            })
-        .inspect(this::noCallToWait)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello, world");
+    List<Matcher<Diagnostic>> matchers =
+        ImmutableList.of(
+            allOf(matchAssumeNoSideEffectsMessage(), matchMessageForAllProblematicMethods()));
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+          .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { <methods>; }")
+          .setMinApi(parameters.getApiLevel())
+          .allowDiagnosticWarningMessages()
+          .compileWithExpectedDiagnostics(
+              diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+          .inspect(this::noCallToWait)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world");
+      assertTrue(dontWarnObject);
+    } catch (CompilationFailedException e) {
+      assertFalse(dontWarnObject);
+    }
   }
 
   @Test
   public void testR8WaitMethodMatch() throws Exception {
-    testForR8(parameters.getBackend())
-        .addProgramClasses(TestClass.class, B.class)
-        .addKeepMainRule(TestClass.class)
-        .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
-        .setMinApi(parameters.getApiLevel())
-        .allowDiagnosticWarningMessages()
-        .compileWithExpectedDiagnostics(
-            diagnostics -> {
-              diagnostics.assertOnlyWarnings();
-              diagnostics.assertWarningsMatch(matchAssumeNoSideEffectsWarningMessage());
-              diagnostics.assertWarningsMatch(matchWarningMessageForWaitMethods());
-            })
-        .inspect(this::noCallToWait)
-        .run(parameters.getRuntime(), TestClass.class)
-        .assertSuccessWithOutputLines("Hello, world");
+    List<Matcher<Diagnostic>> matchers =
+        ImmutableList.of(allOf(matchAssumeNoSideEffectsMessage(), matchMessageForWaitMethods()));
+
+    try {
+      testForR8(parameters.getBackend())
+          .addProgramClasses(TestClass.class, B.class)
+          .addKeepMainRule(TestClass.class)
+          .applyIf(dontWarnObject, tb -> tb.addKeepRules("-dontwarn java.lang.Object"))
+          .addKeepRules("-assumenosideeffects class " + B.class.getTypeName() + " { *** w*(...); }")
+          .setMinApi(parameters.getApiLevel())
+          .allowDiagnosticWarningMessages()
+          .compileWithExpectedDiagnostics(
+              diagnostics -> assertErrorsOrWarnings(diagnostics, matchers))
+          .inspect(this::noCallToWait)
+          .run(parameters.getRuntime(), TestClass.class)
+          .assertSuccessWithOutputLines("Hello, world");
+      assertTrue(dontWarnObject);
+    } catch (CompilationFailedException e) {
+      assertFalse(dontWarnObject);
+    }
   }
 
   @Test
   public void testR8WaitSpecificMethodMatch() throws Exception {
+    assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+
     testForR8(parameters.getBackend())
         .addProgramClasses(TestClass.class, B.class)
         .addKeepMainRule(TestClass.class)
@@ -254,7 +310,8 @@
 
   @Test
   public void testProguardNotRemovingWait() throws Exception {
-    Assume.assumeTrue(parameters.isCfRuntime());
+    assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+    assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
         .addProgramClasses(TestClass.class, B.class)
@@ -269,7 +326,8 @@
 
   @Test
   public void testProguardRemovingWait() throws Exception {
-    Assume.assumeTrue(parameters.isCfRuntime());
+    assumeTrue("No need to run this with -dontwarn java.lang.Object", !dontWarnObject);
+    assumeTrue(parameters.isCfRuntime());
 
     testForProguard()
         .addProgramClasses(TestClass.class, B.class)
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
index 47a6182..81b988e 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentClassSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import java.util.List;
 import java.util.function.Consumer;
@@ -93,6 +94,11 @@
   }
 
   @Override
+  public String getOriginalBinaryName() {
+    return null;
+  }
+
+  @Override
   public String getFinalName() {
     return null;
   }
@@ -138,6 +144,11 @@
   }
 
   @Override
+  public DexMethod getFinalEnclosingMethod() {
+    throw new Unreachable("Cannot determine EnclosingMethod attribute of an absent class");
+  }
+
+  @Override
   public String getOriginalSignatureAttribute() {
     return null;
   }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
index 78ac796..622db02 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmTypeAliasSubject.java
@@ -5,7 +5,9 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import com.android.tools.r8.errors.Unreachable;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
+import kotlinx.metadata.KmAnnotation;
 
 public class AbsentKmTypeAliasSubject extends KmTypeAliasSubject {
 
@@ -48,4 +50,9 @@
   public KmTypeSubject underlyingType() {
     return null;
   }
+
+  @Override
+  public List<KmAnnotation> annotations() {
+    return ImmutableList.of();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
index 8fed691..97a3b29 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/ClassSubject.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.utils.codeinspector;
 
+import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.naming.MemberNaming.MethodSignature;
 import com.android.tools.r8.references.MethodReference;
@@ -164,6 +165,8 @@
 
   public abstract String getOriginalDescriptor();
 
+  public abstract String getOriginalBinaryName();
+
   public abstract String getFinalName();
 
   public abstract String getFinalDescriptor();
@@ -178,6 +181,8 @@
 
   public abstract boolean isSynthesizedJavaLambdaClass();
 
+  public abstract DexMethod getFinalEnclosingMethod();
+
   public abstract String getOriginalSignatureAttribute();
 
   public abstract String getFinalSignatureAttribute();
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index b2edae2..2a4c7c5 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -83,12 +83,12 @@
     this(Collections.singletonList(file), null, null);
   }
 
-  public CodeInspector(List<Path> files) throws IOException {
+  public CodeInspector(Collection<Path> files) throws IOException {
     this(files, null, null);
   }
 
   public CodeInspector(
-      List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
+      Collection<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
       throws IOException {
     Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
     if (mappingPath != null && Files.exists(mappingPath)) {
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 6923021..2ce3024 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -263,6 +263,11 @@
     }
   }
 
+  @Override
+  public String getOriginalBinaryName() {
+    return DescriptorUtils.getBinaryNameFromDescriptor(getOriginalDescriptor());
+  }
+
   public DexType getOriginalDexType(DexItemFactory dexItemFactory) {
     return dexItemFactory.createType(getOriginalDescriptor());
   }
@@ -308,6 +313,11 @@
   }
 
   @Override
+  public DexMethod getFinalEnclosingMethod() {
+    return dexClass.getEnclosingMethodAttribute().getEnclosingMethod();
+  }
+
+  @Override
   public String getOriginalSignatureAttribute() {
     return codeInspector.getOriginalSignatureAttribute(
         dexClass.annotations(), GenericSignatureParser::parseClassSignature);
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
index a07125c..396ef98 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmTypeAliasSubject.java
@@ -9,6 +9,7 @@
 
 import java.util.List;
 import java.util.stream.Collectors;
+import kotlinx.metadata.KmAnnotation;
 import kotlinx.metadata.KmTypeAlias;
 
 public class FoundKmTypeAliasSubject extends KmTypeAliasSubject {
@@ -62,4 +63,9 @@
   public KmTypeSubject underlyingType() {
     return new KmTypeSubject(codeInspector, kmTypeAlias.underlyingType);
   }
+
+  @Override
+  public List<KmAnnotation> annotations() {
+    return kmTypeAlias.getAnnotations();
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
index f87d368..8ff3c90 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmTypeAliasSubject.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.utils.codeinspector;
 
 import java.util.List;
+import kotlinx.metadata.KmAnnotation;
 
 public abstract class KmTypeAliasSubject extends Subject {
 
@@ -17,4 +18,6 @@
   public abstract KmTypeSubject expandedType();
 
   public abstract KmTypeSubject underlyingType();
+
+  public abstract List<KmAnnotation> annotations();
 }
diff --git a/tools/archive_desugar_jdk_libs.py b/tools/archive_desugar_jdk_libs.py
index 7714362..343a7f2 100755
--- a/tools/archive_desugar_jdk_libs.py
+++ b/tools/archive_desugar_jdk_libs.py
@@ -115,7 +115,7 @@
             'Target archive directory %s already exists' % destination)
 
       bazel = os.path.join(utils.BAZEL_TOOL, 'lib', 'bazel', 'bin', 'bazel')
-      cmd = [bazel, 'build', '--host_force_python=PY2', 'maven_release']
+      cmd = [bazel, 'build', 'maven_release']
       utils.PrintCmd(cmd)
       subprocess.check_call(cmd)
       cmd = [bazel, 'shutdown']
diff --git a/tools/create_maven_release.py b/tools/create_maven_release.py
index cc36693..42dcc63 100755
--- a/tools/create_maven_release.py
+++ b/tools/create_maven_release.py
@@ -96,11 +96,6 @@
       <distribution>repo</distribution>
     </license>
   </licenses>
-  <dependencies>
-    <groupId>com.android.tools</groupId>
-    <artifactId>desugar_jdk_libs</artifactId>
-    <version>1.0.1</version>
-  </dependencies>
   <developers>
     <developer>
       <name>The Android Open Source Project</name>
diff --git a/tools/r8_release.py b/tools/r8_release.py
index 7b1ba0b..357b659 100755
--- a/tools/r8_release.py
+++ b/tools/r8_release.py
@@ -390,41 +390,6 @@
   return make_release
 
 
-def prepare_push_desugar_library(args):
-  client_name = 'push-desugar-library'
-  # Check if an existing client exists.
-  check_no_google3_client(args, client_name)
-
-  def push_desugar_library(options):
-    print 'Pushing to %s' % GITHUB_DESUGAR_JDK_LIBS
-
-    google3_base = subprocess.check_output(
-        ['p4', 'g4d', '-f', client_name]).rstrip()
-    third_party_desugar_jdk_libs = \
-        os.path.join(google3_base, 'third_party', 'java_src', 'desugar_jdk_libs')
-    version = archive_desugar_jdk_libs.GetVersion(
-        os.path.join(third_party_desugar_jdk_libs, 'oss', 'VERSION.txt'))
-    if args.push_desugar_library != version:
-      print ("Failed, version of desugared library is %s, but version %s was expected." %
-        (version, args.push_desugar_library))
-      sys.exit(1)
-    with utils.ChangedWorkingDirectory(google3_base):
-      cmd = [
-          'copybara',
-           os.path.join(
-              'third_party',
-              'java_src',
-              'desugar_jdk_libs',
-              'copy.bara.sky'),
-           'push-to-github']
-      if options.dry_run:
-        print "Dry-run, not running '%s'" % ' '.join(cmd)
-      else:
-        subprocess.check_call(cmd)
-
-  return push_desugar_library
-
-
 def download_configuration(hash, archive):
   print
   print 'Downloading %s from GCS' % archive
@@ -662,10 +627,6 @@
   group.add_argument('--version',
                       metavar=('<version>'),
                       help='The new version of R8 (e.g., 1.4.51) to release to selected channels')
-  group.add_argument('--push-desugar-library',
-                      metavar=('<version>'),
-                      help='The expected version of '
-                          + 'com.android.tools:desugar_jdk_libs to push to GitHub')
   group.add_argument('--desugar-library',
                       nargs=2,
                       metavar=('<version>', '<configuration hash>'),
@@ -751,8 +712,7 @@
 
   if (args.google3
       or (args.studio and not args.no_sync)
-      or (args.desugar_library and not args.dry_run)
-      or (args.push_desugar_library and not args.dry_run)):
+      or (args.desugar_library and not args.dry_run)):
     utils.check_prodacces()
 
   if args.google3:
@@ -765,9 +725,6 @@
   if args.desugar_library:
     targets_to_run.append(prepare_desugar_library(args))
 
-  if args.push_desugar_library:
-    targets_to_run.append(prepare_push_desugar_library(args))
-
   final_results = []
   for target_closure in targets_to_run:
     final_results.append(target_closure(args))