Merge "Retrieve single call target without liveness if possible."
diff --git a/src/main/java/com/android/tools/r8/GenerateMainDexList.java b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
index f222908..02d6475 100644
--- a/src/main/java/com/android/tools/r8/GenerateMainDexList.java
+++ b/src/main/java/com/android/tools/r8/GenerateMainDexList.java
@@ -10,9 +10,9 @@
 import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexReference;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.shaking.DiscardedChecker;
 import com.android.tools.r8.shaking.Enqueuer;
-import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
 import com.android.tools.r8.shaking.MainDexClasses;
 import com.android.tools.r8.shaking.MainDexListBuilder;
 import com.android.tools.r8.shaking.RootSetBuilder;
@@ -24,8 +24,8 @@
 import com.android.tools.r8.utils.ThreadUtils;
 import com.android.tools.r8.utils.Timing;
 import java.io.IOException;
-import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.ExecutorService;
 import java.util.stream.Collectors;
@@ -49,7 +49,7 @@
       appView.setAppServices(AppServices.builder(appView).build());
 
       RootSet mainDexRootSet =
-          new RootSetBuilder(appView, application, options.mainDexKeepRules, options).run(executor);
+          new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executor);
 
       GraphConsumer graphConsumer = options.mainDexKeptGraphConsumer;
       WhyAreYouKeepingConsumer whyAreYouKeepingConsumer = null;
@@ -59,10 +59,9 @@
       }
 
       Enqueuer enqueuer = new Enqueuer(appView, options, graphConsumer);
-      AppInfoWithLiveness mainDexAppInfo = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
+      Set<DexType> liveTypes = enqueuer.traceMainDex(mainDexRootSet, executor, timing);
       // LiveTypes is the result.
-      MainDexClasses mainDexClasses =
-          new MainDexListBuilder(new HashSet<>(mainDexAppInfo.liveTypes), application).run();
+      MainDexClasses mainDexClasses = new MainDexListBuilder(liveTypes, application).run();
 
       List<String> result =
           mainDexClasses.getClasses().stream()
@@ -75,9 +74,7 @@
       }
 
       if (!mainDexRootSet.checkDiscarded.isEmpty()) {
-        new DiscardedChecker(
-                mainDexRootSet, mainDexClasses.getClasses(), appView.appInfo(), options)
-            .run();
+        new DiscardedChecker(mainDexRootSet, mainDexClasses.getClasses(), appView).run();
       }
       // Print -whyareyoukeeping results if any.
       if (whyAreYouKeepingConsumer != null) {
diff --git a/src/main/java/com/android/tools/r8/PrintSeeds.java b/src/main/java/com/android/tools/r8/PrintSeeds.java
index c787f02..7402eab 100644
--- a/src/main/java/com/android/tools/r8/PrintSeeds.java
+++ b/src/main/java/com/android/tools/r8/PrintSeeds.java
@@ -87,8 +87,7 @@
           AppView.createForR8(new AppInfoWithSubtyping(application), options);
       appView.setAppServices(AppServices.builder(appView).build());
       RootSet rootSet =
-          new RootSetBuilder(
-                  appView, application, options.getProguardConfiguration().getRules(), options)
+          new RootSetBuilder(appView, application, options.getProguardConfiguration().getRules())
               .run(executor);
       Enqueuer enqueuer = new Enqueuer(appView, options, null);
       AppInfoWithLiveness appInfo =
diff --git a/src/main/java/com/android/tools/r8/PrintUses.java b/src/main/java/com/android/tools/r8/PrintUses.java
index f2d2143..654c144 100644
--- a/src/main/java/com/android/tools/r8/PrintUses.java
+++ b/src/main/java/com/android/tools/r8/PrintUses.java
@@ -212,7 +212,7 @@
         registerTypeReference(type);
       }
       for (DexAnnotation annotation : method.annotations.annotations) {
-        if (annotation.annotation.type == appInfo.dexItemFactory.annotationThrows) {
+        if (annotation.annotation.type == appInfo.dexItemFactory().annotationThrows) {
           DexValueArray dexValues = (DexValueArray) annotation.annotation.elements[0].value;
           for (DexValue dexValType : dexValues.getValues()) {
             registerTypeReference(((DexValueType) dexValType).value);
@@ -302,7 +302,7 @@
   }
 
   private void analyze() {
-    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory);
+    UseCollector useCollector = new UseCollector(appInfo.dexItemFactory());
     for (DexProgramClass dexProgramClass : application.classes()) {
       useCollector.registerSuperType(dexProgramClass, dexProgramClass.superType);
       for (DexType implementsType : dexProgramClass.interfaces.values) {
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 535eb25..889b1b5 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -294,7 +294,7 @@
 
         // Compute kotlin info before setting the roots and before
         // kotlin metadata annotation is removed.
-        computeKotlinInfoForProgramClasses(application, appView.appInfo());
+        computeKotlinInfoForProgramClasses(application, appView);
 
         ProguardConfiguration.Builder compatibility =
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
@@ -313,12 +313,11 @@
 
         rootSet =
             new RootSetBuilder(
-                appView,
-                application,
-                Iterables.concat(
-                    options.getProguardConfiguration().getRules(), synthesizedProguardRules),
-                options
-            ).run(executorService);
+                    appView,
+                    application,
+                    Iterables.concat(
+                        options.getProguardConfiguration().getRules(), synthesizedProguardRules))
+                .run(executorService);
 
         Enqueuer enqueuer = new Enqueuer(appView, options, null, compatibility);
         appView.setAppInfo(
@@ -401,15 +400,14 @@
       if (!options.mainDexKeepRules.isEmpty()) {
         // Find classes which may have code executed before secondary dex files installation.
         mainDexRootSet =
-            new RootSetBuilder(appView, application, options.mainDexKeepRules, options)
-                .run(executorService);
+            new RootSetBuilder(appView, application, options.mainDexKeepRules).run(executorService);
         Enqueuer enqueuer = new Enqueuer(appView, options, null);
-        AppInfoWithLiveness mainDexAppInfo =
+        // Live types is the tracing result.
+        Set<DexType> mainDexBaseClasses =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
-        // LiveTypes is the tracing result.
-        Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
         // Calculate the automatic main dex list according to legacy multidex constraints.
         mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
+        appView.appInfo().unsetObsolete();
       }
 
       if (appView.appInfo().hasLiveness()) {
@@ -508,7 +506,7 @@
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appView.appInfo(), options).run();
+      new SourceFileRewriter(appView).run();
       timing.end();
 
       // Collect the already pruned types before creating a new app info without liveness.
@@ -527,16 +525,13 @@
 
         Enqueuer enqueuer = new Enqueuer(appView, options, mainDexKeptGraphConsumer);
         // Find classes which may have code executed before secondary dex files installation.
-        AppInfoWithLiveness mainDexAppInfo =
+        // Live types is the tracing result.
+        Set<DexType> mainDexBaseClasses =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
-        // LiveTypes is the tracing result.
-        Set<DexType> mainDexBaseClasses = new HashSet<>(mainDexAppInfo.liveTypes);
         // Calculate the automatic main dex list according to legacy multidex constraints.
         mainDexClasses = new MainDexListBuilder(mainDexBaseClasses, application).run();
         if (!mainDexRootSet.checkDiscarded.isEmpty()) {
-          new DiscardedChecker(
-                  mainDexRootSet, mainDexClasses.getClasses(), appView.appInfo(), options)
-              .run();
+          new DiscardedChecker(mainDexRootSet, mainDexClasses.getClasses(), appView).run();
         }
         if (whyAreYouKeepingConsumer != null) {
           for (DexReference reference : mainDexRootSet.reasonAsked) {
@@ -696,8 +691,8 @@
   }
 
   private void computeKotlinInfoForProgramClasses(
-      DexApplication application, AppInfoWithSubtyping appInfo) {
-    Kotlin kotlin = appInfo.dexItemFactory.kotlin;
+      DexApplication application, AppView<? extends AppInfo> appView) {
+    Kotlin kotlin = appView.dexItemFactory().kotlin;
     Reporter reporter = options.reporter;
     for (DexProgramClass programClass : application.classes()) {
       programClass.setKotlinInfo(kotlin.getKotlinInfo(programClass, reporter));
diff --git a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
index 5f198c0..5c2b363 100644
--- a/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/cf/CfRegisterAllocator.java
@@ -144,7 +144,7 @@
   }
 
   @Override
-  public InternalOptions getOptions() {
+  public InternalOptions options() {
     return appView.options();
   }
 
diff --git a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
index d84f551..69517f9 100644
--- a/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
+++ b/src/main/java/com/android/tools/r8/cf/TypeVerificationHelper.java
@@ -217,7 +217,7 @@
   }
 
   private TypeLatticeElement getLatticeElement(DexType type) {
-    return TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appView.appInfo());
+    return TypeLatticeElement.fromDexType(type, Nullability.maybeNull(), appView);
   }
 
   public Map<Value, TypeInfo> computeVerificationTypes() {
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 1754ad9..fa2947b 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfo.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfo.java
@@ -9,7 +9,6 @@
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableMap.Builder;
 import com.google.common.collect.ImmutableSet;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -19,8 +18,8 @@
 
 public class AppInfo implements DexDefinitionSupplier {
 
-  public final DexApplication app;
-  public final DexItemFactory dexItemFactory;
+  private final DexApplication app;
+  private final DexItemFactory dexItemFactory;
   private final ConcurrentHashMap<DexType, Map<Descriptor<?,?>, KeyedDexItem<?>>> definitions =
       new ConcurrentHashMap<>();
   // For some optimizations, e.g. optimizing synthetic classes, we may need to resolve the current
@@ -28,38 +27,60 @@
   private final ConcurrentHashMap<DexType, DexProgramClass> synthesizedClasses =
       new ConcurrentHashMap<>();
 
+  // Set when a new AppInfo replaces a previous one. All public methods should verify that the
+  // current instance is not obsolete, to ensure that we almost use the most recent AppInfo.
+  private boolean obsolete;
+
   public AppInfo(DexApplication application) {
     this.app = application;
     this.dexItemFactory = app.dexItemFactory;
   }
 
   protected AppInfo(AppInfo previous) {
+    assert !previous.isObsolete();
     this.app = previous.app;
     this.dexItemFactory = app.dexItemFactory;
     this.definitions.putAll(previous.definitions);
     this.synthesizedClasses.putAll(previous.synthesizedClasses);
   }
 
-  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);
+  public boolean isObsolete() {
+    return obsolete;
+  }
+
+  public void markObsolete() {
+    obsolete = true;
+  }
+
+  public void unsetObsolete() {
+    obsolete = false;
+  }
+
+  public boolean checkIfObsolete() {
+    assert !isObsolete();
+    return true;
+  }
+
+  public DexApplication app() {
+    assert checkIfObsolete();
+    return app;
   }
 
   @Override
   public DexItemFactory dexItemFactory() {
+    assert checkIfObsolete();
     return dexItemFactory;
   }
 
   public void addSynthesizedClass(DexProgramClass clazz) {
+    assert checkIfObsolete();
     assert clazz.type.isD8R8SynthesizedClassType();
     DexProgramClass previous = synthesizedClasses.put(clazz.type, clazz);
     assert previous == null || previous == clazz;
   }
 
   public Collection<DexProgramClass> getSynthesizedClassesForSanityCheck() {
+    assert checkIfObsolete();
     return Collections.unmodifiableCollection(synthesizedClasses.values());
   }
 
@@ -74,15 +95,18 @@
   }
 
   public Iterable<DexProgramClass> classes() {
+    assert checkIfObsolete();
     return app.classes();
   }
 
   public Iterable<DexProgramClass> classesWithDeterministicOrder() {
+    assert checkIfObsolete();
     return app.classesWithDeterministicOrder();
   }
 
   @Override
   public DexDefinition definitionFor(DexReference reference) {
+    assert checkIfObsolete();
     if (reference.isDexType()) {
       return definitionFor(reference.asDexType());
     }
@@ -95,6 +119,7 @@
 
   @Override
   public DexClass definitionFor(DexType type) {
+    assert checkIfObsolete();
     DexProgramClass cached = synthesizedClasses.get(type);
     if (cached != null) {
       assert app.definitionFor(type) == null;
@@ -104,12 +129,14 @@
   }
 
   public Origin originFor(DexType type) {
+    assert checkIfObsolete();
     DexClass definition = app.definitionFor(type);
     return definition == null ? Origin.unknown() : definition.origin;
   }
 
   @Override
   public DexEncodedMethod definitionFor(DexMethod method) {
+    assert checkIfObsolete();
     DexType holderType = method.holder;
     DexEncodedMethod cached = (DexEncodedMethod) getDefinitions(holderType).get(method);
     if (cached != null && cached.isObsolete()) {
@@ -121,6 +148,7 @@
 
   @Override
   public DexEncodedField definitionFor(DexField field) {
+    assert checkIfObsolete();
     return (DexEncodedField) getDefinitions(field.holder).get(field);
   }
 
@@ -145,6 +173,7 @@
    * @return The actual target for {@code method} or {@code null} if none found.
    */
   public DexEncodedMethod lookupStaticTarget(DexMethod method) {
+    assert checkIfObsolete();
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
     return target == null || target.isStatic() ? target : null;
@@ -152,16 +181,16 @@
 
   /**
    * Lookup super method following the super chain from the holder of {@code method}.
-   * <p>
-   * This method will resolve the method on the holder of {@code method} and only return a non-null
-   * value if the result of resolution was an instance (i.e. non-static) method.
+   *
+   * <p>This method will resolve the method on the holder of {@code method} and only return a
+   * non-null value if the result of resolution was an instance (i.e. non-static) method.
    *
    * @param method the method to lookup
    * @param invocationContext the class the invoke is contained in, i.e., the holder of the caller.
    * @return The actual target for {@code method} or {@code null} if none found.
    */
-  public DexEncodedMethod lookupSuperTarget(DexMethod method,
-      DexType invocationContext) {
+  public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
+    assert checkIfObsolete();
     // Make sure we are not chasing NotFoundError.
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
     if (resolutionResult.asListOfTargets().isEmpty()) {
@@ -192,6 +221,7 @@
    * @return The actual target for {@code method} or {@code null} if none found.
    */
   public DexEncodedMethod lookupDirectTarget(DexMethod method) {
+    assert checkIfObsolete();
     ResolutionResult resolutionResult = resolveMethod(method.holder, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
     return target == null || target.isDirectMethod() ? target : null;
@@ -204,6 +234,7 @@
    * non-null value if the result of resolution was a non-static, non-private method.
    */
   public DexEncodedMethod lookupVirtualTarget(DexType type, DexMethod method) {
+    assert checkIfObsolete();
     assert type.isClassType() || type.isArrayType();
     ResolutionResult resolutionResult = resolveMethod(type, method);
     DexEncodedMethod target = resolutionResult.asSingleTarget();
@@ -221,6 +252,7 @@
    * kind of a method reference.
    */
   public ResolutionResult resolveMethod(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
     if (holder.isArrayType()) {
       return resolveMethodOnArray(holder, method);
     }
@@ -241,6 +273,7 @@
    * All invokations will have target java.lang.Object except clone which has no target.
    */
   public ResolutionResult resolveMethodOnArray(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
     assert holder.isArrayType();
     if (method.name == dexItemFactory.cloneMethodName) {
       return EmptyResult.get();
@@ -261,6 +294,7 @@
    * resolved method is used as basis for dispatch.
    */
   public ResolutionResult resolveMethodOnClass(DexType holder, DexMethod method) {
+    assert checkIfObsolete();
     DexClass clazz = definitionFor(holder);
     // Step 1: If holder is an interface, resolution fails with an ICCE. We return null.
     if (clazz == null || clazz.isInterface()) {
@@ -385,6 +419,7 @@
    * resolved method is used as basis for dispatch.
    */
   public ResolutionResult resolveMethodOnInterface(DexType holder, DexMethod desc) {
+    assert checkIfObsolete();
     // Step 1: Lookup interface.
     DexClass definition = definitionFor(holder);
     // If the definition is not an interface, resolution fails with an ICCE. We just return the
@@ -419,6 +454,7 @@
    * of null indicates that the field is either undefined or not an instance field.
    */
   public DexEncodedField lookupInstanceTarget(DexType type, DexField field) {
+    assert checkIfObsolete();
     assert type.isClassType();
     DexEncodedField result = resolveFieldOn(type, field);
     return result == null || result.accessFlags.isStatic() ? null : result;
@@ -431,6 +467,7 @@
    * of null indicates that the field is either undefined or not a static field.
    */
   public DexEncodedField lookupStaticTarget(DexType type, DexField field) {
+    assert checkIfObsolete();
     assert type.isClassType();
     DexEncodedField result = resolveFieldOn(type, field);
     return result == null || !result.accessFlags.isStatic() ? null : result;
@@ -441,6 +478,7 @@
    * #resolveFieldOn}.
    */
   public DexEncodedField resolveField(DexField field) {
+    assert checkIfObsolete();
     return resolveFieldOn(field.holder, field);
   }
 
@@ -451,6 +489,7 @@
    * Section 5.4.3.2 of the JVM Spec</a>.
    */
   public DexEncodedField resolveFieldOn(DexType type, DexField desc) {
+    assert checkIfObsolete();
     DexClass holder = definitionFor(type);
     if (holder == null) {
       return null;
@@ -483,6 +522,7 @@
    * The only requirement is that the method is indeed static.
    */
   public DexEncodedMethod dispatchStaticInvoke(ResolutionResult resolvedMethod) {
+    assert checkIfObsolete();
     DexEncodedMethod target = resolvedMethod.asSingleTarget();
     if (target != null && target.accessFlags.isStatic()) {
       return target;
@@ -496,6 +536,7 @@
    * The only requirement is that the method is not static.
    */
   public DexEncodedMethod dispatchDirectInvoke(ResolutionResult resolvedMethod) {
+    assert checkIfObsolete();
     DexEncodedMethod target = resolvedMethod.asSingleTarget();
     if (target != null && !target.accessFlags.isStatic()) {
       return target;
@@ -520,42 +561,35 @@
   }
 
   public boolean hasSubtyping() {
+    assert checkIfObsolete();
     return false;
   }
 
   public AppInfoWithSubtyping withSubtyping() {
+    assert checkIfObsolete();
     return null;
   }
 
   public boolean hasLiveness() {
+    assert checkIfObsolete();
     return false;
   }
 
   public AppInfoWithLiveness withLiveness() {
+    assert checkIfObsolete();
     return null;
   }
 
   public void registerNewType(DexType newType, DexType superType) {
     // We do not track subtyping relationships in the basic AppInfo. So do nothing.
+    assert checkIfObsolete();
   }
 
   public boolean isInMainDexList(DexType type) {
+    assert checkIfObsolete();
     return app.mainDexList.contains(type);
   }
 
-  public List<DexClass> getSuperTypeClasses(DexType type) {
-    List<DexClass> result = new ArrayList<>();
-    do {
-      DexClass clazz = definitionFor(type);
-      if (clazz == null) {
-        break;
-      }
-      result.add(clazz);
-      type = clazz.superType;
-    } while (type != null);
-    return result;
-  }
-
   public interface ResolutionResult {
 
     DexEncodedMethod asResultOfResolve();
@@ -630,7 +664,6 @@
     public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
       methods.forEach(consumer);
     }
-
   }
 
   private static class EmptyResult implements ResolutionResult {
@@ -669,6 +702,5 @@
     public void forEachTarget(Consumer<DexEncodedMethod> consumer) {
       // Intentionally left empty.
     }
-
   }
 }
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 c62df9d..293f6f1 100644
--- a/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
+++ b/src/main/java/com/android/tools/r8/graph/AppInfoWithSubtyping.java
@@ -28,6 +28,7 @@
 
   public AppInfoWithSubtyping(DexApplication application) {
     super(application);
+    // Recompute subtype map if we have modified the graph.
     populateSubtypeMap(application.asDirect(), application.dexItemFactory);
   }
 
@@ -35,29 +36,26 @@
     super(previous);
     missingClasses.addAll(previous.missingClasses);
     subtypeMap.putAll(previous.subtypeMap);
-    assert app instanceof DirectMappedDexApplication;
-  }
-
-  protected AppInfoWithSubtyping(DirectMappedDexApplication application, GraphLense lense) {
-    super(application, lense);
-    // Recompute subtype map if we have modified the graph.
-    populateSubtypeMap(application, dexItemFactory);
+    assert app() instanceof DirectMappedDexApplication;
   }
 
   private DirectMappedDexApplication getDirectApplication() {
     // TODO(herhut): Remove need for cast.
-    return (DirectMappedDexApplication) app;
+    return (DirectMappedDexApplication) app();
   }
 
   public Iterable<DexLibraryClass> libraryClasses() {
+    assert checkIfObsolete();
     return getDirectApplication().libraryClasses();
   }
 
   public Set<DexType> getMissingClasses() {
+    assert checkIfObsolete();
     return Collections.unmodifiableSet(missingClasses);
   }
 
   public Set<DexType> subtypes(DexType type) {
+    assert checkIfObsolete();
     assert type.isClassType();
     ImmutableSet<DexType> subtypes = subtypeMap.get(type);
     return subtypes == null ? ImmutableSet.of() : subtypes;
@@ -84,7 +82,7 @@
         holderClass.superType.addDirectSubtype(holder);
       } else {
         // We found java.lang.Object
-        assert dexItemFactory.objectType == holder;
+        assert dexItemFactory().objectType == holder;
       }
       for (DexType inter : holderClass.interfaces.values) {
         populateSuperType(map, inter, baseClass, definitions);
@@ -98,8 +96,8 @@
         missingClasses.add(holder);
       }
       // The subtype chain is broken, at least make this type a subtype of Object.
-      if (holder != dexItemFactory.objectType) {
-        dexItemFactory.objectType.addDirectSubtype(holder);
+      if (holder != dexItemFactory().objectType) {
+        dexItemFactory().objectType.addDirectSubtype(holder);
       }
     }
   }
@@ -119,6 +117,7 @@
 
   // For mapping invoke virtual instruction to target methods.
   public Set<DexEncodedMethod> lookupVirtualTargets(DexMethod method) {
+    assert checkIfObsolete();
     if (method.holder.isArrayType()) {
       // For javac output this will only be clone(), but in general the methods from Object can
       // be invoked with an array type holder.
@@ -169,6 +168,7 @@
    */
   @Override
   public DexEncodedMethod lookupSuperTarget(DexMethod method, DexType invocationContext) {
+    assert checkIfObsolete();
     if (!invocationContext.isSubtypeOf(method.holder, this)) {
       DexClass contextClass = definitionFor(invocationContext);
       throw new CompilationError(
@@ -179,11 +179,13 @@
   }
 
   protected boolean hasAnyInstantiatedLambdas(DexType type) {
+    assert checkIfObsolete();
     return true; // Don't know, there might be.
   }
 
   // For mapping invoke interface instruction to target methods.
   public Set<DexEncodedMethod> lookupInterfaceTargets(DexMethod method) {
+    assert checkIfObsolete();
     // First check that there is a target for this invoke-interface to hit. If there is none,
     // this will fail at runtime.
     ResolutionResult topTarget = resolveMethodOnInterface(method.holder, method);
@@ -250,6 +252,7 @@
    * @return Methods implemented by the lambda expression that created the {@code callSite}.
    */
   public Set<DexEncodedMethod> lookupLambdaImplementedMethods(DexCallSite callSite) {
+    assert checkIfObsolete();
     List<DexType> callSiteInterfaces = LambdaDescriptor.getInterfaces(callSite, this);
     if (callSiteInterfaces == null || callSiteInterfaces.isEmpty()) {
       return Collections.emptySet();
@@ -290,24 +293,28 @@
   }
 
   public boolean isStringConcat(DexMethodHandle bootstrapMethod) {
+    assert checkIfObsolete();
     return bootstrapMethod.type.isInvokeStatic()
-        && (bootstrapMethod.asMethod() == dexItemFactory.stringConcatWithConstantsMethod
-            || bootstrapMethod.asMethod() == dexItemFactory.stringConcatMethod);
+        && (bootstrapMethod.asMethod() == dexItemFactory().stringConcatWithConstantsMethod
+            || bootstrapMethod.asMethod() == dexItemFactory().stringConcatMethod);
   }
 
   @Override
   public void registerNewType(DexType newType, DexType superType) {
+    assert checkIfObsolete();
     // Register the relationship between this type and its superType.
     superType.addDirectSubtype(newType);
   }
 
   @Override
   public boolean hasSubtyping() {
+    assert checkIfObsolete();
     return true;
   }
 
   @Override
   public AppInfoWithSubtyping withSubtyping() {
+    assert checkIfObsolete();
     return this;
   }
 }
diff --git a/src/main/java/com/android/tools/r8/graph/AppServices.java b/src/main/java/com/android/tools/r8/graph/AppServices.java
index ec22d85..474704a 100644
--- a/src/main/java/com/android/tools/r8/graph/AppServices.java
+++ b/src/main/java/com/android/tools/r8/graph/AppServices.java
@@ -98,7 +98,7 @@
 
     public AppServices build() {
       Iterable<ProgramResourceProvider> programResourceProviders =
-          appView.appInfo().app.programResourceProviders;
+          appView.appInfo().app().programResourceProviders;
       for (ProgramResourceProvider programResourceProvider : programResourceProviders) {
         DataResourceProvider dataResourceProvider =
             programResourceProvider.getDataResourceProvider();
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 6448896..8d20b1c 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -26,7 +26,7 @@
   private AppView(
       T appInfo, WholeProgramOptimizations wholeProgramOptimizations, InternalOptions options) {
     this.appInfo = appInfo;
-    this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory : null;
+    this.dexItemFactory = appInfo != null ? appInfo.dexItemFactory() : null;
     this.wholeProgramOptimizations = wholeProgramOptimizations;
     this.graphLense = GraphLense.getIdentityLense();
     this.options = options;
@@ -45,7 +45,12 @@
   }
 
   public void setAppInfo(T appInfo) {
+    assert !appInfo.isObsolete();
+    AppInfo previous = this.appInfo;
     this.appInfo = appInfo;
+    if (appInfo != previous) {
+      previous.markObsolete();
+    }
   }
 
   public AppServices appServices() {
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 0b104f5..c6b5348 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -666,21 +666,22 @@
     return false;
   }
 
-  public boolean isSerializable(AppInfo appInfo) {
-    return type.isSerializable(appInfo);
+  public boolean isSerializable(DexDefinitionSupplier definitions) {
+    return type.isSerializable(definitions);
   }
 
-  public boolean isExternalizable(AppInfo appInfo) {
-    return type.isExternalizable(appInfo);
+  public boolean isExternalizable(DexDefinitionSupplier definitions) {
+    return type.isExternalizable(definitions);
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
-    return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  public boolean classInitializationMayHaveSideEffects(DexDefinitionSupplier definitions) {
+    return classInitializationMayHaveSideEffects(definitions, Predicates.alwaysFalse());
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
+  public boolean classInitializationMayHaveSideEffects(
+      DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
     if (ignore.test(type)
-        || appInfo.dexItemFactory.libraryTypesWithoutStaticInitialization.contains(type)) {
+        || definitions.dexItemFactory().libraryTypesWithoutStaticInitialization.contains(type)) {
       return false;
     }
     if (hasNonTrivialClassInitializer()) {
@@ -689,21 +690,21 @@
     if (defaultValuesForStaticFieldsMayTriggerAllocation()) {
       return true;
     }
-    return initializationOfParentTypesMayHaveSideEffects(appInfo, ignore);
+    return initializationOfParentTypesMayHaveSideEffects(definitions, ignore);
   }
 
-  public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
-    return initializationOfParentTypesMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  public boolean initializationOfParentTypesMayHaveSideEffects(DexDefinitionSupplier definitions) {
+    return initializationOfParentTypesMayHaveSideEffects(definitions, Predicates.alwaysFalse());
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(
-      AppInfo appInfo, Predicate<DexType> ignore) {
+      DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
     for (DexType iface : interfaces.values) {
-      if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
+      if (iface.classInitializationMayHaveSideEffects(definitions, ignore)) {
         return true;
       }
     }
-    if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo, ignore)) {
+    if (superType != null && superType.classInitializationMayHaveSideEffects(definitions, ignore)) {
       return true;
     }
     return false;
diff --git a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
index 9c7b10a..ec1506a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
+++ b/src/main/java/com/android/tools/r8/graph/DexEncodedField.java
@@ -30,17 +30,17 @@
     this.staticValue = staticValue;
   }
 
-  public boolean isProgramField(AppInfo appInfo) {
+  public boolean isProgramField(DexDefinitionSupplier definitions) {
     if (field.holder.isClassType()) {
-      DexClass clazz = appInfo.definitionFor(field.holder);
+      DexClass clazz = definitions.definitionFor(field.holder);
       return clazz != null && clazz.isProgramClass();
     }
     return false;
   }
 
   @Override
-  public void collectIndexedItems(IndexedItemCollection indexedItems,
-      DexMethod method, int instructionOffset) {
+  public void collectIndexedItems(
+      IndexedItemCollection indexedItems, DexMethod method, int instructionOffset) {
     field.collectIndexedItems(indexedItems, method, instructionOffset);
     annotations.collectIndexedItems(indexedItems, method, instructionOffset);
     if (accessFlags.isStatic()) {
diff --git a/src/main/java/com/android/tools/r8/graph/DexType.java b/src/main/java/com/android/tools/r8/graph/DexType.java
index efd4a2b..83ab503 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -116,21 +116,24 @@
     return hierarchyLevel == INTERFACE_LEVEL;
   }
 
-  public boolean isExternalizable(AppInfo appInfo) {
-    return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.externalizableType);
+  public boolean isExternalizable(DexDefinitionSupplier definitions) {
+    return implementedInterfaces(definitions)
+        .contains(definitions.dexItemFactory().externalizableType);
   }
 
-  public boolean isSerializable(AppInfo appInfo) {
-    return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
+  public boolean isSerializable(DexDefinitionSupplier definitions) {
+    return implementedInterfaces(definitions)
+        .contains(definitions.dexItemFactory().serializableType);
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
-    return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  public boolean classInitializationMayHaveSideEffects(DexDefinitionSupplier definitions) {
+    return classInitializationMayHaveSideEffects(definitions, Predicates.alwaysFalse());
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
-    DexClass clazz = appInfo.definitionFor(this);
-    return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
+  public boolean classInitializationMayHaveSideEffects(
+      DexDefinitionSupplier definitions, Predicate<DexType> ignore) {
+    DexClass clazz = definitions.definitionFor(this);
+    return clazz == null || clazz.classInitializationMayHaveSideEffects(definitions, ignore);
   }
 
   public boolean initializationOfParentTypesMayHaveSideEffects(AppInfo appInfo) {
diff --git a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
index 75a9726..b8a8f04 100644
--- a/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
+++ b/src/main/java/com/android/tools/r8/ir/analysis/TypeChecker.java
@@ -30,10 +30,10 @@
  */
 public class TypeChecker {
 
-  private final AppInfo appInfo;
+  private final AppView<? extends AppInfo> appView;
 
   public TypeChecker(AppView<? extends AppInfo> appView) {
-    this.appInfo = appView.appInfo();
+    this.appView = appView;
   }
 
   public boolean check(IRCode code) {
@@ -72,8 +72,9 @@
             ? instruction.asInstancePut().value()
             : instruction.asStaticPut().inValue();
     TypeLatticeElement valueType = value.getTypeLattice();
-    TypeLatticeElement fieldType = TypeLatticeElement.fromDexType(
-        instruction.getField().type, valueType.nullability(), appInfo);
+    TypeLatticeElement fieldType =
+        TypeLatticeElement.fromDexType(
+            instruction.getField().type, valueType.nullability(), appView);
     if (isSubtypeOf(valueType, fieldType)) {
       return true;
     }
@@ -81,7 +82,7 @@
     if (fieldType.isClassType() && valueType.isReference()) {
       // Interface types are treated like Object according to the JVM spec.
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-4.html#jvms-4.10.1.2-100
-      DexClass clazz = appInfo.definitionFor(instruction.getField().type);
+      DexClass clazz = appView.definitionFor(instruction.getField().type);
       return clazz != null && clazz.isInterface();
     }
 
@@ -90,15 +91,16 @@
 
   public boolean check(Throw instruction) {
     TypeLatticeElement valueType = instruction.exception().getTypeLattice();
-    TypeLatticeElement throwableType = TypeLatticeElement.fromDexType(
-        appInfo.dexItemFactory.throwableType, valueType.nullability(), appInfo);
+    TypeLatticeElement throwableType =
+        TypeLatticeElement.fromDexType(
+            appView.dexItemFactory().throwableType, valueType.nullability(), appView);
     return isSubtypeOf(valueType, throwableType);
   }
 
   private boolean isSubtypeOf(
       TypeLatticeElement expectedSubtype, TypeLatticeElement expectedSupertype) {
     return (expectedSubtype.isNullType() && expectedSupertype.isReference())
-        || expectedSubtype.lessThanOrEqual(expectedSupertype, appInfo)
-        || expectedSubtype.isBasedOnMissingClass(appInfo);
+        || expectedSubtype.lessThanOrEqual(expectedSupertype, appView)
+        || expectedSubtype.isBasedOnMissingClass(appView);
   }
 }
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
index a3a230d..b5bbf89 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlock.java
@@ -1307,7 +1307,7 @@
   public static BasicBlock createRethrowBlock(
       IRCode code, Position position, DexType guard, AppView<? extends AppInfo> appView) {
     TypeLatticeElement guardTypeLattice =
-        TypeLatticeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView.appInfo());
+        TypeLatticeElement.fromDexType(guard, Nullability.definitelyNotNull(), appView);
     BasicBlock block = new BasicBlock();
     MoveException moveException =
         new MoveException(
diff --git a/src/main/java/com/android/tools/r8/ir/code/Binop.java b/src/main/java/com/android/tools/r8/ir/code/Binop.java
index 78827cd..40ce694 100644
--- a/src/main/java/com/android/tools/r8/ir/code/Binop.java
+++ b/src/main/java/com/android/tools/r8/ir/code/Binop.java
@@ -53,11 +53,10 @@
       int leftRegister = allocator.getRegisterForValue(leftValue(), getNumber());
       int rightRegister = allocator.getRegisterForValue(rightValue(), getNumber());
       int destRegister = allocator.getRegisterForValue(outValue, getNumber());
-      return ((leftRegister == destRegister) ||
-          (isCommutative() && rightRegister == destRegister)) &&
-          leftRegister <= U4BIT_MAX &&
-          rightRegister <= U4BIT_MAX &&
-          !(allocator.getOptions().canHaveMul2AddrBug() && isMul());
+      return ((leftRegister == destRegister) || (isCommutative() && rightRegister == destRegister))
+          && leftRegister <= U4BIT_MAX
+          && rightRegister <= U4BIT_MAX
+          && !(allocator.options().canHaveMul2AddrBug() && isMul());
     }
     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 9c28e92..4714251 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
@@ -436,7 +436,7 @@
     }
     // In debug mode or if the instruction can throw we must account for positions, in release mode
     // we do want to share non-throwing instructions even if their positions differ.
-    if (instructionTypeCanThrow() || allocator.getOptions().debug) {
+    if (instructionTypeCanThrow() || allocator.options().debug) {
       if (!identicalNonValueParts(other)) {
         return false;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/code/StackValue.java b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
index 18019dc..40407db 100644
--- a/src/main/java/com/android/tools/r8/ir/code/StackValue.java
+++ b/src/main/java/com/android/tools/r8/ir/code/StackValue.java
@@ -25,8 +25,7 @@
       TypeInfo typeInfo, int height, AppView<? extends AppInfo> appView) {
     return new StackValue(
         typeInfo,
-        TypeLatticeElement.fromDexType(
-            typeInfo.getDexType(), Nullability.maybeNull(), appView.appInfo()),
+        TypeLatticeElement.fromDexType(typeInfo.getDexType(), Nullability.maybeNull(), appView),
         height);
   }
 
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 37102ef..6a6cd08 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
@@ -148,8 +148,10 @@
 
   private List<Action> onWaveDoneActions = null;
 
-  // The argument `appView` is only available when full program optimizations are allowed
-  // (i.e., when running R8).
+  /**
+   * The argument `appView` is used to determine if whole program optimizations are allowed or not
+   * (i.e., whether we are running R8). See {@link AppView#enableWholeProgramOptimizations()}.
+   */
   public IRConverter(
       AppView<? extends AppInfo> appView,
       Timing timing,
@@ -727,14 +729,13 @@
   private DexType computeOutlineClassType() {
     DexType result;
     int count = 0;
-    AppInfo appInfo = appView.appInfo();
     do {
       String name = OutlineOptions.CLASS_NAME + (count == 0 ? "" : Integer.toString(count));
       count++;
-      result = appInfo.dexItemFactory.createType(DescriptorUtils.javaTypeToDescriptor(name));
-    } while (appInfo.definitionFor(result) != null);
+      result = appView.dexItemFactory().createType(DescriptorUtils.javaTypeToDescriptor(name));
+    } while (appView.definitionFor(result) != null);
     // Register the newly generated type in the subtyping hierarchy, if we have one.
-    appInfo.registerNewType(result, appInfo.dexItemFactory.objectType);
+    appView.appInfo().registerNewType(result, appView.dexItemFactory().objectType);
     return result;
   }
 
@@ -1270,8 +1271,7 @@
     deadCodeRemover.run(code);
     materializeInstructionBeforeLongOperationsWorkaround(code);
     workaroundForwardingInitializerBug(code);
-    LinearScanRegisterAllocator registerAllocator =
-        new LinearScanRegisterAllocator(appView.appInfo(), code, options);
+    LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(appView, code);
     registerAllocator.allocateRegisters();
     if (options.canHaveExceptionTargetingLoopHeaderBug()) {
       codeRewriter.workaroundExceptionTargetingLoopHeaderBug(code);
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
index 7b58034..2a35299 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/LensCodeRewriter.java
@@ -91,7 +91,6 @@
    * Replace type appearances, invoke targets and field accesses with actual definitions.
    */
   public void rewrite(IRCode code, DexEncodedMethod method) {
-    AppInfo appInfo = appView.appInfo();
     GraphLense graphLense = appView.graphLense();
 
     Set<Value> newSSAValues = Sets.newIdentityHashSet();
@@ -112,12 +111,16 @@
           InvokeCustom invokeCustom = current.asInvokeCustom();
           DexCallSite callSite = invokeCustom.getCallSite();
           DexProto newMethodProto =
-              appInfo.dexItemFactory.applyClassMappingToProto(
-                  callSite.methodProto, graphLense::lookupType, protoFixupCache);
+              appView
+                  .dexItemFactory()
+                  .applyClassMappingToProto(
+                      callSite.methodProto, graphLense::lookupType, protoFixupCache);
           DexMethodHandle newBootstrapMethod = rewriteDexMethodHandle(
               callSite.bootstrapMethod, method, NOT_ARGUMENT_TO_LAMBDA_METAFACTORY);
           boolean isLambdaMetaFactory =
-              appInfo.dexItemFactory.isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
+              appView
+                  .dexItemFactory()
+                  .isLambdaMetafactoryMethod(callSite.bootstrapMethod.asMethod());
           MethodHandleUse methodHandleUse = isLambdaMetaFactory
               ? ARGUMENT_TO_LAMBDA_METAFACTORY
               : NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
@@ -127,8 +130,10 @@
               || newBootstrapMethod != callSite.bootstrapMethod
               || !newArgs.equals(callSite.bootstrapArgs)) {
             DexCallSite newCallSite =
-                appInfo.dexItemFactory.createCallSite(
-                    callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
+                appView
+                    .dexItemFactory()
+                    .createCallSite(
+                        callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
             InvokeCustom newInvokeCustom = new InvokeCustom(newCallSite, invokeCustom.outValue(),
                 invokeCustom.inValues());
             iterator.replaceCurrentInstruction(newInvokeCustom);
@@ -147,16 +152,17 @@
           DexMethod invokedMethod = invoke.getInvokedMethod();
           DexType invokedHolder = invokedMethod.holder;
           if (invokedHolder.isArrayType()) {
-            DexType baseType = invokedHolder.toBaseType(appInfo.dexItemFactory);
+            DexType baseType = invokedHolder.toBaseType(appView.dexItemFactory());
             DexType mappedBaseType = graphLense.lookupType(baseType);
             if (baseType != mappedBaseType) {
               DexType mappedHolder =
-                  invokedHolder.replaceBaseType(mappedBaseType, appInfo.dexItemFactory);
+                  invokedHolder.replaceBaseType(mappedBaseType, appView.dexItemFactory());
               // Just reuse proto and name, as no methods on array types can be renamed nor
               // change signature.
               DexMethod actualTarget =
-                  appInfo.dexItemFactory.createMethod(
-                      mappedHolder, invokedMethod.proto, invokedMethod.name);
+                  appView
+                      .dexItemFactory()
+                      .createMethod(mappedHolder, invokedMethod.proto, invokedMethod.name);
               Invoke newInvoke =
                   Invoke.create(
                       VIRTUAL,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index 1bb56ea..857de34 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -470,11 +470,11 @@
     abstract boolean ensureAccessibility();
 
     DexClass definitionFor(DexType type) {
-      return rewriter.converter.appView.appInfo().app.definitionFor(type);
+      return rewriter.converter.appView.appInfo().app().definitionFor(type);
     }
 
     DexProgramClass programDefinitionFor(DexType type) {
-      return rewriter.converter.appView.appInfo().app.programDefinitionFor(type);
+      return rewriter.converter.appView.appInfo().app().programDefinitionFor(type);
     }
   }
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index 235c96d..20becfb 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -237,7 +237,7 @@
       return LambdaDescriptor.MATCH_FAILED;
     }
 
-    DexItemFactory factory = appInfo.dexItemFactory;
+    DexItemFactory factory = appInfo.dexItemFactory();
     DexMethod bootstrapMethod = callSite.bootstrapMethod.asMethod();
     if (!factory.isLambdaMetafactoryMethod(bootstrapMethod)) {
       // It is not a lambda, thus no need to manage this call site.
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 92d7a9d..064f105 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
@@ -273,8 +273,7 @@
       // The out value might be empty in case it was optimized out.
       lambdaInstanceValue =
           code.createValue(
-              TypeLatticeElement.fromDexType(
-                  lambdaClass.type, Nullability.maybeNull(), appView.appInfo()));
+              TypeLatticeElement.fromDexType(lambdaClass.type, Nullability.maybeNull(), appView));
     }
 
     // For stateless lambdas we replace InvokeCustom instruction with StaticGet
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
index 4e7636c..e31924e 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/StringConcatRewriter.java
@@ -340,8 +340,7 @@
 
       // new-instance v0, StringBuilder
       TypeLatticeElement stringBuilderTypeLattice =
-          TypeLatticeElement.fromDexType(
-              factory.stringBuilderType, definitelyNotNull(), appView.appInfo());
+          TypeLatticeElement.fromDexType(factory.stringBuilderType, definitelyNotNull(), appView);
       Value sbInstance = code.createValue(stringBuilderTypeLattice);
       appendInstruction(new NewInstance(factory.stringBuilderType, sbInstance));
 
@@ -364,8 +363,7 @@
       if (concatValue == null) {
         // The out value might be empty in case it was optimized out.
         concatValue =
-            code.createValue(
-                TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull()));
+            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
       }
 
       // Replace the instruction.
@@ -444,8 +442,7 @@
       @Override
       Value getOrCreateValue() {
         Value value =
-            code.createValue(
-                TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull()));
+            code.createValue(TypeLatticeElement.stringClassType(appView, definitelyNotNull()));
         appendInstruction(
             new ConstString(
                 value,
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
index f8ebb06..566caf1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/TwrCloseResourceRewriter.java
@@ -49,12 +49,12 @@
 // tree shaking to remove them since now they should not be referenced.
 //
 public final class TwrCloseResourceRewriter {
+
   public static final String UTILITY_CLASS_NAME = "$r8$twr$utility";
   public static final String UTILITY_CLASS_DESCRIPTOR = "L$r8$twr$utility;";
 
   private final AppView<? extends AppInfo> appView;
   private final IRConverter converter;
-  private final DexItemFactory factory;
 
   private final DexMethod twrCloseResourceMethod;
 
@@ -63,13 +63,15 @@
   public TwrCloseResourceRewriter(AppView<? extends AppInfo> appView, IRConverter converter) {
     this.appView = appView;
     this.converter = converter;
-    this.factory = appView.dexItemFactory();
 
-    DexType twrUtilityClass = factory.createType(UTILITY_CLASS_DESCRIPTOR);
-    DexProto twrCloseResourceProto = factory.createProto(
-        factory.voidType, factory.throwableType, factory.objectType);
-    this.twrCloseResourceMethod = factory.createMethod(
-        twrUtilityClass, twrCloseResourceProto, factory.twrCloseResourceMethodName);
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
+    DexType twrUtilityClass = dexItemFactory.createType(UTILITY_CLASS_DESCRIPTOR);
+    DexProto twrCloseResourceProto =
+        dexItemFactory.createProto(
+            dexItemFactory.voidType, dexItemFactory.throwableType, dexItemFactory.objectType);
+    this.twrCloseResourceMethod =
+        dexItemFactory.createMethod(
+            twrUtilityClass, twrCloseResourceProto, dexItemFactory.twrCloseResourceMethodName);
   }
 
   // Rewrites calls to $closeResource() method. Can be invoked concurrently.
@@ -111,7 +113,7 @@
     //    right attributes, but it still does not guarantee much since we also
     //    need to look into code and doing this seems an overkill
     //
-    DexItemFactory dexItemFactory = appView.appInfo().dexItemFactory;
+    DexItemFactory dexItemFactory = appView.dexItemFactory();
     return original.name == dexItemFactory.twrCloseResourceMethodName
         && original.proto == dexItemFactory.twrCloseResourceMethodProto;
   }
@@ -137,7 +139,7 @@
             null,
             new SynthesizedOrigin("twr utility class", getClass()),
             ClassAccessFlags.fromSharedAccessFlags(Constants.ACC_PUBLIC | Constants.ACC_SYNTHETIC),
-            factory.objectType,
+            appView.dexItemFactory().objectType,
             DexTypeList.empty(),
             null,
             null,
@@ -145,9 +147,9 @@
             DexAnnotationSet.empty(),
             DexEncodedField.EMPTY_ARRAY,
             DexEncodedField.EMPTY_ARRAY,
-            new DexEncodedMethod[]{method},
+            new DexEncodedMethod[] {method},
             DexEncodedMethod.EMPTY_ARRAY,
-            factory.getSkipNameValidationForTesting(),
+            appView.dexItemFactory().getSkipNameValidationForTesting(),
             referencingClasses);
 
     code.setUpContext(utilityClass);
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 240ad73..d0bd538 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
@@ -1575,15 +1575,14 @@
 
   private boolean checkArgumentType(InvokeMethod invoke, int argumentIndex) {
     // TODO(sgjesse): Insert cast if required.
-    AppInfo appInfo = appView.appInfo();
     TypeLatticeElement returnType =
         TypeLatticeElement.fromDexType(
-            invoke.getInvokedMethod().proto.returnType, maybeNull(), appInfo);
+            invoke.getInvokedMethod().proto.returnType, maybeNull(), appView);
     TypeLatticeElement argumentType =
         TypeLatticeElement.fromDexType(
-            getArgumentType(invoke, argumentIndex), maybeNull(), appInfo);
+            getArgumentType(invoke, argumentIndex), maybeNull(), appView);
     return appView.enableWholeProgramOptimizations()
-        ? argumentType.lessThanOrEqual(returnType, appInfo)
+        ? argumentType.lessThanOrEqual(returnType, appView)
         : argumentType.equals(returnType);
   }
 
@@ -1879,7 +1878,6 @@
   // Returns true if the given check-cast instruction was removed.
   private boolean removeCheckCastInstructionIfTrivial(
       CheckCast checkCast, InstructionIterator it, IRCode code) {
-    AppInfo appInfo = appView.appInfo();
     Value inValue = checkCast.object();
     Value outValue = checkCast.outValue();
     DexType castType = checkCast.getType();
@@ -1916,11 +1914,11 @@
     TypeLatticeElement inTypeLattice = inValue.getTypeLattice();
     TypeLatticeElement outTypeLattice = outValue.getTypeLattice();
     TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(castType, inTypeLattice.nullability(), appInfo);
+        TypeLatticeElement.fromDexType(castType, inTypeLattice.nullability(), appView);
 
     assert inTypeLattice.nullability().lessThanOrEqual(outTypeLattice.nullability());
 
-    if (inTypeLattice.lessThanOrEqual(castTypeLattice, appInfo)) {
+    if (inTypeLattice.lessThanOrEqual(castTypeLattice, appView)) {
       // 1) Trivial cast.
       //   A a = ...
       //   A a' = (A) a;
@@ -1928,7 +1926,7 @@
       //   A < B
       //   A a = ...
       //   B b = (B) a;
-      assert inTypeLattice.lessThanOrEqual(outTypeLattice, appInfo);
+      assert inTypeLattice.lessThanOrEqual(outTypeLattice, appView);
       removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
       return true;
     }
@@ -1944,19 +1942,18 @@
   }
 
   private boolean isTypeInaccessibleInCurrentContext(DexType type, DexEncodedMethod context) {
-    AppInfo appInfo = appView.appInfo();
-    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (baseType.isPrimitiveType()) {
       return false;
     }
-    DexClass clazz = appInfo.definitionFor(baseType);
+    DexClass clazz = appView.definitionFor(baseType);
     if (clazz == null) {
       // Conservatively say yes.
       return true;
     }
     ConstraintWithTarget classVisibility =
         ConstraintWithTarget.deriveConstraint(
-            context.method.holder, baseType, clazz.accessFlags, appInfo);
+            context.method.holder, baseType, clazz.accessFlags, appView);
     return classVisibility == ConstraintWithTarget.NEVER;
   }
 
@@ -1969,22 +1966,21 @@
       return false;
     }
 
-    AppInfo appInfo = appView.appInfo();
     Value inValue = instanceOf.value();
     TypeLatticeElement inType = inValue.getTypeLattice();
     TypeLatticeElement instanceOfType =
-        TypeLatticeElement.fromDexType(instanceOf.type(), inType.nullability(), appInfo);
+        TypeLatticeElement.fromDexType(instanceOf.type(), inType.nullability(), appView);
 
     InstanceOfResult result = InstanceOfResult.UNKNOWN;
     if (inType.isDefinitelyNull()) {
       result = InstanceOfResult.FALSE;
-    } else if (inType.lessThanOrEqual(instanceOfType, appInfo) && !inType.isNullable()) {
+    } else if (inType.lessThanOrEqual(instanceOfType, appView) && !inType.isNullable()) {
       result = InstanceOfResult.TRUE;
     } else if (!inValue.isPhi()
         && inValue.definition.isCreatingInstanceOrArray()
-        && instanceOfType.strictlyLessThan(inType, appInfo)) {
+        && instanceOfType.strictlyLessThan(inType, appView)) {
       result = InstanceOfResult.FALSE;
-    } else if (appInfo.hasLiveness()) {
+    } else if (appView.appInfo().hasLiveness()) {
       if (instanceOf.type().isClassType()
           && isNeverInstantiatedDirectlyOrIndirectly(instanceOf.type())) {
         // The type of the instance-of instruction is a program class, and is never instantiated
@@ -2018,13 +2014,12 @@
   }
 
   private boolean isNeverInstantiatedDirectlyOrIndirectly(DexType type) {
-    AppInfo appInfo = appView.appInfo();
-    assert appInfo.hasLiveness();
+    assert appView.appInfo().hasLiveness();
     assert type.isClassType();
-    DexClass clazz = appInfo.definitionFor(type);
+    DexClass clazz = appView.definitionFor(type);
     return clazz != null
         && clazz.isProgramClass()
-        && !appInfo.withLiveness().isInstantiatedDirectlyOrIndirectly(type);
+        && !appView.appInfo().withLiveness().isInstantiatedDirectlyOrIndirectly(type);
   }
 
   public static void removeOrReplaceByDebugLocalWrite(
@@ -3641,7 +3636,7 @@
 
   private Value addConstString(IRCode code, InstructionListIterator iterator, String s) {
     TypeLatticeElement typeLattice =
-        TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull());
+        TypeLatticeElement.stringClassType(appView, definitelyNotNull());
     Value value = code.createValue(typeLattice);
     ThrowingInfo throwingInfo =
         options.isGeneratingClassFiles() ? ThrowingInfo.NO_THROW : ThrowingInfo.CAN_THROW;
@@ -3675,8 +3670,7 @@
     DexType javaIoPrintStreamType = dexItemFactory.createType("Ljava/io/PrintStream;");
     Value out =
         code.createValue(
-            TypeLatticeElement.fromDexType(
-                javaIoPrintStreamType, definitelyNotNull(), appView.appInfo()));
+            TypeLatticeElement.fromDexType(javaIoPrintStreamType, definitelyNotNull(), appView));
 
     DexProto proto = dexItemFactory.createProto(dexItemFactory.voidType, dexItemFactory.objectType);
     DexMethod print = dexItemFactory.createMethod(javaIoPrintStreamType, proto, "print");
@@ -3741,9 +3735,7 @@
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, nul)));
         iterator = isNotNullBlock.listIterator();
         iterator.setInsertionPosition(position);
-        value =
-            code.createValue(
-                TypeLatticeElement.classClassType(appView.appInfo(), definitelyNotNull()));
+        value = code.createValue(TypeLatticeElement.classClassType(appView, definitelyNotNull()));
         iterator.add(new InvokeVirtual(dexItemFactory.objectMethods.getClass, value,
             ImmutableList.of(arguments.get(i))));
         iterator.add(new InvokeVirtual(print, null, ImmutableList.of(out, value)));
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 8fc16fe..a62c4a1 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
@@ -145,11 +145,11 @@
           TypeLatticeElement receiverTypeLattice = receiver.getTypeLattice();
           TypeLatticeElement castTypeLattice =
               TypeLatticeElement.fromDexType(
-                  holderType, receiverTypeLattice.nullability(), appView.appInfo());
+                  holderType, receiverTypeLattice.nullability(), appView);
           // Avoid adding trivial cast and up-cast.
           // We should not use strictlyLessThan(castType, receiverType), which detects downcast,
           // due to side-casts, e.g., A (unused) < I, B < I, and cast from A to B.
-          if (!receiverTypeLattice.lessThanOrEqual(castTypeLattice, appView.appInfo())) {
+          if (!receiverTypeLattice.lessThanOrEqual(castTypeLattice, appView)) {
             Value newReceiver = null;
             // If this value is ever downcast'ed to the same holder type before, and that casted
             // value is safely accessible, i.e., the current line is dominated by that cast, use it.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
index 55876af..bf92dfa 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Inliner.java
@@ -63,12 +63,12 @@
   public Inliner(AppView<AppInfoWithLiveness> appView, MainDexClasses mainDexClasses) {
     this.appView = appView;
     this.mainDexClasses = mainDexClasses;
-    fillInBlackList(appView.appInfo());
+    fillInBlackList();
   }
 
-  private void fillInBlackList(AppInfoWithLiveness appInfo) {
-    blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwParameterIsNullException);
-    blackList.add(appInfo.dexItemFactory.kotlin.intrinsics.throwNpe);
+  private void fillInBlackList() {
+    blackList.add(appView.dexItemFactory().kotlin.intrinsics.throwParameterIsNullException);
+    blackList.add(appView.dexItemFactory().kotlin.intrinsics.throwNpe);
   }
 
   public boolean isBlackListed(DexMethod method) {
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 36e9f25..166ce52 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
@@ -94,7 +94,7 @@
         && rule.hasReturnValue() && rule.getReturnValue().isField()) {
       DexField field = rule.getReturnValue().getField();
       assert typeLattice
-          == TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView.appInfo());
+          == TypeLatticeElement.fromDexType(field.type, Nullability.maybeNull(), appView);
       DexEncodedField staticField = appView.appInfo().lookupStaticTarget(field.holder, field);
       if (staticField != null) {
         Value value = code.createValue(typeLattice, instruction.getLocalInfo());
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
index 5e32629..66a1106 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/PeepholeOptimizer.java
@@ -413,7 +413,7 @@
             changed = true;
             int otherPredIndex = blockToIndex.get(wrapper);
             BasicBlock otherPred = block.getPredecessors().get(otherPredIndex);
-            assert !allocator.getOptions().debug
+            assert !allocator.options().debug
                 || Objects.equals(pred.getPosition(), otherPred.getPosition());
             allocator.mergeBlocks(otherPred, pred);
             pred.clearCatchHandlers();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
index 8313b09..dbe3f71 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchUtils.java
@@ -72,7 +72,7 @@
     InvokeVirtual ordinalInvoke = index.asInvokeVirtual();
     DexMethod ordinalMethod = ordinalInvoke.getInvokedMethod();
     DexClass enumClass = appInfo.definitionFor(ordinalMethod.holder);
-    DexItemFactory dexItemFactory = appInfo.dexItemFactory;
+    DexItemFactory dexItemFactory = appInfo.dexItemFactory();
     // After member rebinding, enumClass will be the actual java.lang.Enum class.
     if (enumClass == null
         || (!enumClass.accessFlags.isEnum() && enumClass.type != dexItemFactory.enumType)
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
index ad5f3be..1a71b05 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UnusedArgumentsCollector.java
@@ -134,7 +134,7 @@
         }
         assert newIndex == parameters.length;
       }
-      return appView.appInfo().dexItemFactory.createProto(method.proto.returnType, parameters);
+      return appView.dexItemFactory().createProto(method.proto.returnType, parameters);
     }
 
     private boolean isMethodSignatureAvailable(DexMethod method) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
index 48967c3..a5d8c39 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/kotlin/KotlinLambdaGroupCodeStrategy.java
@@ -120,7 +120,7 @@
             group.getGroupClassType(),
             context.code.createValue(
                 TypeLatticeElement.fromDexType(
-                    newInstance.clazz, definitelyNotNull(), context.appView.appInfo())));
+                    newInstance.clazz, definitelyNotNull(), context.appView)));
     context.instructions().replaceCurrentInstruction(patchedNewInstance);
   }
 
@@ -167,7 +167,7 @@
     // Since all captured values of non-primitive types are stored in fields of type
     // java.lang.Object, we need to cast them to appropriate type to satisfy the verifier.
     TypeLatticeElement castTypeLattice =
-        TypeLatticeElement.fromDexType(fieldType, definitelyNotNull(), context.appView.appInfo());
+        TypeLatticeElement.fromDexType(fieldType, definitelyNotNull(), context.appView);
     Value newValue = context.code.createValue(castTypeLattice, newInstanceGet.getLocalInfo());
     newInstanceGet.outValue().replaceUsers(newValue);
     CheckCast cast = new CheckCast(newValue, newInstanceGet.outValue(), fieldType);
@@ -232,7 +232,7 @@
     return returnType == context.factory.voidType
         ? null
         : context.code.createValue(
-            TypeLatticeElement.fromDexType(returnType, maybeNull(), context.appView.appInfo()));
+            TypeLatticeElement.fromDexType(returnType, maybeNull(), context.appView));
   }
 
   private List<Value> mapInitializerArgs(
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
index ec23f05..7791af1 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/StaticizingProcessor.java
@@ -444,10 +444,9 @@
                   ? null
                   : code.createValue(
                       TypeLatticeElement.fromDexType(
-                          method.proto.returnType, maybeNull(), classStaticizer.appView.appInfo()),
+                          method.proto.returnType, maybeNull(), classStaticizer.appView),
                       outValue == null ? null : outValue.getLocalInfo());
-          it.replaceCurrentInstruction(
-              new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
+          it.replaceCurrentInstruction(new InvokeStatic(newMethod, newOutValue, invoke.inValues()));
         }
         continue;
       }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
index 35ddb6f..b305be4 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/string/StringOptimizer.java
@@ -120,7 +120,7 @@
         String sub = rcvString.substring(beginIndexValue, endIndexValue);
         Value stringValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull()),
+                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
                 invoke.getLocalInfo());
         it.replaceCurrentInstruction(
             new ConstString(stringValue, factory.createString(sub), throwingInfo));
@@ -335,7 +335,7 @@
       if (name != null) {
         Value stringValue =
             code.createValue(
-                TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull()),
+                TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
                 invoke.getLocalInfo());
         ConstString constString = new ConstString(stringValue, name, throwingInfo);
         it.replaceCurrentInstruction(constString);
@@ -401,7 +401,7 @@
         if (inType.isNullType()) {
           Value nullStringValue =
               code.createValue(
-                  TypeLatticeElement.stringClassType(appView.appInfo(), definitelyNotNull()),
+                  TypeLatticeElement.stringClassType(appView, definitelyNotNull()),
                   invoke.getLocalInfo());
           ConstString nullString =
               new ConstString(nullStringValue, factory.createString("null"), throwingInfo);
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
index c0008dc..98ca2a2 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/LinearScanRegisterAllocator.java
@@ -11,6 +11,7 @@
 import com.android.tools.r8.dex.Constants;
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DebugLocalInfo;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.Add;
@@ -122,14 +123,12 @@
     }
   }
 
-  // App info to be able to create types.
-  private final AppInfo appInfo;
+  // App view to be able to create types and access the options.
+  private final AppView<? extends AppInfo> appView;
   // The code for which to allocate registers.
   private final IRCode code;
   // Number of registers used for arguments.
   protected final int numberOfArgumentRegisters;
-  // Compiler options.
-  private final InternalOptions options;
 
   // Mapping from basic blocks to the set of values live at entry to that basic block.
   private Map<BasicBlock, LiveAtEntrySets> liveAtEntrySets;
@@ -185,10 +184,9 @@
     return numberOfArgumentRegisters;
   }
 
-  public LinearScanRegisterAllocator(AppInfo appInfo, IRCode code, InternalOptions options) {
-    this.appInfo = appInfo;
+  public LinearScanRegisterAllocator(AppView<? extends AppInfo> appView, IRCode code) {
+    this.appView = appView;
     this.code = code;
-    this.options = options;
     int argumentRegisters = 0;
     for (Instruction instruction : code.entryBlock().getInstructions()) {
       if (instruction.isArgument()) {
@@ -223,7 +221,7 @@
     // register allocation. We just treat the method as being in debug mode in order to keep
     // locals alive for their entire live range. In release mode the liveness is all that matters
     // and we do not actually want locals information in the output.
-    if (options.debug) {
+    if (options().debug) {
       computeDebugInfo(blocks);
     } else if (code.method.getOptimizationInfo().isReachabilitySensitive()) {
       InstructionIterator it = code.instructionIterator();
@@ -641,8 +639,8 @@
   }
 
   @Override
-  public InternalOptions getOptions() {
-    return options;
+  public InternalOptions options() {
+    return appView.options();
   }
 
   private ImmutableList<BasicBlock> computeLivenessInformation() {
@@ -676,8 +674,9 @@
 
     switch (mode) {
       case ALLOW_ARGUMENT_REUSE_U4BIT:
-        if (!succeeded || highestUsedRegister() > Constants.U4BIT_MAX
-            || options.testing.alwaysUsePessimisticRegisterAllocation) {
+        if (!succeeded
+            || highestUsedRegister() > Constants.U4BIT_MAX
+            || options().testing.alwaysUsePessimisticRegisterAllocation) {
           // Redo allocation in mode ALLOW_ARGUMENT_REUSE_U8BIT. This may in principle also fail.
           // It is extremely rare that a method will use more than 256 registers, though.
           result = performAllocation(ArgumentReuseMode.ALLOW_ARGUMENT_REUSE_U8BIT, true);
@@ -699,7 +698,7 @@
         computeUnusedRegisters();
 
         if (highestUsedRegister() > Constants.U8BIT_MAX
-            || options.testing.alwaysUsePessimisticRegisterAllocation) {
+            || options().testing.alwaysUsePessimisticRegisterAllocation) {
           // Redo allocation in mode ALLOW_ARGUMENT_REUSE_U16BIT. This always succeed.
           unusedRegisters = null;
           result = performAllocation(ArgumentReuseMode.ALLOW_ARGUMENT_REUSE_U16BIT, true);
@@ -1423,7 +1422,7 @@
   // We work around that bug by disallowing aget-wide with the same array
   // and result register.
   private boolean needsArrayGetWideWorkaround(LiveIntervals intervals) {
-    if (options.canUseSameArrayAndResultRegisterInArrayGetWide()) {
+    if (options().canUseSameArrayAndResultRegisterInArrayGetWide()) {
       return false;
     }
     if (intervals.requiredRegisters() == 1) {
@@ -1455,7 +1454,7 @@
   }
 
   private boolean needsSingleResultOverlappingLongOperandsWorkaround(LiveIntervals intervals) {
-    if (!options.canHaveCmpLongBug() && !options.canHaveLongToIntBug()) {
+    if (!options().canHaveCmpLongBug() && !options().canHaveLongToIntBug()) {
       return false;
     }
     if (intervals.requiredRegisters() == 2) {
@@ -1516,7 +1515,7 @@
   // Dalvik would add v0 and v2 and write that to v3. It would then read v1 and v3 and produce
   // the wrong result.
   private boolean needsLongResultOverlappingLongOperandsWorkaround(LiveIntervals intervals) {
-    if (!options.canHaveOverlappingLongRegisterBug()) {
+    if (!options().canHaveOverlappingLongRegisterBug()) {
       return false;
     }
     if (intervals.requiredRegisters() == 1) {
@@ -1651,7 +1650,7 @@
     // Set all free positions for possible registers to max integer.
     RegisterPositions freePositions = new RegisterPositions(registerConstraint + 1);
 
-    if ((options.debug || code.method.getOptimizationInfo().isReachabilitySensitive())
+    if ((options().debug || code.method.getOptimizationInfo().isReachabilitySensitive())
         && !code.method.accessFlags.isStatic()) {
       // If we are generating debug information or if the method is reachability sensitive,
       // we pin the this value register. The debugger expects to always be able to find it in
@@ -2365,7 +2364,7 @@
   private void insertMoves() {
     computeRematerializableBits();
 
-    SpillMoveSet spillMoves = new SpillMoveSet(this, code, appInfo);
+    SpillMoveSet spillMoves = new SpillMoveSet(this, code, appView);
     for (LiveIntervals intervals : liveIntervals) {
       if (intervals.hasSplits()) {
         LiveIntervals current = intervals;
@@ -2507,12 +2506,12 @@
   }
 
   private void computeLiveRanges() {
-    computeLiveRanges(options, code, liveAtEntrySets, liveIntervals);
+    computeLiveRanges(options(), code, liveAtEntrySets, liveIntervals);
     // Art VMs before Android M assume that the register for the receiver never changes its value.
     // This assumption is used during verification. Allowing the receiver register to be
     // overwritten can therefore lead to verification errors. If we could be targeting one of these
     // VMs we block the receiver register throughout the method.
-    if ((options.canHaveThisTypeVerifierBug() || options.canHaveThisJitCodeDebuggingBug())
+    if ((options().canHaveThisTypeVerifierBug() || options().canHaveThisJitCodeDebuggingBug())
         && !code.method.accessFlags.isStatic()) {
       for (Instruction instruction : code.entryBlock().getInstructions()) {
         if (instruction.isArgument() && instruction.outValue().isThis()) {
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
index 43b41e5..9cdc715 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/RegisterAllocator.java
@@ -13,7 +13,8 @@
   int registersUsed();
   int getRegisterForValue(Value value, int instructionNumber);
   int getArgumentOrAllocateRegisterForValue(Value value, int instructionNumber);
-  InternalOptions getOptions();
+
+  InternalOptions options();
 
   void mergeBlocks(BasicBlock kept, BasicBlock removed);
 
diff --git a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
index aad705f..c7dd142 100644
--- a/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
+++ b/src/main/java/com/android/tools/r8/ir/regalloc/SpillMoveSet.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.regalloc;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.ir.analysis.type.Nullability;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -42,10 +43,11 @@
   // The number of temporary registers used for parallel moves when scheduling the moves.
   private int usedTempRegisters = 0;
 
-  public SpillMoveSet(LinearScanRegisterAllocator allocator, IRCode code, AppInfo appInfo) {
+  public SpillMoveSet(
+      LinearScanRegisterAllocator allocator, IRCode code, AppView<? extends AppInfo> appView) {
     this.allocator = allocator;
     this.code = code;
-    this.objectType = TypeLatticeElement.objectClassType(appInfo, Nullability.maybeNull());
+    this.objectType = TypeLatticeElement.objectClassType(appView, Nullability.maybeNull());
     for (BasicBlock block : code.blocks) {
       blockStartMap.put(block.entry().getNumber(), block);
     }
diff --git a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
index 0f9b571..c080a09 100644
--- a/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
+++ b/src/main/java/com/android/tools/r8/naming/SourceFileRewriter.java
@@ -4,13 +4,14 @@
 package com.android.tools.r8.naming;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDebugEvent;
 import com.android.tools.r8.graph.DexDebugEvent.SetFile;
 import com.android.tools.r8.graph.DexDebugInfo;
 import com.android.tools.r8.graph.DexString;
-import com.android.tools.r8.utils.InternalOptions;
+import com.android.tools.r8.shaking.ProguardConfiguration;
 import java.util.Arrays;
 
 /**
@@ -20,27 +21,25 @@
  */
 public class SourceFileRewriter {
 
-  private final AppInfo appInfo;
-  private final InternalOptions options;
+  private final AppView<? extends AppInfo> appView;
 
-  public SourceFileRewriter(AppInfo appInfo, InternalOptions options) {
-    this.appInfo = appInfo;
-    this.options = options;
+  public SourceFileRewriter(AppView<? extends AppInfo> appView) {
+    this.appView = appView;
   }
 
   public void run() {
-    String renameSourceFile = options.getProguardConfiguration().getRenameSourceFileAttribute();
+    ProguardConfiguration proguardConfiguration = appView.options().getProguardConfiguration();
+    String renameSourceFile = proguardConfiguration.getRenameSourceFileAttribute();
     // Return early if a user wants to keep the current source file attribute as-is.
-    if (renameSourceFile == null
-        && options.getProguardConfiguration().getKeepAttributes().sourceFile) {
+    if (renameSourceFile == null && proguardConfiguration.getKeepAttributes().sourceFile) {
       return;
     }
     // Now, the user wants either to remove source file attribute or to rename it.
     DexString dexRenameSourceFile =
         renameSourceFile == null
-            ? appInfo.dexItemFactory.createString("")
-            : appInfo.dexItemFactory.createString(renameSourceFile);
-    for (DexClass clazz : appInfo.classes()) {
+            ? appView.dexItemFactory().createString("")
+            : appView.dexItemFactory().createString(renameSourceFile);
+    for (DexClass clazz : appView.appInfo().classes()) {
       clazz.sourceFile = dexRenameSourceFile;
       clazz.forEachMethod(encodedMethod -> {
         // Abstract methods do not have code_item.
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 20f4bc2..4187099 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
@@ -29,7 +29,6 @@
 public class GenericSignatureRewriter {
 
   private final AppView<AppInfoWithLiveness> appView;
-  private final AppInfoWithLiveness appInfo;
   private final Map<DexType, DexString> renaming;
   private final Reporter reporter;
 
@@ -40,7 +39,6 @@
   public GenericSignatureRewriter(
       AppView<AppInfoWithLiveness> appView, Map<DexType, DexString> renaming) {
     this.appView = appView;
-    this.appInfo = appView.appInfo();
     this.renaming = renaming;
     this.reporter = appView.options().reporter;
   }
@@ -50,7 +48,7 @@
     final GenericSignatureParser<DexType> genericSignatureParser =
         new GenericSignatureParser<>(genericSignatureCollector);
 
-    for (DexClass clazz : appInfo.classes()) {
+    for (DexClass clazz : appView.appInfo().classes()) {
       clazz.annotations =
           rewriteGenericSignatures(
               clazz.annotations,
@@ -86,13 +84,12 @@
     int invalid = VALID;
     for (int i = 0; i < annotations.annotations.length && invalid == VALID; i++) {
       DexAnnotation annotation = annotations.annotations[i];
-      if (DexAnnotation.isSignatureAnnotation(annotation, appInfo.dexItemFactory)) {
+      if (DexAnnotation.isSignatureAnnotation(annotation, appView.dexItemFactory())) {
         String signature = DexAnnotation.getSignature(annotation);
         try {
           parser.accept(signature);
-          annotations.annotations[i] = DexAnnotation.createSignatureAnnotation(
-              collector.get(),
-              appInfo.dexItemFactory);
+          annotations.annotations[i] =
+              DexAnnotation.createSignatureAnnotation(collector.get(), appView.dexItemFactory());
         } catch (GenericSignatureFormatError e) {
           parseError.accept(signature, e);
           invalid = i;
@@ -159,10 +156,10 @@
 
     @Override
     public DexType parsedTypeName(String name) {
-      DexType type = appInfo.dexItemFactory.createType(getDescriptorFromClassBinaryName(name));
+      DexType type = appView.dexItemFactory().createType(getDescriptorFromClassBinaryName(name));
       type = appView.graphLense().lookupType(type);
-      if (appInfo.wasPruned(type)) {
-        type = appInfo.dexItemFactory.objectType;
+      if (appView.appInfo().wasPruned(type)) {
+        type = appView.dexItemFactory().objectType;
       }
       DexString renamedDescriptor = renaming.getOrDefault(type, type.descriptor);
       renamedSignature.append(getClassBinaryNameFromDescriptor(renamedDescriptor.toString()));
@@ -174,11 +171,13 @@
       assert enclosingType.isClassType();
       String enclosingDescriptor = enclosingType.toDescriptorString();
       DexType type =
-          appInfo.dexItemFactory.createType(
-              getDescriptorFromClassBinaryName(
-                  getClassBinaryNameFromDescriptor(enclosingDescriptor)
-                      + DescriptorUtils.INNER_CLASS_SEPARATOR
-                      + name));
+          appView
+              .dexItemFactory()
+              .createType(
+                  getDescriptorFromClassBinaryName(
+                      getClassBinaryNameFromDescriptor(enclosingDescriptor)
+                          + DescriptorUtils.INNER_CLASS_SEPARATOR
+                          + name));
       String enclosingRenamedBinaryName =
           getClassBinaryNameFromDescriptor(
               renaming.getOrDefault(enclosingType, enclosingType.descriptor).toString());
diff --git a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
index 0bdbcee..f5250d8 100644
--- a/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
+++ b/src/main/java/com/android/tools/r8/shaking/AbstractMethodRemover.java
@@ -31,7 +31,7 @@
 
   public void run() {
     assert scope.getParent() == null;
-    processClass(appInfo.dexItemFactory.objectType);
+    processClass(appInfo.dexItemFactory().objectType);
   }
 
   private void processClass(DexType type) {
diff --git a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
index 07a62a8..3d8afb2 100644
--- a/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
+++ b/src/main/java/com/android/tools/r8/shaking/DiscardedChecker.java
@@ -5,6 +5,7 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexDefinition;
@@ -31,17 +32,16 @@
     this.options = options;
   }
 
-  public DiscardedChecker(
-      RootSet rootSet, Set<DexType> types, AppInfo appInfo, InternalOptions options) {
+  public DiscardedChecker(RootSet rootSet, Set<DexType> types, AppView<? extends AppInfo> appView) {
     this.checkDiscarded = rootSet.checkDiscarded;
     this.classes = new ArrayList<>();
     types.forEach(
         type -> {
-          DexClass clazz = appInfo.definitionFor(type);
+          DexClass clazz = appView.definitionFor(type);
           assert clazz.isProgramClass();
           this.classes.add(clazz.asProgramClass());
         });
-    this.options = options;
+    this.options = appView.options();
   }
 
   public void run() {
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 d164d91..64624d3 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -336,7 +336,7 @@
   }
 
   private void enqueueRootItem(Entry<DexReference, Set<ProguardKeepRule>> root) {
-    DexDefinition item = appInfo.definitionFor(root.getKey());
+    DexDefinition item = appView.definitionFor(root.getKey());
     if (item != null) {
       enqueueRootItem(item, root.getValue());
     } else {
@@ -377,7 +377,7 @@
                   clazz.getDefaultInitializer(),
                   KeepReason.dueToProguardCompatibilityKeepRule(compatRule)));
         }
-        if (clazz.isExternalizable(appInfo)) {
+        if (clazz.isExternalizable(appView)) {
           workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
         }
       }
@@ -392,11 +392,11 @@
   }
 
   private void enqueueFirstNonSerializableClassInitializer(DexClass clazz, KeepReason reason) {
-    assert clazz.isProgramClass() && clazz.isSerializable(appInfo);
+    assert clazz.isProgramClass() && clazz.isSerializable(appView);
     // Climb up the class hierarchy. Break out if the definition is not found, or hit the library
     // classes which are kept by definition, or encounter the first non-serializable class.
-    while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appInfo)) {
-      clazz = appInfo.definitionFor(clazz.superType);
+    while (clazz != null && clazz.isProgramClass() && clazz.isSerializable(appView)) {
+      clazz = appView.definitionFor(clazz.superType);
     }
     if (clazz != null && clazz.isProgramClass() && clazz.hasDefaultInitializer()) {
       workList.add(Action.markMethodLive(clazz.getDefaultInitializer(), reason));
@@ -413,7 +413,11 @@
       if (dependentItem.isDexType()) {
         continue;
       }
-      DexDefinition dependentDefinition = appInfo.definitionFor(dependentItem);
+      DexDefinition dependentDefinition = appView.definitionFor(dependentItem);
+      if (dependentDefinition == null) {
+        assert false;
+        continue;
+      }
       if (!dependentDefinition.isStaticMember()) {
         enqueueRootItem(holder, entry.getValue());
         // Enough to enqueue the known holder once.
@@ -434,7 +438,7 @@
         item.isDexField()
             ? item.asDexField().holder
             : item.asDexMethod().holder;
-    DexType holder = itemHolder.toBaseType(appInfo.dexItemFactory);
+    DexType holder = itemHolder.toBaseType(appView.dexItemFactory());
     if (!holder.isClassType()) {
       return false;
     }
@@ -449,7 +453,7 @@
         item.isDexField()
             ? item.asDexField().holder
             : item.asDexMethod().holder;
-    DexType holder = itemHolder.toBaseType(appInfo.dexItemFactory);
+    DexType holder = itemHolder.toBaseType(appView.dexItemFactory());
     if (!holder.isClassType()) {
       return false;
     }
@@ -474,7 +478,7 @@
     }
 
     boolean registerInvokeVirtual(DexMethod method, KeepReason keepReason) {
-      if (appInfo.dexItemFactory.classMethods.isReflectiveMemberLookup(method)) {
+      if (appView.dexItemFactory().classMethods.isReflectiveMemberLookup(method)) {
         // Implicitly add -identifiernamestring rule for the Java reflection in use.
         identifierNameStrings.add(method);
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
@@ -512,19 +516,19 @@
     }
 
     boolean registerInvokeStatic(DexMethod method, KeepReason keepReason) {
-      if (method == appInfo.dexItemFactory.classMethods.forName
-          || appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(method)) {
+      if (method == appView.dexItemFactory().classMethods.forName
+          || appView.dexItemFactory().atomicFieldUpdaterMethods.isFieldUpdater(method)) {
         // Implicitly add -identifiernamestring rule for the Java reflection in use.
         identifierNameStrings.add(method);
         // Revisit the current method to implicitly add -keep rule for items with reflective access.
         pendingReflectiveUses.add(currentMethod);
       }
       // See comment in handleJavaLangEnumValueOf.
-      if (method == appInfo.dexItemFactory.enumMethods.valueOf) {
+      if (method == appView.dexItemFactory().enumMethods.valueOf) {
         pendingReflectiveUses.add(currentMethod);
       }
       // Handling of application services.
-      if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(method)) {
+      if (appView.dexItemFactory().serviceLoaderMethods.isLoadMethod(method)) {
         pendingReflectiveUses.add(currentMethod);
       }
       if (!registerItemWithTargetAndContext(staticInvokes, method, currentMethod)) {
@@ -625,7 +629,7 @@
       }
 
       DexEncodedField encodedField = appInfo.resolveField(field);
-      if (encodedField != null && encodedField.isProgramField(appInfo)) {
+      if (encodedField != null && encodedField.isProgramField(appView)) {
         boolean isWrittenOutsideEnclosingStaticInitializer =
             currentMethod.method.holder != encodedField.field.holder
                 || !currentMethod.isClassInitializer();
@@ -666,7 +670,7 @@
       // stays in the output (and is not class merged). To ensure that we treat the receiver
       // as instantiated.
       if (methodHandle.isMethodHandle() && use != MethodHandleUse.ARGUMENT_TO_LAMBDA_METAFACTORY) {
-        DexClass holder = appInfo.definitionFor(methodHandle.asMethod().holder);
+        DexClass holder = appView.definitionFor(methodHandle.asMethod().holder);
         if (holder != null) {
           markInstantiated(holder.type, KeepReason.methodHandleReferencedIn(currentMethod));
         }
@@ -695,7 +699,7 @@
         }
       }
 
-      DexClass bootstrapClass = appInfo.definitionFor(callSite.bootstrapMethod.asMethod().holder);
+      DexClass bootstrapClass = appView.definitionFor(callSite.bootstrapMethod.asMethod().holder);
       if (bootstrapClass != null && bootstrapClass.isProgramClass()) {
         bootstrapMethods.add(callSite.bootstrapMethod.asMethod());
       }
@@ -751,8 +755,8 @@
       }
 
       Set<DexType> allInterfaces = Sets.newHashSet(directInterfaces);
-      DexType instantiatedType = appInfo.dexItemFactory.objectType;
-      DexClass clazz = appInfo.definitionFor(instantiatedType);
+      DexType instantiatedType = appView.dexItemFactory().objectType;
+      DexClass clazz = appView.definitionFor(instantiatedType);
       if (clazz == null) {
         reportMissingClass(instantiatedType);
         return;
@@ -778,7 +782,7 @@
       // the shadowing of other interface chains into account.
       // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
       for (DexType iface : allInterfaces) {
-        DexClass ifaceClazz = appInfo.definitionFor(iface);
+        DexClass ifaceClazz = appView.definitionFor(iface);
         if (ifaceClazz == null) {
           reportMissingClass(iface);
           return;
@@ -789,9 +793,9 @@
 
     private boolean registerConstClassOrCheckCast(DexType type) {
       if (forceProguardCompatibility) {
-        DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+        DexType baseType = type.toBaseType(appView.dexItemFactory());
         if (baseType.isClassType()) {
-          DexClass baseClass = appInfo.definitionFor(baseType);
+          DexClass baseClass = appView.definitionFor(baseType);
           if (baseClass != null && baseClass.isProgramClass()) {
             // Don't require any constructor, see b/112386012.
             markClassAsInstantiatedWithCompatRule(baseClass);
@@ -809,17 +813,17 @@
   }
 
   private DexMethod getInvokeSuperTarget(DexMethod method, DexEncodedMethod currentMethod) {
-    DexClass methodHolderClass = appInfo.definitionFor(method.holder);
+    DexClass methodHolderClass = appView.definitionFor(method.holder);
     if (methodHolderClass != null && methodHolderClass.isInterface()) {
       return method;
     }
-    DexClass holderClass = appInfo.definitionFor(currentMethod.method.holder);
+    DexClass holderClass = appView.definitionFor(currentMethod.method.holder);
     if (holderClass == null || holderClass.superType == null || holderClass.isInterface()) {
       // We do not know better or this call is made from an interface.
       return method;
     }
     // Return the invoked method on the supertype.
-    return appInfo.dexItemFactory.createMethod(holderClass.superType, method.proto, method.name);
+    return appView.dexItemFactory().createMethod(holderClass.superType, method.proto, method.name);
   }
 
   //
@@ -827,7 +831,7 @@
   //
 
   private void markTypeAsLive(DexType type) {
-    type = type.toBaseType(appInfo.dexItemFactory);
+    type = type.toBaseType(appView.dexItemFactory());
     if (!type.isClassType()) {
       // Ignore primitive types.
       return;
@@ -836,7 +840,7 @@
       if (Log.ENABLED) {
         Log.verbose(getClass(), "Type `%s` has become live.", type);
       }
-      DexClass holder = appInfo.definitionFor(type);
+      DexClass holder = appView.definitionFor(type);
       if (holder == null) {
         reportMissingClass(type);
         return;
@@ -865,7 +869,7 @@
         }
       }
 
-      if (holder.isProgramClass() && holder.isSerializable(appInfo)) {
+      if (holder.isProgramClass() && holder.isSerializable(appView)) {
         enqueueFirstNonSerializableClassInitializer(holder, reason);
       }
 
@@ -905,9 +909,9 @@
     assert !holder.isDexClass() || !holder.asDexClass().isLibraryClass();
     DexType type = annotation.annotation.type;
     boolean annotationTypeIsLibraryClass =
-        appInfo.definitionFor(type) == null || appInfo.definitionFor(type).isLibraryClass();
+        appView.definitionFor(type) == null || appView.definitionFor(type).isLibraryClass();
     boolean isLive = annotationTypeIsLibraryClass || liveTypes.contains(type);
-    if (!shouldKeepAnnotation(annotation, isLive, appInfo.dexItemFactory, options)) {
+    if (!shouldKeepAnnotation(annotation, isLive, appView.dexItemFactory(), options)) {
       // Remember this annotation for later.
       if (!annotationTypeIsLibraryClass) {
         deferredAnnotations.computeIfAbsent(type, ignore -> new HashSet<>()).add(annotation);
@@ -916,7 +920,7 @@
     }
     liveAnnotations.add(annotation, KeepReason.annotatedOn(holder));
     AnnotationReferenceMarker referenceMarker =
-        new AnnotationReferenceMarker(annotation.annotation.type, appInfo.dexItemFactory);
+        new AnnotationReferenceMarker(annotation.annotation.type, appView.dexItemFactory());
     annotation.annotation.collectIndexedItems(referenceMarker);
   }
 
@@ -967,7 +971,7 @@
       return;
     }
 
-    DexClass holder = appInfo.definitionFor(type);
+    DexClass holder = appView.definitionFor(type);
     if (holder != null && !holder.isLibraryClass()) {
       if (!dontWarnPatterns.matches(context)) {
         Diagnostic message =
@@ -1010,7 +1014,7 @@
     }
     markTypeAsLive(method.method.holder);
     markParameterAndReturnTypesAsLive(method);
-    if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+    if (!appView.definitionFor(method.method.holder).isLibraryClass()) {
       processAnnotations(method, method.annotations.annotations);
       method.parameterAnnotationsList.forEachAnnotation(
           annotation -> processAnnotation(method, annotation));
@@ -1021,7 +1025,7 @@
     if (forceProguardCompatibility) {
       // Keep targeted default methods in compatibility mode. The tree pruner will otherwise make
       // these methods abstract, whereas Proguard does not (seem to) touch their code.
-      DexClass clazz = appInfo.definitionFor(method.method.holder);
+      DexClass clazz = appView.definitionFor(method.method.holder);
       if (!method.accessFlags.isAbstract()
           && clazz.isInterface() && !clazz.isLibraryClass()) {
         markMethodAsKeptWithCompatRule(method);
@@ -1069,7 +1073,7 @@
     Set<DexType> interfaces = Sets.newIdentityHashSet();
     DexType type = instantiatedType;
     do {
-      DexClass clazz = appInfo.definitionFor(type);
+      DexClass clazz = appView.definitionFor(type);
       if (clazz == null) {
         reportMissingClass(type);
         // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
@@ -1095,7 +1099,7 @@
     // the shadowing of other interface chains into account.
     // See https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-5.html#jvms-5.4.3.3
     for (DexType iface : interfaces) {
-      DexClass clazz = appInfo.definitionFor(iface);
+      DexClass clazz = appView.definitionFor(iface);
       if (clazz == null) {
         reportMissingClass(iface);
         // TODO(herhut): In essence, our subtyping chain is broken here. Handle that case better.
@@ -1107,7 +1111,7 @@
 
   private void transitionDefaultMethodsForInstantiatedClass(DexType iface, DexType instantiatedType,
       ScopedDexMethodSet seen) {
-    DexClass clazz = appInfo.definitionFor(iface);
+    DexClass clazz = appView.definitionFor(iface);
     if (clazz == null) {
       reportMissingClass(iface);
       return;
@@ -1143,7 +1147,7 @@
    */
   private void transitionFieldsForInstantiatedClass(DexType type) {
     do {
-      DexClass clazz = appInfo.definitionFor(type);
+      DexClass clazz = appView.definitionFor(type);
       if (clazz == null) {
         // TODO(herhut) The subtype chain is broken. We need a way to deal with this better.
         reportMissingClass(type);
@@ -1215,7 +1219,7 @@
     if (instantiatedTypes.contains(type)) {
       return;
     }
-    DexClass clazz = appInfo.definitionFor(type);
+    DexClass clazz = appView.definitionFor(type);
     if (clazz == null) {
       reportMissingClass(type);
       return;
@@ -1227,7 +1231,7 @@
   }
 
   private void markLambdaInstantiated(DexType itf, DexEncodedMethod method) {
-    DexClass clazz = appInfo.definitionFor(itf);
+    DexClass clazz = appView.definitionFor(itf);
     if (clazz == null) {
       if (options.reporter != null) {
         StringDiagnostic message =
@@ -1331,7 +1335,7 @@
       markTypeAsLive(method.holder);
       return;
     }
-    DexClass holder = appInfo.definitionFor(method.holder);
+    DexClass holder = appView.definitionFor(method.holder);
     if (holder == null) {
       reportMissingClass(method.holder);
       return;
@@ -1371,7 +1375,7 @@
               fillWorkList(worklist, encodedMethod.method.holder);
               while (!worklist.isEmpty()) {
                 DexType current = worklist.pollFirst();
-                DexClass currentHolder = appInfo.definitionFor(current);
+                DexClass currentHolder = appView.definitionFor(current);
                 // If this class overrides the virtual, abort the search. Note that, according to
                 // the JVM spec, private methods cannot override a virtual method.
                 if (currentHolder == null
@@ -1393,11 +1397,14 @@
 
   private DexMethod generatedEnumValuesMethod(DexClass enumClass) {
     DexType arrayOfEnumClass =
-        appInfo.dexItemFactory.createType(
-            appInfo.dexItemFactory.createString("[" + enumClass.type.toDescriptorString()));
-    DexProto proto = appInfo.dexItemFactory.createProto(arrayOfEnumClass);
-    return appInfo.dexItemFactory.createMethod(
-        enumClass.type, proto, appInfo.dexItemFactory.createString("values"));
+        appView
+            .dexItemFactory()
+            .createType(
+                appView.dexItemFactory().createString("[" + enumClass.type.toDescriptorString()));
+    DexProto proto = appView.dexItemFactory().createProto(arrayOfEnumClass);
+    return appView
+        .dexItemFactory()
+        .createMethod(enumClass.type, proto, appView.dexItemFactory().createString("values"));
   }
 
   private void markEnumValuesAsReachable(DexClass clazz, KeepReason reason) {
@@ -1464,15 +1471,16 @@
     }
   }
 
-  public AppInfoWithLiveness traceMainDex(
+  // Returns the set of live types.
+  public SortedSet<DexType> traceMainDex(
       RootSet rootSet, ExecutorService executorService, Timing timing) throws ExecutionException {
     this.tracingMainDex = true;
     this.rootSet = rootSet;
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
-    AppInfoWithLiveness appInfo = trace(executorService, timing);
+    trace(executorService, timing);
     options.reporter.failIfPendingErrors();
-    return appInfo;
+    return ImmutableSortedSet.copyOf(PresortedComparable::slowCompareTo, liveTypes);
   }
 
   public AppInfoWithLiveness traceApplication(
@@ -1486,13 +1494,12 @@
     // Translate the result of root-set computation into enqueuer actions.
     enqueueRootItems(rootSet.noShrinking);
     appInfo.libraryClasses().forEach(this::markAllLibraryVirtualMethodsReachable);
-    AppInfoWithLiveness result = trace(executorService, timing);
+    trace(executorService, timing);
     options.reporter.failIfPendingErrors();
-    return result;
+    return new AppInfoWithLiveness(appInfo, this);
   }
 
-  private AppInfoWithLiveness trace(
-      ExecutorService executorService, Timing timing) throws ExecutionException {
+  private void trace(ExecutorService executorService, Timing timing) throws ExecutionException {
     timing.begin("Grow the tree.");
     try {
       while (true) {
@@ -1537,8 +1544,7 @@
         numOfLiveItemsAfterProcessing += (long) liveMethods.items.size();
         numOfLiveItemsAfterProcessing += (long) liveFields.items.size();
         if (numOfLiveItemsAfterProcessing > numOfLiveItems) {
-          RootSetBuilder consequentSetBuilder =
-              new RootSetBuilder(appView, rootSet.ifRules, options);
+          RootSetBuilder consequentSetBuilder = new RootSetBuilder(appView, rootSet.ifRules);
           IfRuleEvaluator ifRuleEvaluator =
               consequentSetBuilder.getIfRuleEvaluator(
                   liveFields.getItems(),
@@ -1550,14 +1556,15 @@
           enqueueRootItems(consequentRootSet.noShrinking);
           // Check if any newly dependent members are not static, and in that case find the holder
           // and enqueue it as well. This is -if version of workaround for b/115867670.
-          consequentRootSet.dependentNoShrinking.forEach((precondition, dependentItems) -> {
-            if (precondition.isDexType()) {
-              DexClass preconditionHolder = appInfo.definitionFor(precondition.asDexType());
-              enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
-            }
-            // Add all dependent members to the workqueue.
-            enqueueRootItems(dependentItems);
-          });
+          consequentRootSet.dependentNoShrinking.forEach(
+              (precondition, dependentItems) -> {
+                if (precondition.isDexType()) {
+                  DexClass preconditionHolder = appView.definitionFor(precondition.asDexType());
+                  enqueueHolderIfDependentNonStaticMember(preconditionHolder, dependentItems);
+                }
+                // Add all dependent members to the workqueue.
+                enqueueRootItems(dependentItems);
+              });
           if (!workList.isEmpty()) {
             continue;
           }
@@ -1599,7 +1606,6 @@
       timing.end();
     }
     unpinLambdaMethods();
-    return new AppInfoWithLiveness(appInfo, this);
   }
 
   private void unpinLambdaMethods() {
@@ -1611,7 +1617,7 @@
   }
 
   private void markMethodAsKept(DexEncodedMethod target, KeepReason reason) {
-    DexClass holder = appInfo.definitionFor(target.method.holder);
+    DexClass holder = appView.definitionFor(target.method.holder);
     // If this method no longer has a corresponding class then we have shaken it away before.
     if (holder == null) {
       return;
@@ -1634,7 +1640,7 @@
 
   private void markFieldAsKept(DexEncodedField target, KeepReason reason) {
     // If this field no longer has a corresponding class, then we have shaken it away before.
-    if (appInfo.definitionFor(target.field.holder) == null) {
+    if (appView.definitionFor(target.field.holder) == null) {
       return;
     }
     if (target.accessFlags.isStatic()) {
@@ -1660,7 +1666,7 @@
   private void processNewlyLiveMethod(DexEncodedMethod method, KeepReason reason) {
     if (liveMethods.add(method, reason)) {
       collectProguardCompatibilityRule(reason);
-      DexClass holder = appInfo.definitionFor(method.method.holder);
+      DexClass holder = appView.definitionFor(method.method.holder);
       assert holder != null;
       if (holder.isLibraryClass()) {
         // We do not process library classes.
@@ -1678,7 +1684,7 @@
         }
       }
       markParameterAndReturnTypesAsLive(method);
-      if (!appInfo.definitionFor(method.method.holder).isLibraryClass()) {
+      if (!appView.definitionFor(method.method.holder).isLibraryClass()) {
         processAnnotations(method, method.annotations.annotations);
         method.parameterAnnotationsList.forEachAnnotation(
             annotation -> processAnnotation(method, annotation));
@@ -1716,7 +1722,7 @@
     return Collections.unmodifiableSortedMap(result);
   }
 
-  private Set<DexField> collectReachedFields(
+  private static Set<DexField> collectReachedFields(
       Set<DexField> set, Function<DexField, DexField> lookup) {
     return set.stream()
         .map(lookup)
@@ -1734,8 +1740,10 @@
     return target == null ? null : target.field;
   }
 
-  SortedSet<DexField> mergeFieldAccesses(Set<DexField> instanceFields, Set<DexField> staticFields) {
-    return ImmutableSortedSet.copyOf(PresortedComparable<DexField>::slowCompareTo,
+  private SortedSet<DexField> mergeFieldAccesses(
+      Set<DexField> instanceFields, Set<DexField> staticFields) {
+    return ImmutableSortedSet.copyOf(
+        PresortedComparable<DexField>::slowCompareTo,
         Sets.union(
             collectReachedFields(instanceFields, this::tryLookupInstanceField),
             collectReachedFields(staticFields, this::tryLookupStaticField)));
@@ -1760,7 +1768,7 @@
   }
 
   private void markMethodAsKeptWithCompatRule(DexEncodedMethod method) {
-    DexClass holderClass = appInfo.definitionFor(method.method.holder);
+    DexClass holderClass = appView.definitionFor(method.method.holder);
     ProguardKeepRule rule =
         ProguardConfigurationUtils.buildMethodKeepRule(holderClass, method);
     proguardCompatibilityWorkList.add(
@@ -1784,15 +1792,15 @@
     }
     InvokeMethod invoke = instruction.asInvokeMethod();
     DexMethod invokedMethod = invoke.getInvokedMethod();
-    if (invokedMethod == appInfo.dexItemFactory.enumMethods.valueOf) {
+    if (invokedMethod == appView.dexItemFactory().enumMethods.valueOf) {
       handleJavaLangEnumValueOf(method, invoke);
       return;
     }
-    if (appInfo.dexItemFactory.serviceLoaderMethods.isLoadMethod(invokedMethod)) {
+    if (appView.dexItemFactory().serviceLoaderMethods.isLoadMethod(invokedMethod)) {
       handleServiceLoaderInvocation(method, invoke);
       return;
     }
-    if (!isReflectionMethod(appInfo.dexItemFactory, invokedMethod)) {
+    if (!isReflectionMethod(appView.dexItemFactory(), invokedMethod)) {
       return;
     }
     DexReference identifierItem = identifyIdentifier(appInfo, invoke);
@@ -1800,7 +1808,7 @@
       return;
     }
     if (identifierItem.isDexType()) {
-      DexClass clazz = appInfo.definitionFor(identifierItem.asDexType());
+      DexClass clazz = appView.definitionFor(identifierItem.asDexType());
       if (clazz != null) {
         markInstantiated(clazz.type, KeepReason.reflectiveUseIn(method));
         if (clazz.hasDefaultInitializer()) {
@@ -1809,7 +1817,7 @@
         }
       }
     } else if (identifierItem.isDexField()) {
-      DexEncodedField encodedField = appInfo.definitionFor(identifierItem.asDexField());
+      DexEncodedField encodedField = appView.definitionFor(identifierItem.asDexField());
       if (encodedField != null) {
         // Normally, we generate a -keepclassmembers rule for the field, such that the field is only
         // kept if it is a static field, or if the holder or one of its subtypes are instantiated.
@@ -1818,9 +1826,9 @@
         // is not present.
         boolean keepClass =
             !encodedField.accessFlags.isStatic()
-                && appInfo.dexItemFactory.atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
+                && appView.dexItemFactory().atomicFieldUpdaterMethods.isFieldUpdater(invokedMethod);
         if (keepClass) {
-          DexClass holderClass = appInfo.definitionFor(encodedField.field.holder);
+          DexClass holderClass = appView.definitionFor(encodedField.field.holder);
           markInstantiated(holderClass.type, KeepReason.reflectiveUseIn(method));
         }
         markFieldAsKept(encodedField, KeepReason.reflectiveUseIn(method));
@@ -1835,7 +1843,7 @@
       }
     } else {
       assert identifierItem.isDexMethod();
-      DexEncodedMethod encodedMethod = appInfo.definitionFor(identifierItem.asDexMethod());
+      DexEncodedMethod encodedMethod = appView.definitionFor(identifierItem.asDexMethod());
       if (encodedMethod != null) {
         if (encodedMethod.accessFlags.isStatic() || encodedMethod.accessFlags.isConstructor()) {
           markDirectStaticOrConstructorMethodAsLive(
@@ -1854,8 +1862,8 @@
     // call this method.
     if (invoke.inValues().get(0).isConstClass()) {
       DexClass clazz =
-          appInfo.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
-      if (clazz.accessFlags.isEnum() && clazz.superType == appInfo.dexItemFactory.enumType) {
+          appView.definitionFor(invoke.inValues().get(0).definition.asConstClass().getValue());
+      if (clazz.accessFlags.isEnum() && clazz.superType == appView.dexItemFactory().enumType) {
         markEnumValuesAsReachable(clazz, KeepReason.invokedFrom(method));
       }
     }
@@ -1905,7 +1913,7 @@
         continue;
       }
 
-      DexClass serviceImplementationClass = appInfo.definitionFor(serviceImplementationType);
+      DexClass serviceImplementationClass = appView.definitionFor(serviceImplementationType);
       if (serviceImplementationClass != null && serviceImplementationClass.isProgramClass()) {
         markClassAsInstantiatedWithReason(serviceImplementationClass, reason);
       }
@@ -2186,10 +2194,10 @@
       this.instanceFieldWrites = enqueuer.collectDescriptors(enqueuer.instanceFieldsWritten);
       this.staticFieldReads = enqueuer.collectDescriptors(enqueuer.staticFieldsRead);
       this.staticFieldWrites = enqueuer.collectDescriptors(enqueuer.staticFieldsWritten);
-      this.fieldsRead = enqueuer.mergeFieldAccesses(
-          instanceFieldReads.keySet(), staticFieldReads.keySet());
-      this.fieldsWritten = enqueuer.mergeFieldAccesses(
-          instanceFieldWrites.keySet(), staticFieldWrites.keySet());
+      this.fieldsRead =
+          enqueuer.mergeFieldAccesses(instanceFieldReads.keySet(), staticFieldReads.keySet());
+      this.fieldsWritten =
+          enqueuer.mergeFieldAccesses(instanceFieldWrites.keySet(), staticFieldWrites.keySet());
       this.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
           ImmutableSortedSet.copyOf(
               DexField::slowCompareTo,
@@ -2221,6 +2229,7 @@
       this.ordinalsMaps = Collections.emptyMap();
       assert Sets.intersection(instanceFieldReads.keySet(), staticFieldReads.keySet()).isEmpty();
       assert Sets.intersection(instanceFieldWrites.keySet(), staticFieldWrites.keySet()).isEmpty();
+      appInfo.markObsolete();
     }
 
     private AppInfoWithLiveness(AppInfoWithLiveness previous, DexApplication application) {
@@ -2287,7 +2296,7 @@
         AppInfoWithLiveness previous,
         DirectMappedDexApplication application,
         GraphLense lense) {
-      super(application, lense);
+      super(application);
       this.liveTypes = rewriteItems(previous.liveTypes, lense::lookupType);
       this.instantiatedAnnotationTypes =
           rewriteItems(previous.instantiatedAnnotationTypes, lense::lookupType);
@@ -2419,13 +2428,15 @@
       this.prunedTypes = previous.prunedTypes;
       this.switchMaps = switchMaps;
       this.ordinalsMaps = ordinalsMaps;
+      previous.markObsolete();
     }
 
     public AppInfoWithLiveness withoutStaticFieldsWrites(Set<DexField> noLongerWrittenFields) {
+      assert checkIfObsolete();
       if (noLongerWrittenFields.isEmpty()) {
         return this;
       }
-      AppInfoWithLiveness result = new AppInfoWithLiveness(this, app);
+      AppInfoWithLiveness result = new AppInfoWithLiveness(this, app());
       Predicate<DexField> isFieldWritten = field -> !noLongerWrittenFields.contains(field);
       result.fieldsWritten = filter(fieldsWritten, isFieldWritten);
       result.staticFieldsWrittenOnlyInEnclosingStaticInitializer =
@@ -2441,10 +2452,12 @@
     }
 
     public Reference2IntMap<DexField> getOrdinalsMapFor(DexType enumClass) {
+      assert checkIfObsolete();
       return ordinalsMaps.get(enumClass);
     }
 
     public Int2ReferenceMap<DexField> getSwitchMapFor(DexField field) {
+      assert checkIfObsolete();
       return switchMaps.get(field);
     }
 
@@ -2466,6 +2479,7 @@
     }
 
     public boolean isInstantiatedDirectly(DexType type) {
+      assert checkIfObsolete();
       assert type.isClassType();
       return type.isD8R8SynthesizedClassType()
           || instantiatedTypes.contains(type)
@@ -2474,6 +2488,7 @@
     }
 
     public boolean isInstantiatedIndirectly(DexType type) {
+      assert checkIfObsolete();
       assert type.isClassType();
       synchronized (indirectlyInstantiatedTypes) {
         if (indirectlyInstantiatedTypes.containsKey(type)) {
@@ -2491,11 +2506,13 @@
     }
 
     public boolean isInstantiatedDirectlyOrIndirectly(DexType type) {
+      assert checkIfObsolete();
       assert type.isClassType();
       return isInstantiatedDirectly(type) || isInstantiatedIndirectly(type);
     }
 
     public boolean isFieldRead(DexField field) {
+      assert checkIfObsolete();
       return fieldsRead.contains(field)
           // TODO(b/121354886): Pinned fields should be in `fieldsRead`.
           || isPinned(field)
@@ -2506,6 +2523,7 @@
     }
 
     public boolean isFieldWritten(DexField field) {
+      assert checkIfObsolete();
       return fieldsWritten.contains(field)
           // TODO(b/121354886): Pinned fields should be in `fieldsWritten`.
           || isPinned(field)
@@ -2516,6 +2534,7 @@
     }
 
     public boolean isStaticFieldWrittenOnlyInEnclosingStaticInitializer(DexField field) {
+      assert checkIfObsolete();
       assert isFieldWritten(field);
       return staticFieldsWrittenOnlyInEnclosingStaticInitializer.contains(field);
     }
@@ -2585,24 +2604,29 @@
 
     @Override
     protected boolean hasAnyInstantiatedLambdas(DexType type) {
+      assert checkIfObsolete();
       return instantiatedLambdas.contains(type);
     }
 
     @Override
     public boolean hasLiveness() {
+      assert checkIfObsolete();
       return true;
     }
 
     @Override
     public AppInfoWithLiveness withLiveness() {
+      assert checkIfObsolete();
       return this;
     }
 
     public boolean isPinned(DexReference reference) {
+      assert checkIfObsolete();
       return pinnedItems.contains(reference);
     }
 
     public Iterable<DexReference> getPinnedItems() {
+      assert checkIfObsolete();
       return pinnedItems;
     }
 
@@ -2610,13 +2634,15 @@
      * Returns a copy of this AppInfoWithLiveness where the set of classes is pruned using the given
      * DexApplication object.
      */
-    public AppInfoWithLiveness prunedCopyFrom(DexApplication application,
-        Collection<DexType> removedClasses) {
+    public AppInfoWithLiveness prunedCopyFrom(
+        DexApplication application, Collection<DexType> removedClasses) {
+      assert checkIfObsolete();
       return new AppInfoWithLiveness(this, application, removedClasses);
     }
 
-    public AppInfoWithLiveness rewrittenWithLense(DirectMappedDexApplication application,
-        GraphLense lense) {
+    public AppInfoWithLiveness rewrittenWithLense(
+        DirectMappedDexApplication application, GraphLense lense) {
+      assert checkIfObsolete();
       return new AppInfoWithLiveness(this, application, lense);
     }
 
@@ -2625,14 +2651,17 @@
      * tree shaking.
      */
     public boolean wasPruned(DexType type) {
+      assert checkIfObsolete();
       return prunedTypes.contains(type);
     }
 
     public Set<DexType> getPrunedTypes() {
+      assert checkIfObsolete();
       return prunedTypes;
     }
 
     public DexEncodedMethod lookup(Type type, DexMethod target, DexType invocationContext) {
+      assert checkIfObsolete();
       DexType holder = target.holder;
       if (!holder.isClassType()) {
         return null;
@@ -2657,11 +2686,13 @@
      * For mapping invoke virtual instruction to single target method.
      */
     public DexEncodedMethod lookupSingleVirtualTarget(DexMethod method) {
+      assert checkIfObsolete();
       return lookupSingleVirtualTarget(method, method.holder);
     }
 
     public DexEncodedMethod lookupSingleVirtualTarget(
         DexMethod method, DexType refinedReceiverType) {
+      assert checkIfObsolete();
       // This implements the logic from
       // https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-6.html#jvms-6.5.invokevirtual
       assert method != null;
@@ -2801,11 +2832,13 @@
     }
 
     public DexEncodedMethod lookupSingleInterfaceTarget(DexMethod method) {
+      assert checkIfObsolete();
       return lookupSingleInterfaceTarget(method, method.holder);
     }
 
     public DexEncodedMethod lookupSingleInterfaceTarget(
         DexMethod method, DexType refinedReceiverType) {
+      assert checkIfObsolete();
       if (instantiatedLambdas.contains(method.holder)) {
         return null;
       }
@@ -2862,12 +2895,14 @@
     }
 
     public AppInfoWithLiveness addSwitchMaps(Map<DexField, Int2ReferenceMap<DexField>> switchMaps) {
+      assert checkIfObsolete();
       assert this.switchMaps.isEmpty();
       return new AppInfoWithLiveness(this, switchMaps, ordinalsMaps);
     }
 
     public AppInfoWithLiveness addEnumOrdinalMaps(
         Map<DexType, Reference2IntMap<DexField>> ordinalsMaps) {
+      assert checkIfObsolete();
       assert this.ordinalsMaps.isEmpty();
       return new AppInfoWithLiveness(this, switchMaps, ordinalsMaps);
     }
@@ -2947,7 +2982,7 @@
 
     @Override
     public boolean addField(DexField field) {
-      DexClass holder = appInfo.definitionFor(field.holder);
+      DexClass holder = appView.definitionFor(field.holder);
       if (holder == null) {
         return false;
       }
@@ -2969,7 +3004,7 @@
 
     @Override
     public boolean addMethod(DexMethod method) {
-      DexClass holder = appInfo.definitionFor(method.holder);
+      DexClass holder = appView.definitionFor(method.holder);
       if (holder == null) {
         return false;
       }
@@ -3100,7 +3135,7 @@
     return classNodes.computeIfAbsent(
         type,
         t -> {
-          DexClass definition = appInfo.definitionFor(t);
+          DexClass definition = appView.definitionFor(t);
           return new ClassGraphNode(
               definition != null && definition.isLibraryClass(),
               Reference.classFromDescriptor(t.toDescriptorString()));
@@ -3111,7 +3146,7 @@
     return methodNodes.computeIfAbsent(
         context,
         m -> {
-          DexClass holderDefinition = appInfo.definitionFor(context.holder);
+          DexClass holderDefinition = appView.definitionFor(context.holder);
           Builder<TypeReference> builder = ImmutableList.builder();
           for (DexType param : m.proto.parameters.values) {
             builder.add(Reference.typeFromDescriptor(param.toDescriptorString()));
@@ -3132,7 +3167,7 @@
     return fieldNodes.computeIfAbsent(
         context,
         f -> {
-          DexClass holderDefinition = appInfo.definitionFor(context.holder);
+          DexClass holderDefinition = appView.definitionFor(context.holder);
           return new FieldGraphNode(
               holderDefinition != null && holderDefinition.isLibraryClass(),
               Reference.field(
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
index 07aaecd..3307f8d 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexDirectReferenceTracer.java
@@ -31,7 +31,7 @@
   private final Consumer<DexType> consumer;
 
   public MainDexDirectReferenceTracer(AppInfoWithSubtyping appInfo, Consumer<DexType> consumer) {
-    this.codeDirectReferenceCollector = new DirectReferencesCollector(appInfo.dexItemFactory);
+    this.codeDirectReferenceCollector = new DirectReferencesCollector(appInfo.dexItemFactory());
     this.appInfo = appInfo;
     this.consumer = consumer;
   }
@@ -68,7 +68,7 @@
     new MainDexDirectReferenceTracer(
             appInfo,
             type -> {
-              DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+              DexType baseType = type.toBaseType(appInfo.dexItemFactory());
               if (baseType.isClassType() && !classes.contains(baseType)) {
                 DexClass cls = appInfo.definitionFor(baseType);
                 if (cls != null && !cls.isLibraryClass()) {
diff --git a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
index 9d129d6..e14ab9b 100644
--- a/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/MainDexListBuilder.java
@@ -25,6 +25,7 @@
  * <li> Annotation classes with a possible enum value and all classes annotated by them.
  */
 public class MainDexListBuilder {
+
   private final Set<DexType> roots;
   private final AppInfoWithSubtyping appInfo;
   private final Map<DexType, Boolean> annotationTypeContainEnum;
@@ -41,19 +42,19 @@
     // Only consider program classes for the root set.
     this.roots = roots.stream().filter(this::isProgramClass).collect(Collectors.toSet());
     mainDexClassesBuilder = MainDexClasses.builder(appInfo).addRoots(this.roots);
-    DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory.enumType);
+    DexClass enumType = appInfo.definitionFor(appInfo.dexItemFactory().enumType);
     if (enumType == null) {
       throw new CompilationError("Tracing for legacy multi dex is not possible without all"
           + " classpath libraries (java.lang.Enum is missing)");
     }
-    DexClass annotationType = appInfo.definitionFor(appInfo.dexItemFactory.annotationType);
+    DexClass annotationType = appInfo.definitionFor(appInfo.dexItemFactory().annotationType);
     if (annotationType == null) {
       throw new CompilationError("Tracing for legacy multi dex is not possible without all"
           + " classpath libraries (java.lang.annotation.Annotation is missing)");
     }
     annotationTypeContainEnum =
         Maps.newHashMapWithExpectedSize(
-            appInfo.subtypes(appInfo.dexItemFactory.annotationType).size());
+            appInfo.subtypes(appInfo.dexItemFactory().annotationType).size());
   }
 
   public MainDexClasses run() {
@@ -99,7 +100,7 @@
         for (DexEncodedMethod method : clazz.virtualMethods()) {
           DexProto proto = method.method.proto;
           if (proto.parameters.isEmpty()) {
-            DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory);
+            DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
             if (isEnum(valueType)) {
               value = true;
               break;
@@ -116,11 +117,11 @@
   }
 
   private boolean isEnum(DexType valueType) {
-    return valueType.isSubtypeOf(appInfo.dexItemFactory.enumType, appInfo);
+    return valueType.isSubtypeOf(appInfo.dexItemFactory().enumType, appInfo);
   }
 
   private boolean isAnnotation(DexType valueType) {
-    return valueType.isSubtypeOf(appInfo.dexItemFactory.annotationType, appInfo);
+    return valueType.isSubtypeOf(appInfo.dexItemFactory().annotationType, appInfo);
   }
 
   private boolean isProgramClass(DexType dexType) {
@@ -140,7 +141,7 @@
     for (DexEncodedMethod method : clazz.virtualMethods()) {
       DexProto proto = method.method.proto;
       if (proto.parameters.isEmpty()) {
-        DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory);
+        DexType valueType = proto.returnType.toBaseType(appInfo.dexItemFactory());
         if (isEnum(valueType)) {
           addDirectDependency(valueType);
         }
@@ -155,7 +156,7 @@
 
   private void addDirectDependency(DexType type) {
     // Consider only component type of arrays
-    type = type.toBaseType(appInfo.dexItemFactory);
+    type = type.toBaseType(appInfo.dexItemFactory());
 
     if (!type.isClassType() || mainDexClassesBuilder.contains(type)) {
       return;
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 e1dd681..cac448d 100644
--- a/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
+++ b/src/main/java/com/android/tools/r8/shaking/RootSetBuilder.java
@@ -94,19 +94,15 @@
   public RootSetBuilder(
       AppView<? extends AppInfo> appView,
       DexApplication application,
-      Iterable<? extends ProguardConfigurationRule> rules,
-      InternalOptions options) {
+      Iterable<? extends ProguardConfigurationRule> rules) {
     this.appView = appView;
     this.application = application.asDirect();
     this.rules = rules;
-    this.options = options;
+    this.options = appView.options();
   }
 
-  RootSetBuilder(
-      AppView<? extends AppInfo> appView,
-      Collection<ProguardIfRule> ifRules,
-      InternalOptions options) {
-    this(appView, appView.appInfo().app, ifRules, options);
+  RootSetBuilder(AppView<? extends AppInfo> appView, Collection<ProguardIfRule> ifRules) {
+    this(appView, appView.appInfo().app(), ifRules);
   }
 
   // Process a class with the keep rule.
@@ -864,7 +860,7 @@
       return;
     }
     if (type.isArrayType()) {
-      type = type.toBaseType(application.dexItemFactory);
+      type = type.toBaseType(appView.dexItemFactory());
     }
     if (type.isPrimitiveType()) {
       return;
diff --git a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
index 1895a07..f244b6d 100644
--- a/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/StaticClassMerger.java
@@ -222,7 +222,7 @@
   }
 
   public GraphLense run() {
-    for (DexProgramClass clazz : appView.appInfo().app.classesWithDeterministicOrder()) {
+    for (DexProgramClass clazz : appView.appInfo().app().classesWithDeterministicOrder()) {
       MergeGroup group = satisfiesMergeCriteria(clazz);
       if (group != MergeGroup.DONT_MERGE) {
         merge(clazz, group);
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 6aa1136..b8fd5f6 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -201,7 +201,6 @@
   private final AppInfoWithLiveness appInfo;
   private final AppView<AppInfoWithLiveness> appView;
   private final ExecutorService executorService;
-  private final GraphLense graphLense;
   private final MethodPoolCollection methodPoolCollection;
   private final Timing timing;
   private Collection<DexMethod> invokes;
@@ -236,7 +235,6 @@
     this.appInfo = appView.appInfo();
     this.appView = appView;
     this.executorService = executorService;
-    this.graphLense = appView.graphLense();
     this.methodPoolCollection = new MethodPoolCollection(application);
     this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
     this.timing = timing;
@@ -331,7 +329,7 @@
   }
 
   private void markTypeAsPinned(DexType type, AbortReason reason) {
-    DexType baseType = type.toBaseType(appInfo.dexItemFactory);
+    DexType baseType = type.toBaseType(appView.dexItemFactory());
     if (!baseType.isClassType() || appInfo.isPinned(baseType)) {
       // We check for the case where the type is pinned according to appInfo.isPinned,
       // so we only need to add it here if it is not the case.
@@ -584,7 +582,7 @@
     }
 
     private boolean typeMayReferenceMergedSourceOrTarget(DexType type) {
-      type = type.toBaseType(appInfo.dexItemFactory);
+      type = type.toBaseType(appView.dexItemFactory());
       if (type.isClassType()) {
         if (mergeeCandidates.contains(type)) {
           return true;
@@ -600,7 +598,7 @@
 
   public GraphLense run() {
     timing.begin("merge");
-    GraphLense mergingGraphLense = mergeClasses(graphLense);
+    GraphLense mergingGraphLense = mergeClasses();
     timing.end();
     timing.begin("fixup");
     GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
@@ -676,13 +674,13 @@
     return true;
   }
 
-  private GraphLense mergeClasses(GraphLense graphLense) {
+  private GraphLense mergeClasses() {
     // Visit the program classes in a top-down order according to the class hierarchy.
     TopDownClassHierarchyTraversal.visit(appView, mergeCandidates, this::mergeClassIfPossible);
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
     }
-    return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
+    return renamedMembersLense.build(appView.graphLense(), mergedClasses, appView);
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -1182,7 +1180,7 @@
       SynthesizedBridgeCode code =
           new SynthesizedBridgeCode(
               newMethod,
-              graphLense.getOriginalMethodSignature(method.method),
+              appView.graphLense().getOriginalMethodSignature(method.method),
               invocationTarget.method,
               invocationTarget.isPrivateMethod() ? DIRECT : STATIC,
               target.isInterface());
@@ -1406,7 +1404,7 @@
     DexType[] parameterTypes = new DexType[proto.parameters.size() + 1];
     parameterTypes[0] = receiverType;
     System.arraycopy(proto.parameters.values, 0, parameterTypes, 1, proto.parameters.size());
-    return appInfo.dexItemFactory.createProto(proto.returnType, parameterTypes);
+    return appView.dexItemFactory().createProto(proto.returnType, parameterTypes);
   }
 
   private class TreeFixer {
@@ -1588,7 +1586,7 @@
       int bitsUsed = 0;
       int accumulator = 0;
       for (DexType parameterType : proto.parameters.values) {
-        DexType parameterBaseType = parameterType.toBaseType(appInfo.dexItemFactory);
+        DexType parameterBaseType = parameterType.toBaseType(appView.dexItemFactory());
         // Substitute the type with the already merged class to estimate what it will look like.
         DexType mappedType = mergedClasses.getOrDefault(parameterBaseType, parameterBaseType);
         accumulator <<= 1;
@@ -1604,7 +1602,7 @@
         }
       }
       // We also take the return type into account for potential conflicts.
-      DexType returnBaseType = proto.returnType.toBaseType(appInfo.dexItemFactory);
+      DexType returnBaseType = proto.returnType.toBaseType(appView.dexItemFactory());
       DexType mappedReturnType = mergedClasses.getOrDefault(returnBaseType, returnBaseType);
       accumulator <<= 1;
       if (mappedReturnType == type) {
@@ -1678,7 +1676,7 @@
     public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       // First look up the method using the existing graph lense (for example, the type will have
       // changed if the method was publicized by ClassAndMemberPublicizer).
-      GraphLenseLookupResult lookup = graphLense.lookupMethod(method, context, type);
+      GraphLenseLookupResult lookup = appView.graphLense().lookupMethod(method, context, type);
       DexMethod previousMethod = lookup.getMethod();
       Type previousType = lookup.getType();
       // Then check if there is a renaming due to the vertical class merger.
diff --git a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
index 6576433..dd90a66 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -47,7 +48,8 @@
 //
 // For the invocation "invoke-virtual A.m()" in B.m2, this graph lense will return the method B.m.
 public class VerticalClassMergerGraphLense extends NestedGraphLense {
-  private final AppInfo appInfo;
+
+  private final AppView<? extends AppInfo> appView;
 
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
@@ -55,7 +57,7 @@
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
-      AppInfo appInfo,
+      AppView<? extends AppInfo> appView,
       Map<DexField, DexField> fieldMap,
       Map<DexMethod, DexMethod> methodMap,
       Set<DexMethod> mergedMethods,
@@ -71,8 +73,8 @@
         originalFieldSignatures,
         originalMethodSignatures,
         previousLense,
-        appInfo.dexItemFactory);
-    this.appInfo = appInfo;
+        appView.dexItemFactory());
+    this.appView = appView;
     this.contextualVirtualToDirectMethodMaps = contextualVirtualToDirectMethodMaps;
     this.mergedMethods = mergedMethods;
     this.originalMethodSignaturesForBridges = originalMethodSignaturesForBridges;
@@ -120,7 +122,7 @@
 
   @Override
   protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
+    return super.mapVirtualInterfaceInvocationTypes(appView, newMethod, originalMethod, type);
   }
 
   @Override
@@ -174,7 +176,7 @@
     public GraphLense build(
         GraphLense previousLense,
         Map<DexType, DexType> mergedClasses,
-        AppInfo appInfo) {
+        AppView<? extends AppInfo> appView) {
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
           && contextualVirtualToDirectMethodMaps.isEmpty()) {
@@ -184,11 +186,11 @@
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
       // Build new graph lense.
       return new VerticalClassMergerGraphLense(
-          appInfo,
+          appView,
           fieldMap,
           methodMap,
           getMergedMethodSignaturesAfterClassMerging(
-              mergedMethodsBuilder.build(), mergedClasses, appInfo.dexItemFactory, cache),
+              mergedMethodsBuilder.build(), mergedClasses, appView.dexItemFactory(), cache),
           contextualVirtualToDirectMethodMaps,
           originalFieldSignatures,
           originalMethodSignatures,
diff --git a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
index 5a71add..52952a5 100644
--- a/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
+++ b/src/test/java/com/android/tools/r8/R8RunArtTestsTest.java
@@ -1848,9 +1848,54 @@
     }
   }
 
+  private static class VmErrors {
+    private final Set<TestRuntime> failedVms = new HashSet<>();
+    private StringBuilder message;
+
+    private void addShouldHaveFailedError(CompilerUnderTest compilerUnderTest, TestRuntime vm) {
+      addFailure(vm);
+      message.append(
+          "FAILURE: Test should have failed on "
+              + vm
+              + " after compiling with "
+              + compilerUnderTest
+              + ".\n");
+    }
+
+    private void addFailedOnRunError(
+        CompilerUnderTest compilerUnderTest, TestRuntime vm, AssertionError error) {
+      addFailure(vm);
+      message.append(
+          "FAILURE: Test failed on "
+              + vm
+              + " after compiling with "
+              + compilerUnderTest
+              + ", error:\n"
+              + error.getMessage()
+              + "\n");
+    }
+
+    private void addFailure(TestRuntime vm) {
+      if (message == null) {
+        message = new StringBuilder();
+      }
+      failedVms.add(vm);
+    }
+  }
+
   protected void runJctfTest(
       CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
       throws IOException, CompilationFailedException {
+    VmErrors vmErrors = runJctfTestCore(compilerUnderTest, classFilePath, fullClassName);
+    if (vmErrors.message != null) {
+      throw new RuntimeException(vmErrors.message.toString());
+    }
+  }
+
+  private VmErrors runJctfTestCore(
+      CompilerUnderTest compilerUnderTest, String classFilePath, String fullClassName)
+      throws IOException, CompilationFailedException {
+    VmErrors vmErrors = new VmErrors();
     List<TestRuntime> vms = new ArrayList<>();
     if (compilerUnderTest == CompilerUnderTest.R8CF) {
       for (CfVm vm : TestParametersBuilder.getAvailableCfVms()) {
@@ -1890,7 +1935,7 @@
     }
 
     if (vmSpecs.isEmpty()) {
-      return;
+      return vmErrors;
     }
 
     File classFile = new File(JCTF_TESTS_PREFIX + "/" + classFilePath);
@@ -1959,7 +2004,7 @@
         runJctfTestDoRunOnJava(
             fileNames, vmSpec.spec, fullClassName, compilationMode, vmSpec.vm.asCf().getVm());
       }
-      return;
+      return vmErrors;
     }
 
     CompilationOptions compilationOptions = null;
@@ -1981,17 +2026,31 @@
       Files.copy(
           compiledDir.toPath().resolve("classes.dex"),
           vmSpec.spec.directory.toPath().resolve("classes.dex"));
-      runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm());
+
+      AssertionError vmError = null;
+      try {
+        runJctfTestDoRunOnArt(fileNames, vmSpec.spec, fullClassName, vmSpec.vm.asDex().getVm());
+      } catch (AssertionError e) {
+        vmError = e;
+      }
+      if (vmSpec.spec.failsOnRun && vmError == null) {
+        vmErrors.addShouldHaveFailedError(firstCompilerUnderTest, vmSpec.vm);
+      } else if (!vmSpec.spec.failsOnRun && vmError != null) {
+        vmErrors.addFailedOnRunError(firstCompilerUnderTest, vmSpec.vm, vmError);
+      }
     }
 
     if (compilerUnderTest != CompilerUnderTest.R8_AFTER_D8) {
-      return;
+      return vmErrors;
     }
 
     // Second pass (R8), if R8_AFTER_D8.
     CompilationOptions r8CompilationOptions = null;
     File r8CompiledDir = temp.newFolder();
     for (VmSpec vmSpec : vmSpecs) {
+      if (vmSpec.spec.failsOnRun || vmErrors.failedVms.contains(vmSpec.vm)) {
+        continue;
+      }
       File r8ResultDir = temp.newFolder("r8-output-" + vmSpec.vm.toString());
       TestSpecification specification =
           JctfTestSpecifications.getExpectedOutcome(
@@ -2019,8 +2078,13 @@
       Files.copy(
           r8CompiledDir.toPath().resolve("classes.dex"),
           specification.directory.toPath().resolve("classes.dex"));
-      runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm());
+      try {
+        runJctfTestDoRunOnArt(fileNames, specification, fullClassName, vmSpec.vm.asDex().getVm());
+      } catch (AssertionError e) {
+        vmErrors.addFailedOnRunError(CompilerUnderTest.R8, vmSpec.vm, e);
+      }
     }
+    return vmErrors;
   }
 
   private void runJctfTestDoRunOnArt(
@@ -2061,10 +2125,6 @@
     builder.setMainClass(JUNIT_TEST_RUNNER);
     builder.appendProgramArgument(fullClassName);
 
-    if (specification.failsOnRun) {
-      expectException(AssertionError.class);
-    }
-
     try {
       ToolHelper.runArt(builder);
     } catch (AssertionError e) {
@@ -2072,9 +2132,6 @@
           specification.resolveFile("classes.dex"), e);
       throw e;
     }
-    if (specification.failsOnRun) {
-      System.err.println("Should have failed run with art.");
-    }
   }
 
   private void runJctfTestDoRunOnJava(
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
index fc00afb..d96d999 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapCurrentEqualityTest.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.ExternalR8TestCompileResult;
 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.utils.FileUtils;
@@ -25,17 +27,22 @@
 import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
 import org.junit.BeforeClass;
 import org.junit.ClassRule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
 
 /**
  * This test relies on a freshly built build/libs/r8lib_with_deps.jar. If this test fails remove
  * build directory and rebuild r8lib_with_deps by calling test.py or gradle r8libWithdeps.
  */
+@RunWith(Parameterized.class)
 public class BootstrapCurrentEqualityTest extends TestBase {
 
   private static final Path MAIN_KEEP = Paths.get("src/main/keep.txt");
@@ -54,8 +61,19 @@
 
   @BeforeClass
   public static void beforeAll() throws Exception {
-    r8R8Debug = compileR8(CompilationMode.DEBUG);
-    r8R8Release = compileR8(CompilationMode.RELEASE);
+    if (data().stream().count() > 0) {
+      r8R8Debug = compileR8(CompilationMode.DEBUG);
+      r8R8Release = compileR8(CompilationMode.RELEASE);
+    }
+  }
+
+  @Parameters
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public BootstrapCurrentEqualityTest(TestParameters parameters) {
+    // TODO: use parameters to run on the right java.
   }
 
   private static Pair<Path, Path> compileR8(CompilationMode mode) throws Exception {
diff --git a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
index fc24ed3..883c2c6 100644
--- a/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
+++ b/src/test/java/com/android/tools/r8/cf/BootstrapTest.java
@@ -13,6 +13,8 @@
 import com.android.tools.r8.CompilationMode;
 import com.android.tools.r8.R8Command;
 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.utils.FileUtils;
@@ -23,7 +25,11 @@
 import java.util.Collections;
 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 BootstrapTest extends TestBase {
 
   private static final Path R8_STABLE_JAR = Paths.get("third_party/r8/r8.jar");
@@ -56,6 +62,15 @@
     }
   }
 
+  @Parameters
+  public static TestParametersCollection data() {
+    return getTestParameters().withCfRuntimes().build();
+  }
+
+  public BootstrapTest(TestParameters parameters) {
+    // TODO: use parameters to fork the right Java.
+  }
+
   @Test
   public void test() throws Exception {
     // Run hello.jar to ensure it exists and is valid.
diff --git a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
index 263a4c0..db68c25 100644
--- a/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/graph/TargetLookupTest.java
@@ -78,7 +78,7 @@
 
     AndroidApp application = buildApplication(builder);
     AppInfo appInfo = getAppInfo(application);
-    CodeInspector inspector = new CodeInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app());
     DexEncodedMethod method = getMethod(inspector, DEFAULT_CLASS_NAME, "int", "x",
         ImmutableList.of());
     assertNull(appInfo.lookupVirtualTarget(method.method.holder, method.method));
@@ -99,7 +99,7 @@
   }
 
   @Test
-  public void lookupDirectSuper() throws Exception {
+  public void lookupDirectSuper() {
     SmaliBuilder builder = new SmaliBuilder("TestSuper");
 
     builder.addDefaultConstructor();
@@ -148,7 +148,7 @@
 
     AndroidApp application = buildApplication(builder);
     AppInfo appInfo = getAppInfo(application);
-    CodeInspector inspector = new CodeInspector(appInfo.app);
+    CodeInspector inspector = new CodeInspector(appInfo.app());
 
     DexMethod methodXOnTestSuper =
         getMethod(inspector, "TestSuper", "int", "x", ImmutableList.of()).method;
@@ -160,7 +160,7 @@
     DexProto methodXProto = methodXOnTestSuper.proto;
     DexString methodXName = methodXOnTestSuper.name;
     DexMethod methodXOnTest =
-        appInfo.dexItemFactory.createMethod(classTest, methodXProto, methodXName);
+        appInfo.dexItemFactory().createMethod(classTest, methodXProto, methodXName);
 
     assertNull(appInfo.lookupVirtualTarget(classTestSuper, methodXOnTestSuper));
     assertNull(appInfo.lookupVirtualTarget(classTest, methodXOnTestSuper));
@@ -197,7 +197,7 @@
 
     AndroidApp application = buildApplication(builder);
     AppInfo appInfo = getAppInfo(application);
-    DexItemFactory factory = appInfo.dexItemFactory;
+    DexItemFactory factory = appInfo.dexItemFactory();
 
     DexField aFieldOnSubClass = factory
         .createField(factory.createType("LSubClass;"), factory.intType, "aField");
@@ -229,7 +229,7 @@
     }
     AndroidApp application = builder.build();
     AppInfo appInfo = getAppInfo(application);
-    DexItemFactory factory = appInfo.dexItemFactory;
+    DexItemFactory factory = appInfo.dexItemFactory();
 
     DexType i0 = factory.createType("L" + pkg + "/I0;");
     DexType i1 = factory.createType("L" + pkg + "/I1;");
diff --git a/src/test/java/com/android/tools/r8/ir/InlineTest.java b/src/test/java/com/android/tools/r8/ir/InlineTest.java
index d7b2762..8e99382 100644
--- a/src/test/java/com/android/tools/r8/ir/InlineTest.java
+++ b/src/test/java/com/android/tools/r8/ir/InlineTest.java
@@ -50,8 +50,7 @@
         new RootSetBuilder(
                 appView,
                 application,
-                ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})),
-                options)
+                ImmutableList.of(ProguardKeepRule.defaultKeepAllRule(unused -> {})))
             .run(executorService);
     Timing timing = new Timing(getClass().getSimpleName());
     Enqueuer enqueuer = new Enqueuer(appView, options, null);
diff --git a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
index 083f478..1ba8700 100644
--- a/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
+++ b/src/test/java/com/android/tools/r8/ir/IrInjectionTestBase.java
@@ -95,7 +95,7 @@
         RootSet rootSet,
         MethodSubject method,
         List<IRCode> additionalCode) {
-      this.application = appView.appInfo().app;
+      this.application = appView.appInfo().app();
       this.appView = appView;
       this.rootSet = rootSet;
       this.method = method.getMethod();
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
index 54399f9..66fa712 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/NullabilityTest.java
@@ -50,7 +50,7 @@
       BiConsumer<AppInfo, IRCode> inspector)
       throws Exception {
     AppView<? extends AppInfo> appView = build(mainClass);
-    CodeInspector codeInspector = new CodeInspector(appView.appInfo().app);
+    CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(mainClass.getName()).method(signature);
     DexEncodedMethod foo = codeInspector.clazz(mainClass.getName()).method(signature).getMethod();
     IRCode irCode = fooSubject.buildIR();
@@ -119,129 +119,209 @@
   public void nonNullAfterSafeInvokes() throws Exception {
     MethodSignature signature =
         new MethodSignature("foo", "int", new String[]{"java.lang.String"});
-    buildAndTest(NonNullAfterInvoke.class, signature, false, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
-          NonNull.class, stringClassType(appInfo, definitelyNotNull()),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
-    });
+    buildAndTest(
+        NonNullAfterInvoke.class,
+        signature,
+        false,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterInvoke.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
+                  NonNull.class, stringClassType(appInfo, definitelyNotNull()),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
+        });
   }
 
   @Test
   public void stillNullAfterExceptionCatch_invoke() throws Exception {
     MethodSignature signature =
         new MethodSignature("bar", "int", new String[]{"java.lang.String"});
-    buildAndTest(NonNullAfterInvoke.class, signature, true, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterInvoke.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
-          NonNull.class, stringClassType(appInfo, definitelyNotNull()),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
-    });
+    buildAndTest(
+        NonNullAfterInvoke.class,
+        signature,
+        true,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterInvoke.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  InvokeVirtual.class, stringClassType(appInfo, maybeNull()),
+                  NonNull.class, stringClassType(appInfo, definitelyNotNull()),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
+        });
   }
 
   @Test
   public void nonNullAfterSafeArrayAccess() throws Exception {
     MethodSignature signature =
         new MethodSignature("foo", "int", new String[]{"java.lang.String[]"});
-    buildAndTest(NonNullAfterArrayAccess.class, signature, false, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, maybeNull(), appInfo),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> {
-        if (l.isArrayType()) {
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertEquals(1, lattice.getNesting());
-          TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
-          assertTrue(elementTypeLattice.isClassType());
-          assertEquals(
-              appInfo.dexItemFactory.stringType,
-              elementTypeLattice.asClassTypeLatticeElement().getClassType());
-          assertEquals(v.definition.isArgument(), l.isNullable());
-        } else if (l.isClassType()) {
-          verifyClassTypeLattice(expectedLattices, mainClass, v, l);
-        }
-      });
-    });
+    buildAndTest(
+        NonNullAfterArrayAccess.class,
+        signature,
+        false,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterArrayAccess.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  // An element inside a non-null array could be null.
+                  ArrayGet.class,
+                      fromDexType(appInfo.dexItemFactory().stringType, maybeNull(), appInfo),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode,
+              (v, l) -> {
+                if (l.isArrayType()) {
+                  ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+                  assertEquals(1, lattice.getNesting());
+                  TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
+                  assertTrue(elementTypeLattice.isClassType());
+                  assertEquals(
+                      appInfo.dexItemFactory().stringType,
+                      elementTypeLattice.asClassTypeLatticeElement().getClassType());
+                  assertEquals(v.definition.isArgument(), l.isNullable());
+                } else if (l.isClassType()) {
+                  verifyClassTypeLattice(expectedLattices, mainClass, v, l);
+                }
+              });
+        });
   }
 
   @Test
   public void stillNullAfterExceptionCatch_aget() throws Exception {
     MethodSignature signature =
         new MethodSignature("bar", "int", new String[]{"java.lang.String[]"});
-    buildAndTest(NonNullAfterArrayAccess.class, signature, true, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterArrayAccess.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          // An element inside a non-null array could be null.
-          ArrayGet.class, fromDexType(appInfo.dexItemFactory.stringType, maybeNull(), appInfo),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> {
-        if (l.isArrayType()) {
-          ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
-          assertEquals(1, lattice.getNesting());
-          TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
-          assertTrue(elementTypeLattice.isClassType());
-          assertEquals(
-              appInfo.dexItemFactory.stringType,
-              elementTypeLattice.asClassTypeLatticeElement().getClassType());
-          assertEquals(v.definition.isArgument(), l.isNullable());
-        } else if (l.isClassType()) {
-          verifyClassTypeLattice(expectedLattices, mainClass, v, l);
-        }
-      });
-    });
+    buildAndTest(
+        NonNullAfterArrayAccess.class,
+        signature,
+        true,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterArrayAccess.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  // An element inside a non-null array could be null.
+                  ArrayGet.class,
+                      fromDexType(appInfo.dexItemFactory().stringType, maybeNull(), appInfo),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode,
+              (v, l) -> {
+                if (l.isArrayType()) {
+                  ArrayTypeLatticeElement lattice = l.asArrayTypeLatticeElement();
+                  assertEquals(1, lattice.getNesting());
+                  TypeLatticeElement elementTypeLattice = lattice.getArrayMemberTypeAsMemberType();
+                  assertTrue(elementTypeLattice.isClassType());
+                  assertEquals(
+                      appInfo.dexItemFactory().stringType,
+                      elementTypeLattice.asClassTypeLatticeElement().getClassType());
+                  assertEquals(v.definition.isArgument(), l.isNullable());
+                } else if (l.isClassType()) {
+                  verifyClassTypeLattice(expectedLattices, mainClass, v, l);
+                }
+              });
+        });
   }
 
   @Test
   public void nonNullAfterSafeFieldAccess() throws Exception {
     MethodSignature signature = new MethodSignature("foo", "int",
         new String[]{FieldAccessTest.class.getCanonicalName()});
-    buildAndTest(NonNullAfterFieldAccess.class, signature, false, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
-      DexType testClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(testClass, maybeNull(), appInfo),
-          NonNull.class, fromDexType(testClass, definitelyNotNull(), appInfo),
-          // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, maybeNull(), appInfo),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
-    });
+    buildAndTest(
+        NonNullAfterFieldAccess.class,
+        signature,
+        false,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterFieldAccess.class.getCanonicalName()));
+          DexType testClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          FieldAccessTest.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  Argument.class, fromDexType(testClass, maybeNull(), appInfo),
+                  NonNull.class, fromDexType(testClass, definitelyNotNull(), appInfo),
+                  // instance may not be initialized.
+                  InstanceGet.class,
+                      fromDexType(appInfo.dexItemFactory().stringType, maybeNull(), appInfo),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
+        });
   }
 
   @Test
   public void stillNullAfterExceptionCatch_iget() throws Exception {
     MethodSignature signature = new MethodSignature("bar", "int",
         new String[]{FieldAccessTest.class.getCanonicalName()});
-    buildAndTest(NonNullAfterFieldAccess.class, signature, true, (appInfo, irCode) -> {
-      DexType assertionErrorType = appInfo.dexItemFactory.createType("Ljava/lang/AssertionError;");
-      DexType mainClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(NonNullAfterFieldAccess.class.getCanonicalName()));
-      DexType testClass = appInfo.dexItemFactory.createType(
-          DescriptorUtils.javaTypeToDescriptor(FieldAccessTest.class.getCanonicalName()));
-      Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices = ImmutableMap.of(
-          Argument.class, fromDexType(testClass, maybeNull(), appInfo),
-          NonNull.class, fromDexType(testClass, definitelyNotNull(), appInfo),
-          // instance may not be initialized.
-          InstanceGet.class, fromDexType(appInfo.dexItemFactory.stringType, maybeNull(), appInfo),
-          NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
-      forEachOutValue(irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
-    });
+    buildAndTest(
+        NonNullAfterFieldAccess.class,
+        signature,
+        true,
+        (appInfo, irCode) -> {
+          DexType assertionErrorType =
+              appInfo.dexItemFactory().createType("Ljava/lang/AssertionError;");
+          DexType mainClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          NonNullAfterFieldAccess.class.getCanonicalName()));
+          DexType testClass =
+              appInfo
+                  .dexItemFactory()
+                  .createType(
+                      DescriptorUtils.javaTypeToDescriptor(
+                          FieldAccessTest.class.getCanonicalName()));
+          Map<Class<? extends Instruction>, TypeLatticeElement> expectedLattices =
+              ImmutableMap.of(
+                  Argument.class, fromDexType(testClass, maybeNull(), appInfo),
+                  NonNull.class, fromDexType(testClass, definitelyNotNull(), appInfo),
+                  // instance may not be initialized.
+                  InstanceGet.class,
+                      fromDexType(appInfo.dexItemFactory().stringType, maybeNull(), appInfo),
+                  NewInstance.class, fromDexType(assertionErrorType, definitelyNotNull(), appInfo));
+          forEachOutValue(
+              irCode, (v, l) -> verifyClassTypeLattice(expectedLattices, mainClass, v, l));
+        });
   }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
index 3aa4394..880d428 100644
--- a/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
+++ b/src/test/java/com/android/tools/r8/ir/analysis/type/TypeLatticeTest.java
@@ -30,6 +30,7 @@
 import org.junit.Test;
 
 public class TypeLatticeTest extends TestBase {
+
   private static final String IO_EXCEPTION = "Ljava/io/IOException;";
   private static final String NOT_FOUND = "Ljava/io/FileNotFoundException;";
   private static final String INTERRUPT = "Ljava/io/InterruptedIOException;";
@@ -505,7 +506,7 @@
 
   @Test
   public void testSelfOrderWithoutSubtypingInfo() {
-    DexType type = appInfo.dexItemFactory.createType("Lmy/Type;");
+    DexType type = factory.createType("Lmy/Type;");
     TypeLatticeElement nonNullType =
         fromDexType(type, Nullability.definitelyNotNull(), appInfo);
     TypeLatticeElement nullableType = nonNullType.asNullable();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
index 0df0f97..3c003ab 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/ConstantRemovalTest.java
@@ -6,6 +6,7 @@
 import static org.junit.Assert.assertEquals;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.BasicBlock;
@@ -29,8 +30,8 @@
 public class ConstantRemovalTest {
 
   private static class MockLinearScanRegisterAllocator extends LinearScanRegisterAllocator {
-    public MockLinearScanRegisterAllocator(AppInfo appInfo, IRCode code, InternalOptions options) {
-      super(appInfo, code, options);
+    public MockLinearScanRegisterAllocator(AppView<? extends AppInfo> appView, IRCode code) {
+      super(appView, code);
     }
 
     @Override
@@ -130,6 +131,7 @@
     InternalOptions options = new InternalOptions();
     options.debug = true;
     AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
+    AppView<? extends AppInfo> appView = AppView.createForD8(appInfo, options);
     IRCode code =
         new IRCode(
             options,
@@ -140,7 +142,7 @@
             false,
             false,
             Origin.unknown());
-    PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appInfo, code, options));
+    PeepholeOptimizer.optimize(code, new MockLinearScanRegisterAllocator(appView, code));
 
     // Check that all four constant number instructions remain.
     assertEquals(
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
index 155a1ba..69e16ba 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/NonNullTrackerTest.java
@@ -35,7 +35,7 @@
       Consumer<IRCode> testAugmentedIRCode)
       throws Exception {
     AppView<? extends AppInfo> appView = build(testClass);
-    CodeInspector codeInspector = new CodeInspector(appView.appInfo().app);
+    CodeInspector codeInspector = new CodeInspector(appView.appInfo().app());
     MethodSubject fooSubject = codeInspector.clazz(testClass.getName()).method(signature);
     IRCode irCode = fooSubject.buildIR();
     checkCountOfNonNull(irCode, 0);
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
new file mode 100644
index 0000000..7acb30d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTest.java
@@ -0,0 +1,147 @@
+// 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.optimize.string;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.D8TestRunResult;
+import com.android.tools.r8.R8TestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRunResult;
+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.InstructionSubject.JumboStringMode;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.Streams;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class StringConcatenationTest extends TestBase {
+  private static final Class<?> MAIN = StringConcatenationTestClass.class;
+  private static final String JAVA_OUTPUT = StringUtils.lines(
+      "xyz",
+      "42",
+      "0.14 0 false null",
+      "Hello,R8",
+      "Hello,R8",
+      "Hello,",
+      "Hello,D8",
+      "na;na;na;na;na;na;na;na;Batman!",
+      "na;na;na;na;na;na;na;na;Batman!"
+  );
+
+  @Parameterized.Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  private final Backend backend;
+
+  public StringConcatenationTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void testJVMoutput() throws Exception {
+    assumeTrue("Only run JVM reference once (for CF backend)", backend == Backend.CF);
+    testForJvm().addTestClasspath().run(MAIN).assertSuccessWithOutput(JAVA_OUTPUT);
+  }
+
+  private void test(
+      TestRunResult result, int expectedStringCount1, int expectedStringCount2)
+      throws Exception {
+    CodeInspector codeInspector = result.inspector();
+    ClassSubject mainClass = codeInspector.clazz(MAIN);
+
+    MethodSubject method = mainClass.uniqueMethodWithName("trivialSequence");
+    assertThat(method, isPresent());
+    long count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(expectedStringCount1, count);
+
+    method = mainClass.uniqueMethodWithName("nonStringArgs");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(0, count);
+
+    method = mainClass.uniqueMethodWithName("typeConversion");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(0, count);
+
+    method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderItself");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(expectedStringCount2, count);
+
+    method = mainClass.uniqueMethodWithName("nestedBuilders_appendBuilderResult");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(expectedStringCount2, count);
+
+    method = mainClass.uniqueMethodWithName("simplePhi");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(5, count);
+
+    method = mainClass.uniqueMethodWithName("loop");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(3, count);
+
+    method = mainClass.uniqueMethodWithName("loopWithBuilder");
+    assertThat(method, isPresent());
+    count = Streams.stream(method.iterateInstructions(
+        i -> i.isConstString(JumboStringMode.ALLOW))).count();
+    assertEquals(2, count);
+  }
+
+  @Test
+  public void testD8() throws Exception {
+    assumeTrue("Only run D8 for Dex backend", backend == Backend.DEX);
+
+    D8TestRunResult result = testForD8()
+        .debug()
+        .addProgramClasses(MAIN)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    test(result, 3, 4);
+
+    result = testForD8()
+        .release()
+        .addProgramClasses(MAIN)
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    // TODO(b/114002137): could be 1 and 3.
+    test(result, 3, 4);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    // CF should not canonicalize strings or lower them. See (r8g/30163) and (r8g/30320).
+    assumeTrue(backend == Backend.DEX);
+    R8TestRunResult result = testForR8(backend)
+        .addProgramClasses(MAIN)
+        .enableInliningAnnotations()
+        .addKeepMainRule(MAIN)
+        .noMinification()
+        .run(MAIN)
+        .assertSuccessWithOutput(JAVA_OUTPUT);
+    // TODO(b/114002137): could be 1 and 3.
+    test(result, 3, 4);
+  }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
new file mode 100644
index 0000000..3cfb5de
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/string/StringConcatenationTestClass.java
@@ -0,0 +1,115 @@
+// 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.optimize.string;
+
+import com.android.tools.r8.NeverInline;
+
+class StringConcatenationTestClass {
+  @NeverInline
+  public static void trivialSequence() {
+    String x = "x";
+    String y = "y";
+    String z = "z";
+    System.out.println(x + y + z);
+  }
+
+  @NeverInline
+  public static void nonStringArgs() {
+    StringBuilder builder = new StringBuilder();
+    builder.append(4);
+    builder.append(2);
+    System.out.println(builder.toString());
+  }
+
+  @NeverInline
+  public static void typeConversion() {
+    StringBuilder builder = new StringBuilder();
+    float f = 0.14f;
+    builder.append(f);
+    builder.append(' ');
+    int i = (int) f;
+    builder.append(i);
+    builder.append(' ');
+
+    boolean b = false;
+    builder.append(b);
+    builder.append(' ');
+    Object n = null;
+    builder.append(n);
+    System.out.println(builder.toString());
+  }
+
+  @NeverInline
+  public static void nestedBuilders_appendBuilderItself() {
+    StringBuilder b1 = new StringBuilder();
+    b1.append("Hello");
+    b1.append(",");
+    StringBuilder b2 = new StringBuilder();
+    b2.append("R");
+    // TODO(b/114002137): switch to use the integer.
+    b2.append("8");
+    b1.append(b2);
+    System.out.println(b1.toString());
+  }
+
+  @NeverInline
+  public static void nestedBuilders_appendBuilderResult() {
+    StringBuilder b1 = new StringBuilder();
+    b1.append("Hello");
+    b1.append(",");
+    StringBuilder b2 = new StringBuilder();
+    b2.append("R");
+    // TODO(b/114002137): switch to use the integer.
+    b2.append("8");
+    b1.append(b2.toString());
+    System.out.println(b1.toString());
+  }
+
+  @NeverInline
+  public static void simplePhi() {
+    StringBuilder builder = new StringBuilder();
+    builder.append("Hello");
+    builder.append(",");
+    System.out.println(builder.toString());
+
+    if (System.currentTimeMillis() > 0) {
+      builder.append("D");
+    } else {
+      builder.append("R");
+    }
+    // TODO(b/114002137): switch to use the integer.
+    builder.append("8");
+    System.out.println(builder.toString());
+  }
+
+  @NeverInline
+  public static void loop() {
+    String r = "";
+    for (int i = 0; i < 8; i++) {
+      r = r + "na;";
+    }
+    System.out.println(r + "Batman!");
+  }
+
+  @NeverInline
+  public static void loopWithBuilder() {
+    StringBuilder builder = new StringBuilder();
+    for (int i = 0; i < 8; i++) {
+      builder.append("na;");
+    }
+    builder.append("Batman!");
+    System.out.println(builder.toString());
+  }
+
+  public static void main(String[] args) {
+    trivialSequence();
+    nonStringArgs();
+    typeConversion();
+    nestedBuilders_appendBuilderItself();
+    nestedBuilders_appendBuilderResult();
+    simplePhi();
+    loop();
+    loopWithBuilder();
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
index 12d0a78..d5a89c8 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/IdenticalAfterRegisterAllocationTest.java
@@ -41,7 +41,7 @@
     }
 
     @Override
-    public InternalOptions getOptions() {
+    public InternalOptions options() {
       return new InternalOptions();
     }
 
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
index a0e937d..e9836ae 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/Regress68656641.java
@@ -4,6 +4,7 @@
 package com.android.tools.r8.ir.regalloc;
 
 import com.android.tools.r8.graph.AppInfo;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication;
 import com.android.tools.r8.ir.analysis.type.TypeLatticeElement;
 import com.android.tools.r8.ir.code.IRCode;
@@ -21,8 +22,8 @@
 public class Regress68656641 extends SmaliTestBase {
 
   private static class MyRegisterAllocator extends LinearScanRegisterAllocator {
-    public MyRegisterAllocator(AppInfo appInfo, IRCode code, InternalOptions options) {
-      super(appInfo, code, options);
+    public MyRegisterAllocator(AppView<? extends AppInfo> appView, IRCode code) {
+      super(appView, code);
     }
 
     public void addInactiveIntervals(LiveIntervals intervals) {
@@ -56,8 +57,9 @@
   public void splitOverlappingInactiveIntervalWithNoNextUse() {
     InternalOptions options = new InternalOptions();
     AppInfo appInfo = new AppInfo(DexApplication.builder(options.itemFactory, null).build());
+    AppView<? extends AppInfo> appView = AppView.createForD8(appInfo, options);
     IRCode code = simpleCode();
-    MyRegisterAllocator allocator = new MyRegisterAllocator(appInfo, code, options);
+    MyRegisterAllocator allocator = new MyRegisterAllocator(appView, code);
     // Setup live an inactive live interval with ranges [0, 10[ and [20, 30[ with only
     // uses in the first interval and which is linked to another interval.
     LiveIntervals inactiveIntervals =
diff --git a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
index 483b02e..5d4f802 100644
--- a/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
+++ b/src/test/java/com/android/tools/r8/maindexlist/MainDexListTests.java
@@ -752,8 +752,9 @@
                 DexAnnotationSet.empty(),
                 ParameterAnnotationsList.empty(),
                 code);
-        IRCode ir = code.buildIR(method, AppView.createForR8(null, options), Origin.unknown());
-        RegisterAllocator allocator = new LinearScanRegisterAllocator(appInfo, ir, options);
+        AppView<? extends AppInfo> appView = AppView.createForR8(null, options);
+        IRCode ir = code.buildIR(method, appView, Origin.unknown());
+        RegisterAllocator allocator = new LinearScanRegisterAllocator(appView, ir);
         method.setCode(ir, allocator, options);
         directMethods[i] = method;
       }
diff --git a/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
new file mode 100644
index 0000000..1727bd6
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/naming/FieldMinificationCollisionTest.java
@@ -0,0 +1,92 @@
+// 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.naming;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.NeverClassInline;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NeverMerge;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.FieldSubject;
+import org.junit.Test;
+
+/** Regression test for b/127932803. */
+public class FieldMinificationCollisionTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("ABC");
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(FieldMinificationCollisionTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-keep class " + B.class.getTypeName() + " { public java.lang.String f2; }")
+            .enableClassInliningAnnotations()
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    FieldSubject f1Subject = inspector.clazz(A.class).uniqueFieldWithName("f1");
+    assertThat(f1Subject, isPresent());
+
+    FieldSubject f3Subject = inspector.clazz(C.class).uniqueFieldWithName("f3");
+    assertThat(f3Subject, isPresent());
+
+    // TODO(b/127932803): f1 and f3 should not be given the same name.
+    assertEquals(f1Subject.getFinalName(), f3Subject.getFinalName());
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new C("A", "B", "C").print();
+    }
+  }
+
+  @NeverMerge
+  static class A {
+
+    public String f1;
+
+    public A(String f1) {
+      this.f1 = f1;
+    }
+  }
+
+  @NeverMerge
+  static class B extends A {
+
+    public String f2;
+
+    public B(String f1, String f2) {
+      super(f1);
+      this.f2 = f2;
+    }
+  }
+
+  @NeverClassInline
+  static class C extends B {
+
+    public String f3;
+
+    public C(String f1, String f2, String f3) {
+      super(f1, f2);
+      this.f3 = f3;
+    }
+
+    @NeverInline
+    public void print() {
+      System.out.println(f1 + f2 + f3);
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
index 93e4a4f..6b7005c 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -74,8 +74,7 @@
         AppView.createForR8(new AppInfoWithSubtyping(program), options);
     appView.setAppServices(AppServices.builder(appView).build());
 
-    RootSet rootSet =
-        new RootSetBuilder(appView, program, configuration.getRules(), options).run(executor);
+    RootSet rootSet = new RootSetBuilder(appView, program, configuration.getRules()).run(executor);
 
     Enqueuer enqueuer = new Enqueuer(appView, options, null);
     appView.setAppInfo(
diff --git a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
index 766d8de..6d71487 100644
--- a/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
+++ b/src/test/java/com/android/tools/r8/resolution/SingleTargetLookupTest.java
@@ -111,10 +111,7 @@
     ExecutorService executor = Executors.newSingleThreadExecutor();
     RootSet rootSet =
         new RootSetBuilder(
-                appView,
-                application,
-                buildKeepRuleForClass(Main.class, application.dexItemFactory),
-                options)
+                appView, application, buildKeepRuleForClass(Main.class, application.dexItemFactory))
             .run(executor);
     appInfo =
         new Enqueuer(appView, options, null)
@@ -203,15 +200,17 @@
   }
 
   private static DexMethod buildMethod(Class clazz, String name) {
-    return appInfo.dexItemFactory.createMethod(
-        toType(clazz),
-        appInfo.dexItemFactory.createProto(appInfo.dexItemFactory.voidType),
-        name
-    );
+    return appInfo
+        .dexItemFactory()
+        .createMethod(
+            toType(clazz),
+            appInfo.dexItemFactory().createProto(appInfo.dexItemFactory().voidType),
+            name);
   }
 
   private static DexType toType(Class clazz) {
-    return appInfo.dexItemFactory
+    return appInfo
+        .dexItemFactory()
         .createType(DescriptorUtils.javaTypeToDescriptor(clazz.getCanonicalName()));
   }
 
diff --git a/tools/archive.py b/tools/archive.py
index 25e6a4f..9213cc2 100755
--- a/tools/archive.py
+++ b/tools/archive.py
@@ -138,7 +138,7 @@
     version = GetGitHash()
 
   destination = GetVersionDestination('gs://', version, is_master)
-  if utils.cloud_storage_exists(destination):
+  if utils.cloud_storage_exists(destination) and not options.dry_run:
     raise Exception('Target archive directory %s already exists' % destination)
   with utils.TempDir() as temp:
     version_file = os.path.join(temp, 'r8-version.properties')
diff --git a/tools/as_utils.py b/tools/as_utils.py
index a5bfffe..83d0d15 100644
--- a/tools/as_utils.py
+++ b/tools/as_utils.py
@@ -171,12 +171,13 @@
 def MoveProfileReportTo(dest_dir, build_stdout, quiet=False):
   html_file = None
   profile_message = 'See the profiling report at: '
+  # We are not interested in the profiling report for buildSrc.
   for line in build_stdout:
-    if profile_message in line:
+    if (profile_message in line) and ('buildSrc' not in line):
+      assert not html_file, "Only one report should be created"
       html_file = line[len(profile_message):]
       if html_file.startswith('file://'):
         html_file = html_file[len('file://'):]
-      break
 
   if not html_file:
     return
diff --git a/tools/run_on_as_app.py b/tools/run_on_as_app.py
index 425b84a..5596cd5 100755
--- a/tools/run_on_as_app.py
+++ b/tools/run_on_as_app.py
@@ -250,7 +250,6 @@
               'module': '',
               'flavor': 'play',
               'main_dex_rules': 'multidex-config.pro',
-              'releaseTarget': 'assemblePlayRelease',
               'signed_apk_name': 'Signal-play-release-4.32.7.apk'
           })
       ]
@@ -287,8 +286,7 @@
       'apps': [
           App({
               'id': 'eu.kanade.tachiyomi',
-              'flavor': 'standard',
-              'releaseTarget': 'app:assembleRelease',
+              'flavor': 'dev',
               'min_sdk': 16
           })
       ]
@@ -680,8 +678,7 @@
     app, repo, options, checkout_dir, temp_dir, shrinker, proguard_config_file):
   recompilation_results = []
 
-  # Build app with gradle using -D...keepRuleSynthesisForRecompilation=
-  # true.
+  # Build app with gradle using -D...keepRuleSynthesisForRecompilation=true.
   out_dir = os.path.join(checkout_dir, 'out', shrinker + '-1')
   (apk_dest, profile_dest_dir, ext_proguard_config_file) = \
       BuildAppWithShrinker(
@@ -1072,10 +1069,10 @@
     else:
       # Make a copy of r8.jar and r8lib.jar such that they stay the same for
       # the entire execution of this script.
-      if 'r8-nolib' in options.shrinker:
+      if 'r8-nolib' in options.shrinker or 'r8-nolib-full' in options.shrinker:
         assert os.path.isfile(utils.R8_JAR), 'Cannot build without r8.jar'
         shutil.copyfile(utils.R8_JAR, os.path.join(temp_dir, 'r8.jar'))
-      if 'r8' in options.shrinker:
+      if 'r8' in options.shrinker or 'r8-full' in options.shrinker:
         assert os.path.isfile(utils.R8LIB_JAR), 'Cannot build without r8lib.jar'
         shutil.copyfile(utils.R8LIB_JAR, os.path.join(temp_dir, 'r8lib.jar'))