Enable GraphLense to also rewrite the type of classes.

Bug:
Change-Id: I644b8abcb337c4396ed14bea0156e674a402155f
diff --git a/src/main/java/com/android/tools/r8/D8.java b/src/main/java/com/android/tools/r8/D8.java
index 8a92ef0..e025665 100644
--- a/src/main/java/com/android/tools/r8/D8.java
+++ b/src/main/java/com/android/tools/r8/D8.java
@@ -205,8 +205,8 @@
       throws IOException, ExecutionException, ApiLevelException {
     final CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
 
-    IRConverter converter = new IRConverter(timing, application, appInfo, options, printer);
-    application = converter.convertToDex(executor);
+    IRConverter converter = new IRConverter(timing, appInfo, options, printer);
+    application = converter.convertToDex(application, executor);
 
     if (options.printCfg) {
       if (options.printCfgFile == null || options.printCfgFile.isEmpty()) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index f72dd9f..27e7d02 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -131,8 +131,8 @@
     timing.begin("Create IR");
     try {
       IRConverter converter = new IRConverter(
-          timing, application, appInfo, options, printer, graphLense);
-      application = converter.optimize(executorService);
+          timing, appInfo, options, printer, graphLense);
+      application = converter.optimize(application, executorService);
     } finally {
       timing.end();
     }
@@ -226,7 +226,7 @@
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
-          RootSetBuilder.writeSeeds(appInfo.withLiveness().pinnedItems, out);
+          RootSetBuilder.writeSeeds(appInfo.withLiveness().getPinnedItems(), out);
           out.flush();
           proguardSeedsData = bytes.toByteArray();
         }
@@ -261,10 +261,12 @@
               appInfo.withLiveness(), graphLense, timing);
           graphLense = classMerger.run();
           timing.end();
+
           appInfo = appInfo.withLiveness()
               .prunedCopyFrom(application, classMerger.getRemovedClasses());
         }
-        appInfo = appInfo.withLiveness().rewrittenWithLense(graphLense);
+        application = application.asDirect().rewrittenWithLense(graphLense);
+        appInfo = appInfo.withLiveness().rewrittenWithLense(application.asDirect(), graphLense);
         // Collect switch maps and ordinals maps.
         new SwitchMapCollector(appInfo.withLiveness(), options).run();
         new EnumOrdinalMapCollector(appInfo.withLiveness(), options).run();
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 2d111f0..820e49d 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -32,10 +32,12 @@
     this.definitions.putAll(previous.definitions);
   }
 
-  protected AppInfo(AppInfo previous, GraphLense lense) {
-    // Do not rewrite basic structure, as the type information in the lense is about applied uses
-    // and not definitions.
-    this(previous);
+  protected AppInfo(DirectMappedDexApplication application, GraphLense lense) {
+    // Rebuild information from scratch, as the application object has changed. We do not
+    // use the lense here, as it is about applied occurrences and not definitions.
+    // In particular, we have to invalidate the definitions cache, as its keys are no longer
+    // valid.
+    this(application);
   }
 
   private Map<Descriptor, KeyedDexItem> computeDefinitions(DexType type) {
@@ -331,6 +333,10 @@
     // We do not track subtyping relationships in the basic AppInfo. So do nothing.
   }
 
+  public boolean isInMainDexList(DexType type) {
+    return app.mainDexList.contains(type);
+  }
+
   public List<DexClass> getSuperTypeClasses(DexType type) {
     List<DexClass> result = new ArrayList<>();
     do {
diff --git a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
index 54b2a51..a98ec9e 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -6,7 +6,6 @@
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Sets;
-
 import java.util.Collections;
 import java.util.HashSet;
 import java.util.Hashtable;
@@ -33,10 +32,10 @@
     assert app instanceof DirectMappedDexApplication;
   }
 
-  protected AppInfoWithSubtyping(AppInfoWithSubtyping previous, GraphLense lense) {
-    super(previous, lense);
+  protected AppInfoWithSubtyping(DirectMappedDexApplication application, GraphLense lense) {
+    super(application, lense);
     // Recompute subtype map if we have modified the graph.
-    populateSubtypeMap(previous.getDirectApplication(), dexItemFactory);
+    populateSubtypeMap(application, dexItemFactory);
   }
 
   private DirectMappedDexApplication getDirectApplication() {
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 e2583c3..b68b1d4 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -6,16 +6,15 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.graph;
 
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-
 import com.android.tools.r8.naming.ClassNameMapper;
 import com.android.tools.r8.utils.ProgramClassCollection;
 import com.android.tools.r8.utils.Timing;
-
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Iterables;
+import java.util.ArrayList;
 import java.util.Collection;
-import java.util.IdentityHashMap;
+import java.util.List;
 import java.util.Map;
 
 public class DirectMappedDexApplication extends DexApplication {
@@ -65,22 +64,43 @@
     return "DexApplication (direct)";
   }
 
+  public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
+    assert graphLense.isContextFree();
+    assert mappingIsValid(graphLense, programClasses.getAllTypes());
+    assert mappingIsValid(graphLense, libraryClasses.keySet());
+    // As a side effect, this will rebuild the program classes and library classes maps.
+    return this.builder().build().asDirect();
+  }
+
+  private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
+    // The lense might either map to a different type that is already present in the application
+    // (e.g. relinking a type) or it might encode a type that was renamed, in which case the
+    // original type will point to a definition that was renamed.
+    for (DexType type : types) {
+      DexType renamed = graphLense.lookupType(type, null);
+      if (renamed != type) {
+        if (definitionFor(type).type != renamed && definitionFor(renamed) == null) {
+          return false;
+        }
+      }
+    }
+    return true;
+  }
+
   public static class Builder extends DexApplication.Builder<Builder> {
 
-    private Map<DexType, DexLibraryClass> libraryClasses = new IdentityHashMap<>();
+    private List<DexLibraryClass> libraryClasses = new ArrayList<>();
 
     Builder(LazyLoadedDexApplication application) {
       super(application);
       // As a side-effect, this will force-load all classes.
       Map<DexType, DexClass> allClasses = application.getFullClassMap();
-      Iterables.filter(allClasses.values(), DexLibraryClass.class)
-          .forEach(k -> libraryClasses.put(k.type, k));
-
+      Iterables.filter(allClasses.values(), DexLibraryClass.class).forEach(libraryClasses::add);
     }
 
     private Builder(DirectMappedDexApplication application) {
       super(application);
-      this.libraryClasses.putAll(application.libraryClasses);
+      this.libraryClasses.addAll(application.libraryClasses.values());
     }
 
     @Override
@@ -90,9 +110,11 @@
 
     @Override
     public DexApplication build() {
+      // Rebuild the map. This will fail if keys are not unique.
       return new DirectMappedDexApplication(proguardMap,
           ProgramClassCollection.create(programClasses),
-          ImmutableMap.copyOf(libraryClasses), ImmutableSet.copyOf(mainDexList), deadCode,
+          libraryClasses.stream().collect(ImmutableMap.toImmutableMap(c -> c.type, c -> c)),
+          ImmutableSet.copyOf(mainDexList), deadCode,
           dexItemFactory, highestSortingString, timing);
     }
   }
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index b32f34d..5704e0f 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -179,7 +179,7 @@
     }
     for (Node value : nodes.values()) {
       // For non-pinned methods we know the exact number of call sites.
-      if (!appInfo.withLiveness().pinnedItems.contains(value.method)) {
+      if (!appInfo.withLiveness().isPinned(value.method)) {
         if (value.invokeCount == 1) {
           singleCallSite.add(value.method);
         } else if (value.invokeCount == 2) {
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 e83774d..c0f5b40 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
@@ -63,7 +63,6 @@
   private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
 
   private final Timing timing;
-  public final DexApplication application;
   public final AppInfo appInfo;
   private final Outliner outliner;
   private final LambdaRewriter lambdaRewriter;
@@ -82,17 +81,14 @@
 
   private IRConverter(
       Timing timing,
-      DexApplication application,
       AppInfo appInfo,
       GraphLense graphLense,
       InternalOptions options,
       CfgPrinter printer,
       boolean enableWholeProgramOptimizations) {
-    assert application != null;
     assert appInfo != null;
     assert options != null;
     this.timing = timing != null ? timing : new Timing("internal");
-    this.application = application;
     this.appInfo = appInfo;
     this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
     this.options = options;
@@ -126,10 +122,9 @@
    * Create an IR converter for processing methods with full program optimization disabled.
    */
   public IRConverter(
-      DexApplication application,
       AppInfo appInfo,
       InternalOptions options) {
-    this(null, application, appInfo, null, options, null, false);
+    this(null, appInfo, null, options, null, false);
   }
 
   /**
@@ -137,11 +132,10 @@
    */
   public IRConverter(
       Timing timing,
-      DexApplication application,
       AppInfo appInfo,
       InternalOptions options,
       CfgPrinter printer) {
-    this(timing, application, appInfo, null, options, printer, false);
+    this(timing, appInfo, null, options, printer, false);
   }
 
   /**
@@ -149,12 +143,11 @@
    */
   public IRConverter(
       Timing timing,
-      DexApplication application,
       AppInfoWithSubtyping appInfo,
       InternalOptions options,
       CfgPrinter printer,
       GraphLense graphLense) {
-    this(timing, application, appInfo, graphLense, options, printer, true);
+    this(timing, appInfo, graphLense, options, printer, true);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
@@ -187,7 +180,7 @@
 
   private void removeLambdaDeserializationMethods() {
     if (lambdaRewriter != null) {
-      lambdaRewriter.removeLambdaDeserializationMethods(application.classes());
+      lambdaRewriter.removeLambdaDeserializationMethods(appInfo.classes());
     }
   }
 
@@ -206,7 +199,7 @@
     }
   }
 
-  public DexApplication convertToDex(ExecutorService executor)
+  public DexApplication convertToDex(DexApplication application, ExecutorService executor)
       throws ExecutionException, ApiLevelException {
     removeLambdaDeserializationMethods();
 
@@ -313,16 +306,18 @@
     }
   }
 
-  public DexApplication optimize() throws ExecutionException, ApiLevelException {
+  public DexApplication optimize(DexApplication application)
+      throws ExecutionException, ApiLevelException {
     ExecutorService executor = Executors.newSingleThreadExecutor();
     try {
-      return optimize(executor);
+      return optimize(application, executor);
     } finally {
       executor.shutdown();
     }
   }
 
-  public DexApplication optimize(ExecutorService executorService)
+  public DexApplication optimize(DexApplication application,
+      ExecutorService executorService)
       throws ExecutionException, ApiLevelException {
     removeLambdaDeserializationMethods();
 
@@ -390,7 +385,7 @@
   }
 
   private void clearDexMethodCompilationState() {
-    application.classes().forEach(this::clearDexMethodCompilationState);
+    appInfo.classes().forEach(this::clearDexMethodCompilationState);
   }
 
   private void clearDexMethodCompilationState(DexProgramClass clazz) {
@@ -427,8 +422,8 @@
     do {
       String name = options.outline.className + (count == 0 ? "" : Integer.toString(count));
       count++;
-      result = application.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
-    } while (application.definitionFor(result) != null);
+      result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
+    } while (appInfo.definitionFor(result) != null);
     // Register the newly generated type in the subtyping hierarchy, if we have one.
     appInfo.registerNewType(result, appInfo.dexItemFactory.objectType);
     return result;
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 c5545f5..a851831 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
@@ -27,7 +27,6 @@
 import com.android.tools.r8.ir.conversion.IRConverter;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.StringDiagnostic;
-import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Sets;
 import java.util.ListIterator;
 import java.util.Map;
@@ -199,8 +198,7 @@
   }
 
   private boolean isInMainDexList(DexType iface) {
-    ImmutableSet<DexType> list = converter.application.mainDexList;
-    return list.contains(iface);
+    return converter.appInfo.isInMainDexList(iface);
   }
 
   // Represent a static interface method as a method of companion class.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
index acac84a..2004742 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriter.java
@@ -95,7 +95,7 @@
   public LambdaRewriter(IRConverter converter) {
     assert converter != null;
     this.converter = converter;
-    this.factory = converter.application.dexItemFactory;
+    this.factory = converter.appInfo.dexItemFactory;
     this.appInfo = converter.appInfo;
 
     DexType metafactoryType = factory.createType(METAFACTORY_TYPE_DESCR);
@@ -226,7 +226,7 @@
   }
 
   private boolean isInMainDexList(DexType type) {
-    return converter.application.mainDexList.contains(type);
+    return converter.appInfo.isInMainDexList(type);
   }
 
   // Returns a lambda class corresponding to the lambda descriptor and context,
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
index 12f1cf2..de0bab0 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MemberValuePropagation.java
@@ -239,7 +239,7 @@
     // Without live set information we cannot tell and assume true.
     if (liveSet == null
         || liveSet.fieldsRead.contains(field.field)
-        || liveSet.pinnedItems.contains(field)) {
+        || liveSet.isPinned(field)) {
       return true;
     }
     // For library classes we don't know whether a field is read.
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 358c1d3..da89ff1 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -23,6 +23,7 @@
 import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexString;
 import com.android.tools.r8.graph.DexType;
+import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.KeyedDexItem;
 import com.android.tools.r8.graph.PresortedComparable;
@@ -1110,7 +1111,7 @@
     /**
      * Set of all items that have to be kept independent of whether they are used.
      */
-    public final Set<DexItem> pinnedItems;
+    final Set<DexItem> pinnedItems;
     /**
      * All items with assumenosideeffects rule.
      */
@@ -1126,11 +1127,11 @@
     /**
      * Map from the class of an extension to the state it produced.
      */
-    public final Map<Class, Object> extensions;
+    final Map<Class, Object> extensions;
     /**
      * A set of types that have been removed by the {@link TreePruner}.
      */
-    public final Set<DexType> prunedTypes;
+    final Set<DexType> prunedTypes;
 
     private AppInfoWithLiveness(AppInfoWithSubtyping appInfo, Enqueuer enqueuer) {
       super(appInfo);
@@ -1138,9 +1139,9 @@
           ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, enqueuer.liveTypes);
       this.instantiatedTypes = ImmutableSortedSet.copyOf(
           PresortedComparable::slowCompareTo, enqueuer.instantiatedTypes.getItems());
-      this.targetedMethods = toDescriptorSet(enqueuer.targetedMethods.getItems());
-      this.liveMethods = toDescriptorSet(enqueuer.liveMethods.getItems());
-      this.liveFields = toDescriptorSet(enqueuer.liveFields.getItems());
+      this.targetedMethods = toSortedDescriptorSet(enqueuer.targetedMethods.getItems());
+      this.liveMethods = toSortedDescriptorSet(enqueuer.liveMethods.getItems());
+      this.liveFields = toSortedDescriptorSet(enqueuer.liveFields.getItems());
       this.instanceFieldReads = enqueuer.collectInstanceFieldsRead();
       this.instanceFieldWrites = enqueuer.collectInstanceFieldsWritten();
       this.staticFieldReads = enqueuer.collectStaticFieldsRead();
@@ -1176,6 +1177,7 @@
       this.fieldsRead = previous.fieldsRead;
       // TODO(herhut): We remove fields that are only written, so maybe update this.
       this.fieldsWritten = previous.fieldsWritten;
+      assert assertNoItemRemoved(previous.pinnedItems, removedClasses);
       this.pinnedItems = previous.pinnedItems;
       this.noSideEffects = previous.noSideEffects;
       this.assumedValues = previous.assumedValues;
@@ -1190,9 +1192,11 @@
       assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
     }
 
-    private AppInfoWithLiveness(AppInfoWithLiveness previous, GraphLense lense) {
-      super(previous, lense);
-      this.liveTypes = previous.liveTypes;
+    private AppInfoWithLiveness(AppInfoWithLiveness previous,
+        DirectMappedDexApplication application,
+        GraphLense lense) {
+      super(application, lense);
+      this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedTypes = rewriteItems(previous.instantiatedTypes, lense::lookupType);
       this.targetedMethods = rewriteItems(previous.targetedMethods, lense::lookupMethod);
       this.liveMethods = rewriteItems(previous.liveMethods, lense::lookupMethod);
@@ -1203,19 +1207,61 @@
       this.staticFieldWrites = rewriteItems(previous.staticFieldWrites, lense::lookupField);
       this.fieldsRead = rewriteItems(previous.fieldsRead, lense::lookupField);
       this.fieldsWritten = rewriteItems(previous.fieldsWritten, lense::lookupField);
-      // TODO(herhut): Migrate these to Descriptors, as well.
+      assert assertNotModifiedByLense(previous.pinnedItems, lense);
       this.pinnedItems = previous.pinnedItems;
-      this.noSideEffects = previous.noSideEffects;
-      this.assumedValues = previous.assumedValues;
       this.virtualInvokes = rewriteItems(previous.virtualInvokes, lense::lookupMethod);
       this.superInvokes = rewriteItems(previous.superInvokes, lense::lookupMethod);
       this.directInvokes = rewriteItems(previous.directInvokes, lense::lookupMethod);
       this.staticInvokes = rewriteItems(previous.staticInvokes, lense::lookupMethod);
+      this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
+      // TODO(herhut): Migrate these to Descriptors, as well.
+      assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
+      this.noSideEffects = previous.noSideEffects;
+      assert assertNotModifiedByLense(previous.assumedValues.keySet(), lense);
+      this.assumedValues = previous.assumedValues;
+      assert assertNotModifiedByLense(previous.alwaysInline, lense);
       this.alwaysInline = previous.alwaysInline;
       this.extensions = previous.extensions;
-      this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
-      assert Sets.intersection(instanceFieldReads, staticFieldReads).size() == 0;
-      assert Sets.intersection(instanceFieldWrites, staticFieldWrites).size() == 0;
+      // Sanity check sets after rewriting.
+      assert Sets.intersection(instanceFieldReads, staticFieldReads).isEmpty();
+      assert Sets.intersection(instanceFieldWrites, staticFieldWrites).isEmpty();
+    }
+
+    private boolean assertNoItemRemoved(Collection<DexItem> items, Collection<DexType> types) {
+      Set<DexType> typeSet = ImmutableSet.copyOf(types);
+      for (DexItem item : items) {
+        if (item instanceof DexClass) {
+          assert !typeSet.contains(((DexClass) item).type);
+        } else if (item instanceof DexEncodedMethod) {
+          assert !typeSet.contains(((DexEncodedMethod) item).method.getHolder());
+        } else if (item instanceof DexEncodedField) {
+          assert !typeSet.contains(((DexEncodedField) item).field.getHolder());
+        } else {
+          assert false;
+        }
+      }
+      return true;
+    }
+
+    private boolean assertNotModifiedByLense(Iterable<DexItem> items, GraphLense lense) {
+      for (DexItem item : items) {
+        if (item instanceof DexClass) {
+          DexType type = ((DexClass) item).type;
+          assert lense.lookupType(type, null) == type;
+        } else if (item instanceof DexEncodedMethod) {
+          DexEncodedMethod method = (DexEncodedMethod) item;
+          // We only allow changes to bridge methods, as these get retargeted even if they
+          // are kept.
+          assert method.accessFlags.isBridge()
+              || lense.lookupMethod(method.method, null) == method.method;
+        } else if (item instanceof DexEncodedField) {
+          DexField field = ((DexEncodedField) item).field;
+          assert lense.lookupField(field, null) == field;
+        } else {
+          assert false;
+        }
+      }
+      return true;
     }
 
     private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
@@ -1225,7 +1271,7 @@
       return builder.build();
     }
 
-    private <T extends PresortedComparable<T>> SortedSet<T> toDescriptorSet(
+    private <T extends PresortedComparable<T>> SortedSet<T> toSortedDescriptorSet(
         Set<? extends KeyedDexItem<T>> set) {
       ImmutableSortedSet.Builder<T> builder =
           new ImmutableSortedSet.Builder<>(PresortedComparable::slowCompareTo);
@@ -1276,6 +1322,14 @@
       return this;
     }
 
+    public boolean isPinned(DexItem item) {
+      return pinnedItems.contains(item);
+    }
+
+    public Iterable<DexItem> getPinnedItems() {
+      return pinnedItems;
+    }
+
     /**
      * Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the
      * given DexApplication object.
@@ -1285,9 +1339,10 @@
       return new AppInfoWithLiveness(this, application, removedClasses);
     }
 
-    public AppInfoWithLiveness rewrittenWithLense(GraphLense lense) {
+    public AppInfoWithLiveness rewrittenWithLense(DirectMappedDexApplication application,
+        GraphLense lense) {
       assert lense.isContextFree();
-      return new AppInfoWithLiveness(this, lense);
+      return new AppInfoWithLiveness(this, application, lense);
     }
 
     /**
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 3b5cdbc..bd72a80 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -7,7 +7,6 @@
 import com.android.tools.r8.graph.DexAnnotation;
 import com.android.tools.r8.graph.DexAnnotationSet;
 import com.android.tools.r8.graph.DexApplication;
-import com.android.tools.r8.graph.DirectMappedDexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
@@ -18,6 +17,7 @@
 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.graph.DirectMappedDexApplication;
 import com.android.tools.r8.logging.Log;
 import com.android.tools.r8.utils.InternalOptions;
 import com.android.tools.r8.utils.MethodSignatureEquivalence;
diff --git a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
index 193ebdf..bf2b761 100644
--- a/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/SimpleClassMerger.java
@@ -76,7 +76,7 @@
     // and we do not have to keep them.
     return !clazz.isLibraryClass()
         && !appInfo.instantiatedTypes.contains(clazz.type)
-        && !appInfo.pinnedItems.contains(clazz)
+        && !appInfo.isPinned(clazz.type)
         && clazz.type.getSingleSubtype() != null;
   }
 
@@ -139,7 +139,7 @@
     for (DexProgramClass clazz : application.classes()) {
       if (isMergeCandidate(clazz)) {
         DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
-        if (appInfo.pinnedItems.contains(targetClass)) {
+        if (appInfo.isPinned(targetClass)) {
           // We have to keep the target class intact, so we cannot merge it.
           continue;
         }
diff --git a/src/main/java/com/android/tools/r8/utils/ClassMap.java b/src/main/java/com/android/tools/r8/utils/ClassMap.java
index 9677473..2e7ac84 100644
--- a/src/main/java/com/android/tools/r8/utils/ClassMap.java
+++ b/src/main/java/com/android/tools/r8/utils/ClassMap.java
@@ -101,6 +101,10 @@
     return loadedClasses;
   }
 
+  public Iterable<DexType> getAllTypes() {
+    return classes.keySet();
+  }
+
   /**
    * Forces loading of all the classes satisfying the criteria specified.
    *
diff --git a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
index 082b5ee..7af3b05 100644
--- a/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
+++ b/src/test/java/com/android/tools/r8/R8UnreachableCodeTest.java
@@ -38,8 +38,8 @@
             .read(executorService)
             .toDirect();
     IRConverter converter =
-        new IRConverter(application, new AppInfoWithSubtyping(application), new InternalOptions());
-    converter.optimize();
+        new IRConverter(new AppInfoWithSubtyping(application), new InternalOptions());
+    converter.optimize(application);
     DexProgramClass clazz = application.classes().iterator().next();
     assertEquals(4, clazz.directMethods().length);
     for (DexEncodedMethod method : clazz.directMethods()) {
diff --git a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
index afe0e2a..a1ea16a 100644
--- a/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
+++ b/src/test/java/com/android/tools/r8/smali/SmaliTestBase.java
@@ -439,7 +439,7 @@
 
     public String run() throws DexOverflowException {
       AppInfo appInfo = new AppInfo(application);
-      IRConverter converter = new IRConverter(application, appInfo, options);
+      IRConverter converter = new IRConverter(appInfo, options);
       converter.replaceCodeForTesting(method, code);
       return runArt(application, options);
     }