Merge "Ignore annotations for non-existent parameters"
diff --git a/src/main/java/com/android/tools/r8/R8.java b/src/main/java/com/android/tools/r8/R8.java
index 7bed8ae..2f40a77 100644
--- a/src/main/java/com/android/tools/r8/R8.java
+++ b/src/main/java/com/android/tools/r8/R8.java
@@ -256,7 +256,7 @@
       String proguardSeedsData = null;
       timing.begin("Strip unused code");
       try {
-        Set<DexType> missingClasses = appView.getAppInfo().getMissingClasses();
+        Set<DexType> missingClasses = appView.appInfo().getMissingClasses();
         missingClasses = filterMissingClasses(
             missingClasses, options.proguardConfiguration.getDontWarnPatterns());
         if (!missingClasses.isEmpty()) {
@@ -273,14 +273,14 @@
 
         // Compute kotlin info before setting the roots and before
         // kotlin metadata annotation is removed.
-        computeKotlinInfoForProgramClasses(application, appView.getAppInfo());
+        computeKotlinInfoForProgramClasses(application, appView.appInfo());
 
         final ProguardConfiguration.Builder compatibility =
             ProguardConfiguration.builder(application.dexItemFactory, options.reporter);
 
         rootSet =
             new RootSetBuilder(
-                    appView.getAppInfo(),
+                    appView.appInfo(),
                     application,
                     options.proguardConfiguration.getRules(),
                     options)
@@ -288,8 +288,8 @@
 
         Enqueuer enqueuer =
             new Enqueuer(
-                appView.getAppInfo(),
-                appView.getGraphLense(),
+                appView.appInfo(),
+                appView.graphLense(),
                 options,
                 options.forceProguardCompatibility,
                 compatibility);
@@ -297,23 +297,23 @@
         if (options.proguardConfiguration.isPrintSeeds()) {
           ByteArrayOutputStream bytes = new ByteArrayOutputStream();
           PrintStream out = new PrintStream(bytes);
-          RootSetBuilder.writeSeeds(appView.getAppInfo().withLiveness(), out, type -> true);
+          RootSetBuilder.writeSeeds(appView.appInfo().withLiveness(), out, type -> true);
           out.flush();
           proguardSeedsData = bytes.toString();
         }
         if (options.enableTreeShaking) {
           TreePruner pruner =
-              new TreePruner(application, appView.getAppInfo().withLiveness(), options);
+              new TreePruner(application, appView.appInfo().withLiveness(), options);
           application = pruner.run();
           // Recompute the subtyping information.
           appView.setAppInfo(
               appView
-                  .getAppInfo()
+                  .appInfo()
                   .withLiveness()
                   .prunedCopyFrom(application, pruner.getRemovedClasses()));
-          new AbstractMethodRemover(appView.getAppInfo()).run();
+          new AbstractMethodRemover(appView.appInfo()).run();
         }
-        new AnnotationRemover(appView.getAppInfo().withLiveness(), compatibility, options).run();
+        new AnnotationRemover(appView.appInfo().withLiveness(), compatibility, options).run();
 
         // TODO(69445518): This is still work in progress, and this file writing is currently used
         // for testing.
@@ -341,10 +341,10 @@
         // We can now remove visibility bridges. Note that we do not need to update the
         // invoke-targets here, as the existing invokes will simply dispatch to the now
         // visible super-method. MemberRebinding, if run, will then dispatch it correctly.
-        application = new VisibilityBridgeRemover(appView.getAppInfo(), application).run();
+        application = new VisibilityBridgeRemover(appView.appInfo(), application).run();
       }
 
-      if (appView.getAppInfo().hasLiveness()) {
+      if (appView.appInfo().hasLiveness()) {
         AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
 
         if (options.proguardConfiguration.hasApplyMappingFile()) {
@@ -353,28 +353,29 @@
           timing.begin("apply-mapping");
           appView.setGraphLense(
               new ProguardMapApplier(appView.withLiveness(), seedMapper).run(timing));
-          application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+          application = application.asDirect().rewrittenWithLense(appView.graphLense());
           appView.setAppInfo(
               appView
-                  .getAppInfo()
+                  .appInfo()
                   .withLiveness()
-                  .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
+                  .rewrittenWithLense(application.asDirect(), appView.graphLense()));
           timing.end();
         }
         appView.setGraphLense(new MemberRebindingAnalysis(appViewWithLiveness).run());
         if (options.enableVerticalClassMerging) {
           timing.begin("ClassMerger");
-          VerticalClassMerger classMerger =
+          VerticalClassMerger verticalClassMerger =
               new VerticalClassMerger(
                   application, appViewWithLiveness, executorService, options, timing);
-          appView.setGraphLense(classMerger.run());
+          appView.setGraphLense(verticalClassMerger.run());
+          appView.setVerticallyMergedClasses(verticalClassMerger.getMergedClasses());
           timing.end();
-          application = application.asDirect().rewrittenWithLense(appView.getGraphLense());
+          application = application.asDirect().rewrittenWithLense(appView.graphLense());
           appViewWithLiveness.setAppInfo(
               appViewWithLiveness
-                  .getAppInfo()
-                  .prunedCopyFrom(application, classMerger.getRemovedClasses())
-                  .rewrittenWithLense(application.asDirect(), appView.getGraphLense()));
+                  .appInfo()
+                  .prunedCopyFrom(application, verticalClassMerger.getRemovedClasses())
+                  .rewrittenWithLense(application.asDirect(), appView.graphLense()));
         }
         // Collect switch maps and ordinals maps.
         appViewWithLiveness.setAppInfo(new SwitchMapCollector(appViewWithLiveness, options).run());
@@ -389,11 +390,8 @@
       Set<DexCallSite> desugaredCallSites;
       CfgPrinter printer = options.printCfg ? new CfgPrinter() : null;
       try {
-        IRConverter converter =
-            new IRConverter(
-                appView.getAppInfo(), options, timing, printer, appView.getGraphLense());
+        IRConverter converter = new IRConverter(appView, options, timing, printer);
         application = converter.optimize(application, executorService);
-        appView.setGraphLense(converter.getGraphLense());
         desugaredCallSites = converter.getDesugaredCallSites();
       } finally {
         timing.end();
@@ -413,16 +411,15 @@
 
       // Overwrite SourceFile if specified. This step should be done after IR conversion.
       timing.begin("Rename SourceFile");
-      new SourceFileRewriter(appView.getAppInfo(), options).run();
+      new SourceFileRewriter(appView.appInfo(), options).run();
       timing.end();
 
       if (!options.mainDexKeepRules.isEmpty()) {
         appView.setAppInfo(new AppInfoWithSubtyping(application));
-        Enqueuer enqueuer =
-            new Enqueuer(appView.getAppInfo(), appView.getGraphLense(), options, true);
+        Enqueuer enqueuer = new Enqueuer(appView.appInfo(), appView.graphLense(), options, true);
         // Lets find classes which may have code executed before secondary dex files installation.
         RootSet mainDexRootSet =
-            new RootSetBuilder(appView.getAppInfo(), application, options.mainDexKeepRules, options)
+            new RootSetBuilder(appView.appInfo(), application, options.mainDexKeepRules, options)
                 .run(executorService);
         AppInfoWithLiveness mainDexAppInfo =
             enqueuer.traceMainDex(mainDexRootSet, executorService, timing);
@@ -444,20 +441,19 @@
         try {
           Enqueuer enqueuer =
               new Enqueuer(
-                  appView.getAppInfo(),
-                  appView.getGraphLense(),
+                  appView.appInfo(),
+                  appView.graphLense(),
                   options,
                   options.forceProguardCompatibility);
           appView.setAppInfo(enqueuer.traceApplication(rootSet, executorService, timing));
 
           AppView<AppInfoWithLiveness> appViewWithLiveness = appView.withLiveness();
           if (options.enableTreeShaking) {
-            TreePruner pruner =
-                new TreePruner(application, appViewWithLiveness.getAppInfo(), options);
+            TreePruner pruner = new TreePruner(application, appViewWithLiveness.appInfo(), options);
             application = pruner.run();
             appViewWithLiveness.setAppInfo(
                 appViewWithLiveness
-                    .getAppInfo()
+                    .appInfo()
                     .prunedCopyFrom(application, pruner.getRemovedClasses()));
             // Print reasons on the application after pruning, so that we reflect the actual result.
             ReasonPrinter reasonPrinter = enqueuer.getReasonPrinter(rootSet.reasonAsked);
@@ -478,8 +474,7 @@
       // will happen. Just avoid the overhead.
       NamingLens namingLens =
           options.enableMinification
-              ? new Minifier(
-                      appView.getAppInfo().withLiveness(), rootSet, desugaredCallSites, options)
+              ? new Minifier(appView.appInfo().withLiveness(), rootSet, desugaredCallSites, options)
                   .run(timing)
               : NamingLens.getIdentityLens();
       timing.end();
@@ -491,7 +486,7 @@
         ClassNameMapper classNameMapper =
             LineNumberOptimizer.run(
                 application,
-                appView.getGraphLense(),
+                appView.graphLense(),
                 namingLens,
                 options.lineNumberOptimization == LineNumberOptimization.IDENTITY_MAPPING);
         timing.end();
@@ -514,7 +509,7 @@
           executorService,
           application,
           application.deadCode,
-          appView.getGraphLense(),
+          appView.graphLense(),
           namingLens,
           proguardSeedsData,
           options,
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 87232d2..0d4e79d 100644
--- a/src/main/java/com/android/tools/r8/Version.java
+++ b/src/main/java/com/android/tools/r8/Version.java
@@ -11,7 +11,7 @@
 
   // This field is accessed from release scripts using simple pattern matching.
   // Therefore, changing this field could break our release scripts.
-  public static final String LABEL = "1.3.22-dev";
+  public static final String LABEL = "1.4.0-dev";
 
   private Version() {
   }
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 64a4ce4..3e4d611 100644
--- a/src/main/java/com/android/tools/r8/graph/AppView.java
+++ b/src/main/java/com/android/tools/r8/graph/AppView.java
@@ -5,12 +5,14 @@
 package com.android.tools.r8.graph;
 
 import com.android.tools.r8.shaking.Enqueuer.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
 
 public class AppView<T extends AppInfo> {
 
   private T appInfo;
   private final DexItemFactory dexItemFactory;
   private GraphLense graphLense;
+  private VerticallyMergedClasses verticallyMergedClasses;
 
   public AppView(T appInfo, GraphLense graphLense) {
     this.appInfo = appInfo;
@@ -18,7 +20,7 @@
     this.graphLense = graphLense;
   }
 
-  public T getAppInfo() {
+  public T appInfo() {
     return appInfo;
   }
 
@@ -26,11 +28,11 @@
     this.appInfo = appInfo;
   }
 
-  public DexItemFactory getDexItemFactory() {
+  public DexItemFactory dexItemFactory() {
     return dexItemFactory;
   }
 
-  public GraphLense getGraphLense() {
+  public GraphLense graphLense() {
     return graphLense;
   }
 
@@ -38,6 +40,16 @@
     this.graphLense = graphLense;
   }
 
+  // Get the result of vertical class merging. Returns null if vertical class merging has not been
+  // run.
+  public VerticallyMergedClasses verticallyMergedClasses() {
+    return verticallyMergedClasses;
+  }
+
+  public void setVerticallyMergedClasses(VerticallyMergedClasses verticallyMergedClasses) {
+    this.verticallyMergedClasses = verticallyMergedClasses;
+  }
+
   public AppView<AppInfoWithLiveness> withLiveness() {
     return new AppViewWithLiveness();
   }
@@ -49,8 +61,8 @@
     }
 
     @Override
-    public AppInfoWithLiveness getAppInfo() {
-      return AppView.this.getAppInfo().withLiveness();
+    public AppInfoWithLiveness appInfo() {
+      return AppView.this.appInfo().withLiveness();
     }
 
     @Override
@@ -61,13 +73,13 @@
     }
 
     @Override
-    public DexItemFactory getDexItemFactory() {
-      return AppView.this.dexItemFactory;
+    public DexItemFactory dexItemFactory() {
+      return AppView.this.dexItemFactory();
     }
 
     @Override
-    public GraphLense getGraphLense() {
-      return AppView.this.getGraphLense();
+    public GraphLense graphLense() {
+      return AppView.this.graphLense();
     }
 
     @Override
diff --git a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
index f1c1700..68f4c95 100644
--- a/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
+++ b/src/main/java/com/android/tools/r8/graph/LazyCfCode.java
@@ -86,7 +86,6 @@
 
   public LazyCfCode(
       DexMethod method, Origin origin, ReparseContext context, JarApplicationReader application) {
-
     this.method = method;
     this.origin = origin;
     this.context = context;
diff --git a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
index 08d7a07..fbbe2fd 100644
--- a/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/BasicBlockInstructionIterator.java
@@ -154,15 +154,6 @@
   }
 
   @Override
-  public void detach() {
-    if (current == null) {
-      throw new IllegalStateException();
-    }
-    listIterator.remove();
-    current = null;
-  }
-
-  @Override
   public void replaceCurrentInstruction(Instruction newInstruction) {
     if (current == null) {
       throw new IllegalStateException();
diff --git a/src/main/java/com/android/tools/r8/ir/code/ConstString.java b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
index f2ecc89..b5a3b90 100644
--- a/src/main/java/com/android/tools/r8/ir/code/ConstString.java
+++ b/src/main/java/com/android/tools/r8/ir/code/ConstString.java
@@ -26,6 +26,12 @@
     this.value = value;
   }
 
+  public static ConstString copyOf(IRCode code, ConstString original) {
+    Value newValue =
+        new Value(code.valueNumberGenerator.next(), original.outType(), original.getLocalInfo());
+    return new ConstString(newValue, original.getValue());
+  }
+
   public Value dest() {
     return outValue;
   }
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 6f62d21..8f1a004 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
@@ -74,10 +74,6 @@
     return outValue.isNeverNull();
   }
 
-  public final void forceSetPosition(Position position) {
-    this.position = position;
-  }
-
   public String getPositionAsString() {
     return position == null ? "???" : position.toString();
   }
diff --git a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
index 5d5e77c..cde197c 100644
--- a/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
+++ b/src/main/java/com/android/tools/r8/ir/code/InstructionListIterator.java
@@ -72,16 +72,6 @@
   void removeOrReplaceByDebugLocalRead();
 
   /**
-   * Remove the current instruction (aka the {@link Instruction} returned by the previous call to
-   * {@link #next}) without updating its def/use chains.
-   * <p>
-   * This is useful for instance when moving an instruction to another block that still dominates
-   * all its uses. In order to do that you would detach the instruction from the original
-   * block and add it to the new block.
-   */
-  void detach();
-
-  /**
    * Replace the current instruction (aka the {@link Instruction} returned by the previous call to
    * {@link #next} with the passed in <code>newInstruction</code>.
    *
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
index 1ee2f6b..524de92 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/CallGraph.java
@@ -5,6 +5,7 @@
 package com.android.tools.r8.ir.conversion;
 
 import com.android.tools.r8.errors.CompilationError;
+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.DexEncodedMethod;
@@ -147,8 +148,7 @@
 
   public static CallGraph build(
       DexApplication application,
-      AppInfoWithLiveness appInfo,
-      GraphLense graphLense,
+      AppView<AppInfoWithLiveness> appView,
       InternalOptions options,
       Timing timing) {
     CallGraph graph = new CallGraph(options);
@@ -157,7 +157,7 @@
     for (DexClass clazz : classes) {
       for (DexEncodedMethod method : clazz.allMethodsSorted()) {
         Node node = graph.ensureMethodNode(method);
-        InvokeExtractor extractor = new InvokeExtractor(appInfo, graphLense, node, graph);
+        InvokeExtractor extractor = new InvokeExtractor(appView, node, graph);
         method.registerCodeReferences(extractor);
       }
     }
@@ -169,7 +169,7 @@
     timing.end();
     assert cycleEliminator.breakCycles() == 0; // This time the cycles should be gone.
 
-    graph.fillCallSiteSets(appInfo);
+    graph.fillCallSiteSets(appView.appInfo());
     return graph;
   }
 
@@ -478,16 +478,15 @@
 
   private static class InvokeExtractor extends UseRegistry {
 
-    AppInfoWithLiveness appInfo;
-    GraphLense graphLense;
-    Node caller;
-    CallGraph graph;
+    private final AppInfoWithLiveness appInfo;
+    private final GraphLense graphLense;
+    private final Node caller;
+    private final CallGraph graph;
 
-    InvokeExtractor(AppInfoWithLiveness appInfo, GraphLense graphLense, Node caller,
-        CallGraph graph) {
-      super(appInfo.dexItemFactory);
-      this.appInfo = appInfo;
-      this.graphLense = graphLense;
+    InvokeExtractor(AppView<AppInfoWithLiveness> appView, Node caller, CallGraph graph) {
+      super(appView.dexItemFactory());
+      this.appInfo = appView.appInfo();
+      this.graphLense = appView.graphLense();
       this.caller = caller;
       this.graph = graph;
     }
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 6890595..1881f79 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
@@ -9,6 +9,7 @@
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.CfCode;
 import com.android.tools.r8.graph.Code;
 import com.android.tools.r8.graph.DexAnnotation;
@@ -91,8 +92,10 @@
 
   private static final int PEEPHOLE_OPTIMIZATION_PASSES = 2;
 
-  private final Timing timing;
   public final AppInfo appInfo;
+  public final AppView<? extends AppInfoWithSubtyping> appView;
+
+  private final Timing timing;
   private final Outliner outliner;
   private final StringConcatRewriter stringConcatRewriter;
   private final LambdaRewriter lambdaRewriter;
@@ -103,7 +106,6 @@
   private final ClassStaticizer classStaticizer;
   private final InternalOptions options;
   private final CfgPrinter printer;
-  private GraphLense graphLense;
   private final CodeRewriter codeRewriter;
   private final MemberValuePropagation memberValuePropagation;
   private final LensCodeRewriter lensCodeRewriter;
@@ -124,19 +126,20 @@
   // the current class being optimized.
   private ConcurrentHashMap<DexType, DexProgramClass> cachedClasses = new ConcurrentHashMap<>();
 
+  // The argument `appView` is only available when full program optimizations are allowed
+  // (i.e., when running R8).
   private IRConverter(
       AppInfo appInfo,
       InternalOptions options,
       Timing timing,
       CfgPrinter printer,
-      GraphLense graphLense,
-      boolean enableWholeProgramOptimizations) {
+      AppView<? extends AppInfoWithSubtyping> appView) {
     assert appInfo != null;
     assert options != null;
     assert options.programConsumer != null;
     this.timing = timing != null ? timing : new Timing("internal");
     this.appInfo = appInfo;
-    this.graphLense = graphLense != null ? graphLense : GraphLense.getIdentityLense();
+    this.appView = appView;
     this.options = options;
     this.printer = printer;
     this.codeRewriter = new CodeRewriter(this, libraryMethodsReturningReceiver(), options);
@@ -155,16 +158,16 @@
             ? new CovariantReturnTypeAnnotationTransformer(this, appInfo.dexItemFactory)
             : null;
     this.stringOptimizer = new StringOptimizer();
-    this.enableWholeProgramOptimizations = enableWholeProgramOptimizations;
+    this.enableWholeProgramOptimizations = appView != null;
     if (enableWholeProgramOptimizations) {
       assert appInfo.hasLiveness();
       this.nonNullTracker = new NonNullTracker();
-      this.inliner = new Inliner(this, options);
+      this.inliner = new Inliner(appView.withLiveness(), this, options);
       this.outliner = new Outliner(appInfo.withLiveness(), options, this);
       this.memberValuePropagation =
           options.enableValuePropagation ?
               new MemberValuePropagation(appInfo.withLiveness()) : null;
-      this.lensCodeRewriter = new LensCodeRewriter(graphLense, appInfo.withSubtyping());
+      this.lensCodeRewriter = new LensCodeRewriter(appView, options);
       if (appInfo.hasLiveness()) {
         if (!appInfo.withLiveness().identifierNameStrings.isEmpty() && options.enableMinification) {
           this.identifierNameStringMarker =
@@ -196,13 +199,8 @@
         ? new ClassStaticizer(appInfo.withLiveness(), this) : null;
   }
 
-  public void setGraphLense(GraphLense graphLense) {
-    assert graphLense != null;
-    this.graphLense = graphLense;
-  }
-
-  public GraphLense getGraphLense() {
-    return graphLense;
+  public GraphLense graphLense() {
+    return appView != null ? appView.graphLense() : GraphLense.getIdentityLense();
   }
 
   public Set<DexCallSite> getDesugaredCallSites() {
@@ -216,33 +214,26 @@
   /**
    * Create an IR converter for processing methods with full program optimization disabled.
    */
-  public IRConverter(
-      AppInfo appInfo,
-      InternalOptions options) {
-    this(appInfo, options, null, null, null, false);
+  public IRConverter(AppInfo appInfo, InternalOptions options) {
+    this(appInfo, options, null, null, null);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization disabled.
    */
-  public IRConverter(
-      AppInfo appInfo,
-      InternalOptions options,
-      Timing timing,
-      CfgPrinter printer) {
-    this(appInfo, options, timing, printer, null, false);
+  public IRConverter(AppInfo appInfo, InternalOptions options, Timing timing, CfgPrinter printer) {
+    this(appInfo, options, timing, printer, null);
   }
 
   /**
    * Create an IR converter for processing methods with full program optimization enabled.
    */
   public IRConverter(
-      AppInfoWithSubtyping appInfo,
+      AppView<AppInfoWithSubtyping> appView,
       InternalOptions options,
       Timing timing,
-      CfgPrinter printer,
-      GraphLense graphLense) {
-    this(appInfo, options, timing, printer, graphLense, true);
+      CfgPrinter printer) {
+    this(appView.appInfo(), options, timing, printer, appView);
   }
 
   private boolean enableInterfaceMethodDesugaring() {
@@ -472,8 +463,7 @@
     OptimizationFeedback directFeedback = new OptimizationFeedbackDirect();
     {
       timing.begin("Build call graph");
-      CallGraph callGraph =
-          CallGraph.build(application, appInfo.withLiveness(), graphLense, options, timing);
+      CallGraph callGraph = CallGraph.build(application, appView.withLiveness(), options, timing);
       timing.end();
       timing.begin("IR conversion phase 1");
       BiConsumer<IRCode, DexEncodedMethod> outlineHandler =
@@ -545,6 +535,7 @@
   private void forEachSelectedOutliningMethod(
       ExecutorService executorService, BiConsumer<IRCode, DexEncodedMethod> consumer)
       throws ExecutionException {
+    assert appView != null;
     assert !options.skipIR;
     Set<DexEncodedMethod> methods = outliner.getMethodsSelectedForOutlining();
     List<Future<?>> futures = new ArrayList<>();
@@ -554,7 +545,8 @@
               () -> {
                 IRCode code =
                     method.buildIR(
-                        appInfo, graphLense, options, appInfo.originFor(method.method.holder));
+                        appInfo, appView.graphLense(), options,
+                        appInfo.originFor(method.method.holder));
                 assert code != null;
                 assert !method.getCode().isOutlineCode();
                 // Instead of repeating all the optimizations of rewriteCode(), only run the
@@ -562,7 +554,7 @@
                 // StringBuilder/StringBuffer method invocations, and removeDeadCode() to remove
                 // unused out-values.
                 codeRewriter.rewriteMoveResult(code);
-                DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
+                DeadCodeRemover.removeDeadCode(code, codeRewriter, appView.graphLense(), options);
                 consumer.accept(code, method);
                 return null;
               }));
@@ -712,7 +704,7 @@
       return;
     }
     IRCode code =
-        method.buildIR(appInfo, graphLense, options, appInfo.originFor(method.method.holder));
+        method.buildIR(appInfo, graphLense(), options, appInfo.originFor(method.method.holder));
     if (code == null) {
       feedback.markProcessed(method, ConstraintWithTarget.NEVER);
       return;
@@ -742,7 +734,7 @@
       if (lensCodeRewriter != null) {
         lensCodeRewriter.rewrite(code, method);
       } else {
-        assert graphLense.isIdentityLense();
+        assert graphLense().isIdentityLense();
       }
     }
 
@@ -774,8 +766,7 @@
       // TODO(zerny): Should we support inlining in debug mode? b/62937285
       assert !options.debug;
       new TypeAnalysis(appInfo, method).widening(method, code);
-      inliner.performInlining(
-          method, code, isProcessedConcurrently, callSiteInformation);
+      inliner.performInlining(method, code, isProcessedConcurrently, callSiteInformation);
     }
     if (!options.debug) {
       stringOptimizer.computeConstStringLength(code, appInfo.dexItemFactory);
@@ -813,7 +804,7 @@
     // Dead code removal. Performed after simplifications to remove code that becomes dead
     // as a result of those simplifications. The following optimizations could reveal more
     // dead code which is removed right before register allocation in performRegisterAllocation.
-    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
+    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense(), options);
     assert code.isConsistentSSA();
 
     if (options.enableDesugaring && enableTryWithResourcesDesugaring()) {
@@ -829,7 +820,7 @@
 
     if (classInliner != null) {
       // Class inliner should work before lambda merger, so if it inlines the
-      // lambda, it is not get collected by merger.
+      // lambda, it does not get collected by merger.
       assert options.enableInlining && inliner != null;
       classInliner.processMethodCode(
           appInfo.withLiveness(), codeRewriter, method, code, isProcessedConcurrently,
@@ -948,7 +939,7 @@
   private void finalizeToCf(DexEncodedMethod method, IRCode code, OptimizationFeedback feedback) {
     assert !method.getCode().isDexCode();
     CfBuilder builder = new CfBuilder(method, code, options.itemFactory);
-    CfCode result = builder.build(codeRewriter, graphLense, options, appInfo);
+    CfCode result = builder.build(codeRewriter, graphLense(), options, appInfo);
     method.setCode(result);
     markProcessed(method, code, feedback);
   }
@@ -992,7 +983,7 @@
   private RegisterAllocator performRegisterAllocation(IRCode code, DexEncodedMethod method) {
     // Always perform dead code elimination before register allocation. The register allocator
     // does not allow dead code (to make sure that we do not waste registers for unneeded values).
-    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense, options);
+    DeadCodeRemover.removeDeadCode(code, codeRewriter, graphLense(), options);
     materializeInstructionBeforeLongOperationsWorkaround(code);
     workaroundForwardingInitializerBug(code);
     LinearScanRegisterAllocator registerAllocator = new LinearScanRegisterAllocator(code, options);
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 ef490f7..283c163 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
@@ -7,6 +7,7 @@
 import static com.android.tools.r8.graph.UseRegistry.MethodHandleUse.NOT_ARGUMENT_TO_LAMBDA_METAFACTORY;
 
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
@@ -34,6 +35,7 @@
 import com.android.tools.r8.ir.code.InstructionListIterator;
 import com.android.tools.r8.ir.code.Invoke;
 import com.android.tools.r8.ir.code.InvokeCustom;
+import com.android.tools.r8.ir.code.InvokeDirect;
 import com.android.tools.r8.ir.code.InvokeMethod;
 import com.android.tools.r8.ir.code.InvokeMultiNewArray;
 import com.android.tools.r8.ir.code.InvokeNewArray;
@@ -42,6 +44,8 @@
 import com.android.tools.r8.ir.code.StaticGet;
 import com.android.tools.r8.ir.code.StaticPut;
 import com.android.tools.r8.ir.code.Value;
+import com.android.tools.r8.shaking.VerticalClassMerger.VerticallyMergedClasses;
+import com.android.tools.r8.utils.InternalOptions;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.ListIterator;
@@ -50,14 +54,19 @@
 
 public class LensCodeRewriter {
 
-  private final GraphLense graphLense;
   private final AppInfoWithSubtyping appInfo;
+  private final GraphLense graphLense;
+  private final VerticallyMergedClasses verticallyMergedClasses;
+  private final InternalOptions options;
 
   private final Map<DexProto, DexProto> protoFixupCache = new ConcurrentHashMap<>();
 
-  public LensCodeRewriter(GraphLense graphLense, AppInfoWithSubtyping appInfo) {
-    this.graphLense = graphLense;
-    this.appInfo = appInfo;
+  public LensCodeRewriter(
+      AppView<? extends AppInfoWithSubtyping> appView, InternalOptions options) {
+    this.appInfo = appView.appInfo();
+    this.graphLense = appView.graphLense();
+    this.verticallyMergedClasses = appView.verticallyMergedClasses();
+    this.options = options;
   }
 
   private Value makeOutValue(Instruction insn, IRCode code) {
@@ -121,6 +130,9 @@
           if (!invokedHolder.isClassType()) {
             continue;
           }
+          if (invoke.isInvokeDirect()) {
+            checkInvokeDirect(method.method, invoke.asInvokeDirect());
+          }
           GraphLenseLookupResult lenseLookup =
               graphLense.lookupMethod(invokedMethod, method, invoke.getType());
           DexMethod actualTarget = lenseLookup.getMethod();
@@ -246,6 +258,49 @@
     assert code.isConsistentSSA();
   }
 
+  // If the given invoke is on the form "invoke-direct A.<init>, v0, ..." and the definition of
+  // value v0 is "new-instance v0, B", where B is a subtype of A (see the Art800 and B116282409
+  // tests), then fail with a compilation error if A has previously been merged into B.
+  //
+  // The motivation for this is that the vertical class merger cannot easily recognize the above
+  // code pattern, since it runs prior to IR construction. Therefore, we currently allow merging
+  // A and B although this will lead to invalid code, because this code pattern does generally
+  // not occur in practice (it leads to a verification error on the JVM, but not on Art).
+  private void checkInvokeDirect(DexMethod method, InvokeDirect invoke) {
+    if (verticallyMergedClasses == null) {
+      // No need to check the invocation.
+      return;
+    }
+    DexMethod invokedMethod = invoke.getInvokedMethod();
+    if (invokedMethod.name != appInfo.dexItemFactory.constructorMethodName) {
+      // Not a constructor call.
+      return;
+    }
+    if (invoke.arguments().isEmpty()) {
+      // The new instance should always be passed to the constructor call, but continue gracefully.
+      return;
+    }
+    Value receiver = invoke.arguments().get(0);
+    if (!receiver.isPhi() && receiver.definition.isNewInstance()) {
+      NewInstance newInstance = receiver.definition.asNewInstance();
+      if (newInstance.clazz != invokedMethod.holder
+          && verticallyMergedClasses.hasBeenMergedIntoSubtype(invokedMethod.holder)) {
+        // Generated code will not work. Fail with a compilation error.
+        throw options.reporter.fatalError(
+            String.format(
+                "Unable to rewrite `invoke-direct %s.<init>(new %s, ...)` in method `%s` after "
+                    + "type `%s` was merged into `%s`. Please add the following rule to your "
+                    + "Proguard configuration file: `-keep,allowobfuscation class %s`.",
+                invokedMethod.holder.toSourceString(),
+                newInstance.clazz,
+                method.toSourceString(),
+                invokedMethod.holder,
+                verticallyMergedClasses.getTargetFor(invokedMethod.holder),
+                invokedMethod.holder.toSourceString()));
+      }
+    }
+  }
+
   private List<DexValue> rewriteBootstrapArgs(
       List<DexValue> bootstrapArgs, DexEncodedMethod method, MethodHandleUse use) {
     List<DexValue> newBoostrapArgs = null;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
index 045cf52..82e3bff 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodRewriter.java
@@ -423,9 +423,12 @@
     }
     if (converter.enableWholeProgramOptimizations &&
         (!processor.methodsWithMovedCode.isEmpty() || !processor.movedMethods.isEmpty())) {
-      converter.setGraphLense(
-          new InterfaceMethodDesugaringLense(processor.movedMethods,
-              processor.methodsWithMovedCode, converter.getGraphLense(), factory));
+      converter.appView.setGraphLense(
+          new InterfaceMethodDesugaringLense(
+              processor.movedMethods,
+              processor.methodsWithMovedCode,
+              converter.appView.graphLense(),
+              factory));
     }
     return processor.syntheticClasses;
   }
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 c87c849..ffca4d1 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
@@ -174,8 +174,8 @@
       lambdaClass.target.ensureAccessibility();
     }
     if (converter.enableWholeProgramOptimizations && !methodMapping.isEmpty()) {
-      converter.setGraphLense(
-        new LambdaRewriterGraphLense(methodMapping, converter.getGraphLense(), factory));
+      converter.appView.setGraphLense(
+          new LambdaRewriterGraphLense(methodMapping, converter.appView.graphLense(), factory));
     }
   }
 
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 7d6dd7b..e1e534e 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
@@ -92,7 +92,7 @@
   }
 
   public static boolean isSynthesizedCloseResourceMethod(DexMethod method, IRConverter converter) {
-    DexMethod original = converter.getGraphLense().getOriginalMethodSignature(method);
+    DexMethod original = converter.graphLense().getOriginalMethodSignature(method);
     assert original != null;
     // We consider all methods of *any* class with expected name and signature
     // to be synthesized by java 9 compiler for try-with-resources, reasoning:
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 d11946c..3330f08 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
@@ -1865,12 +1865,21 @@
       if (i == 0) {
         // For the first block process all ConstNumber instructions
         // as well as ConstString instructions having just one use.
-        shortenLiveRangesInsideBlock(block, dominatorTreeMemoization, addConstantInBlock,
-            insn -> (insn.isConstNumber() && insn.outValue().numberOfAllUsers() != 0)
-                || (insn.isConstString() && insn.outValue().numberOfAllUsers() == 1));
+        shortenLiveRangesInsideBlock(
+            code,
+            block,
+            dominatorTreeMemoization,
+            addConstantInBlock,
+            insn ->
+                (insn.isConstNumber() && insn.outValue().numberOfAllUsers() != 0)
+                    || (insn.isConstString() && insn.outValue().numberOfAllUsers() == 1));
       } else {
         // For all following blocks only process ConstString with just one use.
-        shortenLiveRangesInsideBlock(block, dominatorTreeMemoization, addConstantInBlock,
+        shortenLiveRangesInsideBlock(
+            code,
+            block,
+            dominatorTreeMemoization,
+            addConstantInBlock,
             insn -> insn.isConstString() && insn.outValue().numberOfAllUsers() == 1);
       }
     }
@@ -1920,14 +1929,20 @@
     assert code.isConsistentSSA();
   }
 
-  private void shortenLiveRangesInsideBlock(BasicBlock block,
+  private void shortenLiveRangesInsideBlock(
+      IRCode code,
+      BasicBlock block,
       Supplier<DominatorTree> dominatorTreeMemoization,
       Map<BasicBlock, List<Instruction>> addConstantInBlock,
-      Predicate<Instruction> selector) {
+      Predicate<ConstInstruction> selector) {
 
     InstructionListIterator it = block.listIterator();
     while (it.hasNext()) {
-      Instruction instruction = it.next();
+      Instruction next = it.next();
+      if (!next.isConstInstruction()) {
+        continue;
+      }
+      ConstInstruction instruction = next.asConstInstruction();
       if (!selector.test(instruction) || instruction.outValue().hasLocalInfo()) {
         continue;
       }
@@ -1973,12 +1988,14 @@
         }
       }
 
-      // Move the const instruction as close to its uses as possible.
-      it.detach();
-
       List<Instruction> csts =
           addConstantInBlock.computeIfAbsent(dominator, k -> new ArrayList<>());
-      csts.add(instruction);
+
+      ConstInstruction copy = instruction.isConstNumber()
+          ? ConstNumber.copyOf(code, instruction.asConstNumber())
+          : ConstString.copyOf(code, instruction.asConstString());
+      instruction.outValue().replaceUsers(copy.outValue());
+      csts.add(copy);
     }
   }
 
@@ -1993,7 +2010,7 @@
             || (hasCatchHandlers && i.instructionTypeCanThrow())
             || (options.canHaveCmpIfFloatBug() && i.isCmp()));
     Instruction next = insertAt.previous();
-    instruction.forceSetPosition(next.getPosition());
+    instruction.setPosition(next.getPosition());
     insertAt.add(instruction);
   }
 
@@ -2136,14 +2153,16 @@
           for (ConstInstruction value : values) {
             stringValues.add(value.outValue());
           }
-          InvokeNewArray invoke = new InvokeNewArray(
-              dexItemFactory.stringArrayType, newArray.outValue(), stringValues);
-          invoke.setPosition(newArray.getPosition());
-          it.detach();
+          Value invokeValue = code.createValue(newArray.outType(), newArray.getLocalInfo());
+          InvokeNewArray invoke =
+              new InvokeNewArray(dexItemFactory.stringArrayType, invokeValue, stringValues);
           for (Value value : newArray.inValues()) {
             value.removeUser(newArray);
           }
-          instructionToInsertForArray.put(newArray.outValue(), invoke);
+          newArray.outValue().replaceUsers(invokeValue);
+          it.removeOrReplaceByDebugLocalRead();
+          instructionToInsertForArray.put(invokeValue, invoke);
+          storesToRemoveForArray.put(invokeValue, size);
         } else {
           // If there is only one element it is typically smaller to generate the array put
           // instruction instead of fill array data.
@@ -2156,12 +2175,12 @@
             continue;
           }
           int arraySize = newArray.size().getConstInstruction().asConstNumber().getIntValue();
-          NewArrayFilledData fillArray = new NewArrayFilledData(
-              newArray.outValue(), elementSize, arraySize, contents);
+          NewArrayFilledData fillArray =
+              new NewArrayFilledData(newArray.outValue(), elementSize, arraySize, contents);
           fillArray.setPosition(newArray.getPosition());
           it.add(fillArray);
+          storesToRemoveForArray.put(newArray.outValue(), size);
         }
-        storesToRemoveForArray.put(newArray.outValue(), size);
       }
       // Second pass: remove all the array put instructions for the array for which we have
       // inserted a fill array data instruction instead.
@@ -2184,9 +2203,9 @@
                   storesToRemoveForArray.put(array, --toRemoveCount);
                   Instruction construction = instructionToInsertForArray.get(array);
                   if (construction != null) {
-                    // Update the position of the array construction to be the position of the
+                    // Set the position of the new array construction to be the position of the
                     // last removed put at which point we are now adding the construction.
-                    construction.forceSetPosition(instruction.getPosition());
+                    construction.setPosition(instruction.getPosition());
                     it.add(construction);
                   }
                 }
@@ -2653,8 +2672,6 @@
         Instruction insnGoto = throwNullInsnIterator.next();
         assert insnGoto.isGoto();
         throwNullInsnIterator.replaceCurrentInstruction(notReachableThrow);
-        // Use position from original invoke to guarantee it has a real position.
-        notReachableThrow.forceSetPosition(insn.getPosition());
       }
     }
     code.removeUnreachableBlocks();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
index e655c0e..fc11b78 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/DefaultInliningOracle.java
@@ -67,10 +67,11 @@
   }
 
   private DexEncodedMethod validateCandidate(InvokeMethod invoke, DexType invocationContext) {
-    DexEncodedMethod candidate = invoke.lookupSingleTarget(inliner.appInfo, invocationContext);
+    DexEncodedMethod candidate =
+        invoke.lookupSingleTarget(inliner.appView.appInfo(), invocationContext);
     if ((candidate == null)
         || (candidate.getCode() == null)
-        || inliner.appInfo.definitionFor(candidate.method.getHolder()).isLibraryClass()) {
+        || inliner.appView.appInfo().definitionFor(candidate.method.getHolder()).isLibraryClass()) {
       if (info != null) {
         info.exclude(invoke, "No inlinee");
       }
@@ -90,12 +91,12 @@
 
   private Reason computeInliningReason(DexEncodedMethod target) {
     if (target.getOptimizationInfo().forceInline()
-        || (inliner.appInfo.hasLiveness()
-            && inliner.appInfo.withLiveness().forceInline.contains(target.method))) {
+        || (inliner.appView.appInfo().hasLiveness()
+            && inliner.appView.withLiveness().appInfo().forceInline.contains(target.method))) {
       return Reason.FORCE;
     }
-    if (inliner.appInfo.hasLiveness()
-        && inliner.appInfo.withLiveness().alwaysInline.contains(target.method)) {
+    if (inliner.appView.appInfo().hasLiveness()
+        && inliner.appView.withLiveness().appInfo().alwaysInline.contains(target.method)) {
       return Reason.ALWAYS;
     }
     if (callSiteInformation.hasSingleCallSite(target)) {
@@ -117,7 +118,7 @@
     if (method.method.getHolder() == targetHolder) {
       return true;
     }
-    DexClass clazz = inliner.appInfo.definitionFor(targetHolder);
+    DexClass clazz = inliner.appView.appInfo().definitionFor(targetHolder);
     assert clazz != null;
     if (target.getOptimizationInfo().triggersClassInitBeforeAnySideEffect()) {
       return true;
@@ -135,7 +136,7 @@
    * methods.
    */
   private boolean classInitializationHasNoSideffects(DexType classToCheck) {
-    DexClass clazz = inliner.appInfo.definitionFor(classToCheck);
+    DexClass clazz = inliner.appView.appInfo().definitionFor(classToCheck);
     if ((clazz == null)
         || clazz.hasNonTrivialClassInitializer()
         || clazz.defaultValuesForStaticFieldsMayTriggerAllocation()) {
@@ -191,7 +192,7 @@
       return false;
     }
 
-    DexClass holder = inliner.appInfo.definitionFor(candidate.method.getHolder());
+    DexClass holder = inliner.appView.appInfo().definitionFor(candidate.method.getHolder());
     if (holder.isInterface()) {
       // Art978_virtual_interfaceTest correctly expects an IncompatibleClassChangeError exception at
       // runtime.
@@ -271,12 +272,12 @@
       if (info != null) {
         info.exclude(invoke, "receiver for candidate can be null");
       }
-      assert !inliner.appInfo.forceInline.contains(candidate.method);
+      assert !inliner.appView.appInfo().forceInline.contains(candidate.method);
       return null;
     }
 
     Reason reason = computeInliningReason(candidate);
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+    if (!candidate.isInliningCandidate(method, reason, inliner.appView.appInfo())) {
       // Abort inlining attempt if the single target is not an inlining candidate.
       if (info != null) {
         info.exclude(invoke, "target is not identified for inlining");
@@ -303,7 +304,7 @@
 
     Reason reason = computeInliningReason(candidate);
     // Determine if this should be inlined no matter how big it is.
-    if (!candidate.isInliningCandidate(method, reason, inliner.appInfo)) {
+    if (!candidate.isInliningCandidate(method, reason, inliner.appView.appInfo())) {
       // Abort inlining attempt if the single target is not an inlining candidate.
       if (info != null) {
         info.exclude(invoke, "target is not identified for inlining");
@@ -388,7 +389,7 @@
       }
       assert IteratorUtils.peekNext(blockIterator) == state;
       // TODO(b/72693244): could be done when Value is created.
-      new TypeAnalysis(inliner.appInfo, code.method).narrowing(nonNullValues);
+      new TypeAnalysis(inliner.appView.appInfo(), code.method).narrowing(nonNullValues);
     }
     // TODO(b/72693244): need a test where refined env in inlinee affects the caller.
   }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
index e23b534..8c8f428 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/EnumOrdinalMapCollector.java
@@ -38,8 +38,8 @@
   private final Map<DexType, Reference2IntMap<DexField>> ordinalsMaps = new IdentityHashMap<>();
 
   public EnumOrdinalMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
-    this.appInfo = appView.getAppInfo();
-    this.graphLense = appView.getGraphLense();
+    this.appInfo = appView.appInfo();
+    this.graphLense = appView.graphLense();
     this.options = options;
   }
 
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 fcb7293..000eac2 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
@@ -6,13 +6,13 @@
 import com.android.tools.r8.graph.AccessFlags;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexClass;
 import com.android.tools.r8.graph.DexEncodedField;
 import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexType;
-import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -47,8 +47,8 @@
   // Threshold found empirically by testing on GMS Core.
   private static final int CONTROL_FLOW_RESOLUTION_BLOCKS_THRESHOLD = 15;
 
+  protected final AppView<? extends AppInfoWithLiveness> appView;
   private final IRConverter converter;
-  protected final AppInfoWithLiveness appInfo;
   final InternalOptions options;
 
   // State for inlining methods which are known to be called twice.
@@ -59,11 +59,14 @@
 
   private final Set<DexMethod> blackList = Sets.newIdentityHashSet();
 
-  public Inliner(IRConverter converter, InternalOptions options) {
+  public Inliner(
+      AppView<? extends AppInfoWithLiveness> appView,
+      IRConverter converter,
+      InternalOptions options) {
+    this.appView = appView;
     this.converter = converter;
-    this.appInfo = converter.appInfo.withLiveness();
     this.options = options;
-    fillInBlackList(appInfo);
+    fillInBlackList(appView.appInfo());
   }
 
   private void fillInBlackList(AppInfoWithLiveness appInfo) {
@@ -73,7 +76,7 @@
 
   public boolean isBlackListed(DexEncodedMethod method) {
     return blackList.contains(method.method)
-        || appInfo.neverInline.contains(method.method)
+        || appView.appInfo().neverInline.contains(method.method)
         || TwrCloseResourceRewriter.isSynthesizedCloseResourceMethod(method.method, converter);
   }
 
@@ -89,7 +92,7 @@
 
   public ConstraintWithTarget computeInliningConstraint(IRCode code, DexEncodedMethod method) {
     ConstraintWithTarget result = ConstraintWithTarget.ALWAYS;
-    InliningConstraints inliningConstraints = new InliningConstraints(appInfo);
+    InliningConstraints inliningConstraints = new InliningConstraints(appView.appInfo());
     InstructionIterator it = code.instructionIterator();
     while (it.hasNext()) {
       Instruction instruction = it.next();
@@ -100,7 +103,7 @@
         break;
       }
       // TODO(b/111080693): we may need to collect all meaningful constraints.
-      result = ConstraintWithTarget.meet(result, state, appInfo);
+      result = ConstraintWithTarget.meet(result, state, appView.appInfo());
     }
     return result;
   }
@@ -110,7 +113,7 @@
       return false;
     }
     // The class needs also to be visible for us to have access.
-    DexClass targetClass = appInfo.definitionFor(target.method.holder);
+    DexClass targetClass = appView.appInfo().definitionFor(target.method.holder);
     return isVisibleWithFlags(target.method.holder, method.method.holder, targetClass.accessFlags);
   }
 
@@ -122,7 +125,7 @@
       return target == context;
     }
     if (flags.isProtected()) {
-      return context.isSubtypeOf(target, appInfo) || target.isSamePackage(context);
+      return context.isSubtypeOf(target, appView.appInfo()) || target.isSamePackage(context);
     }
     // package-private
     return target.isSamePackage(context);
@@ -167,7 +170,7 @@
           .collect(Collectors.toList());
       for (DexEncodedMethod method : methods) {
         DexEncodedMethod mappedMethod =
-            converter.getGraphLense().mapDexEncodedMethod(appInfo, method);
+            converter.graphLense().mapDexEncodedMethod(appView.appInfo(), method);
         converter.processMethod(
             mappedMethod,
             feedback,
@@ -412,16 +415,16 @@
 
     public IRCode buildInliningIR(
         ValueNumberGenerator generator,
-        AppInfoWithSubtyping appInfo,
-        GraphLense graphLense,
+        AppView<? extends AppInfoWithSubtyping> appView,
         InternalOptions options,
         Position callerPosition) {
       // Build the IR for a yet not processed method, and perform minimal IR processing.
-      Origin origin = appInfo.originFor(target.method.holder);
+      Origin origin = appView.appInfo().originFor(target.method.holder);
       IRCode code =
-          target.buildInliningIR(appInfo, graphLense, options, generator, callerPosition, origin);
+          target.buildInliningIR(
+              appView.appInfo(), appView.graphLense(), options, generator, callerPosition, origin);
       if (!target.isProcessed()) {
-        new LensCodeRewriter(graphLense, appInfo).rewrite(code, target);
+        new LensCodeRewriter(appView, options).rewrite(code, target);
       }
       return code;
     }
@@ -477,7 +480,7 @@
       if (instruction.inValues().contains(unInitializedObject)) {
         if (instruction.isInvokeDirect() && !seenSuperInvoke) {
           DexMethod target = instruction.asInvokeDirect().getInvokedMethod();
-          seenSuperInvoke = appInfo.dexItemFactory.isConstructor(target);
+          seenSuperInvoke = appView.dexItemFactory().isConstructor(target);
           boolean callOnConstructorThatCallsConstructorSameClass =
               calleeMethodHolder == target.holder;
           boolean callOnSupertypeOfThisInConstructor =
@@ -504,7 +507,7 @@
           return false;
         }
         DexField field = instruction.asInstancePut().getField();
-        DexEncodedField target = appInfo.lookupInstanceTarget(field.getHolder(), field);
+        DexEncodedField target = appView.appInfo().lookupInstanceTarget(field.getHolder(), field);
         if (target != null && target.accessFlags.isFinal()) {
           return false;
         }
@@ -594,11 +597,10 @@
             }
             assert invokePosition.callerPosition == null
                 || invokePosition.getOutermostCaller().method
-                    == converter.getGraphLense().getOriginalMethodSignature(method.method);
+                    == converter.graphLense().getOriginalMethodSignature(method.method);
 
             IRCode inlinee =
-                result.buildInliningIR(code.valueNumberGenerator,
-                    appInfo, converter.getGraphLense(), options, invokePosition);
+                result.buildInliningIR(code.valueNumberGenerator, appView, options, invokePosition);
             if (inlinee != null) {
               if (block.hasCatchHandlers() && !(oracle instanceof ForcedInliningOracle)) {
                 // Inlining could lead to an explosion of move-exception and resolution moves. As an
@@ -652,7 +654,7 @@
               strategy.markInlined(inlinee);
               if (!strategy.exceededAllowance() || result.ignoreInstructionBudget()) {
                 iterator.inlineInvoke(
-                    appInfo, code, inlinee, blockIterator, blocksToRemove, downcast);
+                    appView.appInfo(), code, inlinee, blockIterator, blocksToRemove, downcast);
                 strategy.updateTypeInformationIfNeeded(inlinee, blockIterator, block);
 
                 // If we inlined the invoke from a bridge method, it is no longer a bridge method.
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
index 052c589..e02222f 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/Outliner.java
@@ -865,7 +865,7 @@
       if (outlineMethods.size() >= options.outline.threshold) {
         for (DexEncodedMethod outlineMethod : outlineMethods) {
           methodsSelectedForOutlining.add(
-              converter.getGraphLense().mapDexEncodedMethod(appInfo, outlineMethod));
+              converter.graphLense().mapDexEncodedMethod(appInfo, outlineMethod));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
index c2fbc9e..fbe2e93 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/SwitchMapCollector.java
@@ -71,8 +71,8 @@
   private final Map<DexField, Int2ReferenceMap<DexField>> switchMaps = new IdentityHashMap<>();
 
   public SwitchMapCollector(AppView<AppInfoWithLiveness> appView, InternalOptions options) {
-    this.appInfo = appView.getAppInfo();
-    this.graphLense = appView.getGraphLense();
+    this.appInfo = appView.appInfo();
+    this.graphLense = appView.graphLense();
     this.options = options;
     switchMapPrefix = appInfo.dexItemFactory.createString("$SwitchMap$");
     intArrayType = appInfo.dexItemFactory.createType("[I");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
index 9baa647..832fc56 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/classinliner/InlineCandidateProcessor.java
@@ -457,9 +457,11 @@
       return null;
     }
 
-    assert init.holder == eligibleClass
-        : "Inlined constructor? [invoke: " + initInvoke +
-        ", expected class: " + eligibleClass + "]";
+    if (init.holder != eligibleClass) {
+      // Calling a constructor on a class that is different from the type of the instance.
+      // Gracefully abort class inlining (see the test B116282409).
+      return null;
+    }
 
     DexEncodedMethod definition = findSingleTarget(init, true);
     if (definition == null || isProcessedConcurrently.test(definition)) {
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 b7c830f..916679d 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
@@ -362,9 +362,9 @@
     }
 
     if (!methodMapping.isEmpty() || fieldMapping.isEmpty()) {
-      classStaticizer.converter.setGraphLense(
+      classStaticizer.converter.appView.setGraphLense(
           new ClassStaticizerGraphLense(
-              classStaticizer.converter.getGraphLense(),
+              classStaticizer.converter.graphLense(),
               classStaticizer.factory,
               fieldMapping,
               methodMapping,
diff --git a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
index f114a7e..25df058 100644
--- a/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
+++ b/src/main/java/com/android/tools/r8/naming/ProguardMapApplier.java
@@ -35,9 +35,9 @@
   private final SeedMapper seedMapper;
 
   public ProguardMapApplier(AppView<AppInfoWithLiveness> appView, SeedMapper seedMapper) {
-    assert appView.getGraphLense().isContextFreeForMethods();
-    this.appInfo = appView.getAppInfo();
-    this.previousLense = appView.getGraphLense();
+    assert appView.graphLense().isContextFreeForMethods();
+    this.appInfo = appView.appInfo();
+    this.previousLense = appView.graphLense();
     this.seedMapper = seedMapper;
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
index 50571f3..480eee0 100644
--- a/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
+++ b/src/main/java/com/android/tools/r8/optimize/ClassAndMemberPublicizer.java
@@ -62,8 +62,8 @@
 
     // Phase 2: Visit classes and promote class/member to public if possible.
     timing.begin("Phase 2: promoteToPublic");
-    DexType.forAllInterfaces(appView.getDexItemFactory(), this::publicizeType);
-    publicizeType(appView.getDexItemFactory().objectType);
+    DexType.forAllInterfaces(appView.dexItemFactory(), this::publicizeType);
+    publicizeType(appView.dexItemFactory().objectType);
     timing.end();
 
     return lenseBuilder.build(appView);
@@ -94,7 +94,7 @@
       return false;
     }
 
-    if (appView.getDexItemFactory().isClassConstructor(encodedMethod.method)) {
+    if (appView.dexItemFactory().isClassConstructor(encodedMethod.method)) {
       return false;
     }
 
@@ -105,7 +105,7 @@
     }
     assert accessFlags.isPrivate();
 
-    if (appView.getDexItemFactory().isConstructor(encodedMethod.method)) {
+    if (appView.dexItemFactory().isConstructor(encodedMethod.method)) {
       accessFlags.unsetPrivate();
       accessFlags.setPublic();
       return false;
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
index c1d7403..0b6a84d 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingAnalysis.java
@@ -30,9 +30,9 @@
   private final MemberRebindingLense.Builder builder;
 
   public MemberRebindingAnalysis(AppView<AppInfoWithLiveness> appView) {
-    assert appView.getGraphLense().isContextFreeForMethods();
-    this.appInfo = appView.getAppInfo();
-    this.lense = appView.getGraphLense();
+    assert appView.graphLense().isContextFreeForMethods();
+    this.appInfo = appView.appInfo();
+    this.lense = appView.graphLense();
     this.builder = MemberRebindingLense.builder(appInfo);
   }
 
diff --git a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
index ad7effd..b11393a 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -27,8 +27,8 @@
         ImmutableMap.of(),
         null,
         null,
-        appView.getGraphLense(),
-        appView.getAppInfo().dexItemFactory);
+        appView.graphLense(),
+        appView.dexItemFactory());
     this.appView = appView;
     this.publicizedMethods = publicizedMethods;
   }
@@ -48,9 +48,9 @@
 
   private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
     GraphLenseLookupResult lookup =
-        appView.getGraphLense().lookupMethod(method, context, Type.VIRTUAL);
+        appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
     DexMethod signatureInCurrentWorld = lookup.getMethod();
-    DexClass clazz = appView.getAppInfo().definitionFor(signatureInCurrentWorld.holder);
+    DexClass clazz = appView.appInfo().definitionFor(signatureInCurrentWorld.holder);
     assert clazz != null;
     DexEncodedMethod actualEncodedTarget = clazz.lookupVirtualMethod(signatureInCurrentWorld);
     assert actualEncodedTarget != null;
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 5e03071..03272a5 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -88,6 +88,24 @@
  */
 public class VerticalClassMerger {
 
+  public static class VerticallyMergedClasses {
+
+    private final Map<DexType, DexType> mergedClasses;
+
+    private VerticallyMergedClasses(Map<DexType, DexType> mergedClasses) {
+      this.mergedClasses = mergedClasses;
+    }
+
+    public DexType getTargetFor(DexType type) {
+      assert mergedClasses.containsKey(type);
+      return mergedClasses.get(type);
+    }
+
+    public boolean hasBeenMergedIntoSubtype(DexType type) {
+      return mergedClasses.containsKey(type);
+    }
+  }
+
   private enum AbortReason {
     ALREADY_MERGED,
     ALWAYS_INLINE,
@@ -200,9 +218,9 @@
       InternalOptions options,
       Timing timing) {
     this.application = application;
-    this.appInfo = appView.getAppInfo();
+    this.appInfo = appView.appInfo();
     this.executorService = executorService;
-    this.graphLense = appView.getGraphLense();
+    this.graphLense = appView.graphLense();
     this.methodPoolCollection = new MethodPoolCollection(application);
     this.options = options;
     this.renamedMembersLense = new VerticalClassMergerGraphLense.Builder();
@@ -213,6 +231,10 @@
     initializeMergeCandidates(classes);
   }
 
+  public VerticallyMergedClasses getMergedClasses() {
+    return new VerticallyMergedClasses(mergedClasses);
+  }
+
   private void initializeMergeCandidates(Iterable<DexProgramClass> classes) {
     for (DexProgramClass clazz : classes) {
       if (isMergeCandidate(clazz, pinnedTypes) && isStillMergeCandidate(clazz)) {
diff --git a/src/main/java/com/android/tools/r8/utils/Reporter.java b/src/main/java/com/android/tools/r8/utils/Reporter.java
index a64e25d..37f57da 100644
--- a/src/main/java/com/android/tools/r8/utils/Reporter.java
+++ b/src/main/java/com/android/tools/r8/utils/Reporter.java
@@ -55,6 +55,14 @@
   /**
    * @throws AbortException always.
    */
+  public RuntimeException fatalError(String message) {
+    fatalError(new StringDiagnostic(message));
+    throw new Unreachable();
+  }
+
+  /**
+   * @throws AbortException always.
+   */
   public RuntimeException fatalError(Diagnostic error) {
     error(error);
     failIfPendingErrors();
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
new file mode 100644
index 0000000..6fe1f16
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/B116282409.java
@@ -0,0 +1,178 @@
+// Copyright (c) 2018, the R8 project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+package com.android.tools.r8.ir.optimize;
+
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.ToolHelper.DexVm;
+import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.jasmin.JasminBuilder;
+import com.android.tools.r8.jasmin.JasminBuilder.ClassBuilder;
+import com.android.tools.r8.jasmin.JasminTestBase;
+import com.android.tools.r8.utils.AbortException;
+import com.android.tools.r8.utils.AndroidApp;
+import com.google.common.collect.ImmutableList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class B116282409 extends JasminTestBase {
+
+  private final Backend backend;
+
+  private final boolean enableVerticalClassMerging;
+
+  @Rule public ExpectedException exception = ExpectedException.none();
+
+  @Parameters(name = "Backend: {0}, vertical class merging: {1}")
+  public static Collection<Object[]> data() {
+    return ImmutableList.of(
+        new Object[] {Backend.CF, false},
+        new Object[] {Backend.CF, true},
+        new Object[] {Backend.DEX, false},
+        new Object[] {Backend.DEX, true});
+  }
+
+  public B116282409(Backend backend, boolean enableVerticalClassMerging) {
+    this.backend = backend;
+    this.enableVerticalClassMerging = enableVerticalClassMerging;
+  }
+
+  @Test
+  public void test() throws Exception {
+    JasminBuilder jasminBuilder = new JasminBuilder();
+
+    // Create a class A with a default constructor that prints "In A.<init>()".
+    ClassBuilder classBuilder = jasminBuilder.addClass("A");
+    classBuilder.addMethod(
+        "public",
+        "<init>",
+        ImmutableList.of(),
+        "V",
+        ".limit stack 2",
+        ".limit locals 1",
+        "aload_0",
+        "invokespecial java/lang/Object/<init>()V",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "ldc \"In A.<init>()\"",
+        "invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V",
+        "return");
+
+    // Create a class B that inherits from A.
+    classBuilder = jasminBuilder.addClass("B", "A");
+
+    // Also add a simple method that the class inliner would inline.
+    classBuilder.addVirtualMethod(
+        "m", "I", ".limit stack 5", ".limit locals 5", "bipush 42", "ireturn");
+
+    // Add a test class that initializes an instance of B using A.<init>.
+    classBuilder = jasminBuilder.addClass("TestClass");
+    classBuilder.addMainMethod(
+        ".limit stack 5",
+        ".limit locals 5",
+        "getstatic java/lang/System/out Ljava/io/PrintStream;",
+        "new B",
+        "dup",
+        "invokespecial A/<init>()V", // NOTE: usually B.<init>
+        "invokevirtual B/m()I",
+        "invokevirtual java/io/PrintStream/println(I)V",
+        "return");
+
+    // Build app.
+    if (enableVerticalClassMerging) {
+      exception.expect(CompilationFailedException.class);
+      exception.expectCause(
+          new CustomExceptionMatcher(
+              "Unable to rewrite `invoke-direct A.<init>(new B, ...)` in method "
+                  + "`void TestClass.main(java.lang.String[])` after type `A` was merged into `B`.",
+              "Please add the following rule to your Proguard configuration file: "
+                  + "`-keep,allowobfuscation class A`."));
+    }
+
+    AndroidApp output =
+        compileWithR8(
+            jasminBuilder.build(),
+            keepMainProguardConfiguration("TestClass"),
+            options -> options.enableVerticalClassMerging = enableVerticalClassMerging,
+            backend);
+    assertFalse(enableVerticalClassMerging);
+
+    // Run app.
+    ProcessResult vmResult = runOnVMRaw(output, "TestClass", backend);
+    if (backend == Backend.CF) {
+      // Verify that the input code does not run with java.
+      ProcessResult javaResult = runOnJavaRaw(jasminBuilder, "TestClass");
+      assertNotEquals(0, javaResult.exitCode);
+      assertThat(javaResult.stderr, containsString("VerifyError"));
+      assertThat(javaResult.stderr, containsString("Call to wrong initialization method"));
+
+      // The same should be true for the generated program.
+      assertNotEquals(0, vmResult.exitCode);
+      assertThat(vmResult.stderr, containsString("VerifyError"));
+      assertThat(vmResult.stderr, containsString("Call to wrong initialization method"));
+    } else {
+      assert backend == Backend.DEX;
+      if (ToolHelper.getDexVm().isOlderThanOrEqual(DexVm.ART_4_4_4_HOST)) {
+        assertNotEquals(0, vmResult.exitCode);
+        assertThat(
+            vmResult.stderr,
+            containsString("VFY: invoke-direct <init> on super only allowed for 'this' in <init>"));
+      } else {
+        assertEquals(0, vmResult.exitCode);
+        assertEquals(
+            String.join(System.lineSeparator(), "In A.<init>()", "42", ""), vmResult.stdout);
+      }
+    }
+  }
+
+  private static class CustomExceptionMatcher extends BaseMatcher<Throwable> {
+
+    private List<String> messages;
+
+    public CustomExceptionMatcher(String... messages) {
+      this.messages = Arrays.asList(messages);
+    }
+
+    @Override
+    public void describeTo(Description description) {
+      description
+          .appendText("a string containing ")
+          .appendText(
+              String.join(
+                  ", ", messages.stream().map(m -> "\"" + m + "\"").collect(Collectors.toList())));
+    }
+
+    @Override
+    public boolean matches(Object o) {
+      if (o instanceof AbortException) {
+        AbortException exception = (AbortException) o;
+        if (exception.getMessage() != null
+            && messages.stream().allMatch(message -> exception.getMessage().contains(message))) {
+          return true;
+        }
+        if (exception.getCause() != null) {
+          return matches(exception.getCause());
+        }
+      }
+      return false;
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
index 3005c51..effd5a1 100644
--- a/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
+++ b/src/test/java/com/android/tools/r8/ir/regalloc/RegisterMoveSchedulerTest.java
@@ -81,11 +81,6 @@
     }
 
     @Override
-    public void detach() {
-      remove();
-    }
-
-    @Override
     public void set(Instruction instruction) {
       it.set(instruction);
     }
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 c1d9234..932c09b 100644
--- a/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
+++ b/src/test/java/com/android/tools/r8/naming/NamingTestBase.java
@@ -75,7 +75,7 @@
 
     ExecutorService executor = ThreadUtils.getExecutorService(1);
 
-    AppInfoWithSubtyping appInfo = appView.getAppInfo();
+    AppInfoWithSubtyping appInfo = appView.appInfo();
     RootSet rootSet = new RootSetBuilder(appInfo, program, configuration.getRules(), options)
         .run(executor);
 
diff --git a/tools/gmscore_data.py b/tools/gmscore_data.py
index 8ff59df..e6d7677 100644
--- a/tools/gmscore_data.py
+++ b/tools/gmscore_data.py
@@ -72,10 +72,10 @@
   },
   'v9': {
     'dex' : {
+      'flags': '--no-desugaring',
       'inputs': [os.path.join(V9_BASE, 'armv7_GmsCore_prod_alldpi_release.apk')],
+      'main-dex-list': os.path.join(V9_BASE, 'main_dex_list.txt'),
       'pgmap': '%s_proguard.map' % V9_PREFIX,
-      'libraries' : [ANDROID_JAR],
-      'min-api' : ANDROID_L_API,
     },
     'deploy' : {
       'pgconf': ['%s_proguard.config' % V9_PREFIX],
@@ -83,18 +83,18 @@
       'min-api' : ANDROID_L_API,
     },
     'proguarded' : {
+      'flags': '--no-desugaring',
       'inputs': ['%s_proguard.jar' % V9_PREFIX],
       'main-dex-list': os.path.join(V9_BASE, 'main_dex_list.txt'),
       'pgmap': '%s_proguard.map' % V9_PREFIX,
-      'min-api' : ANDROID_L_API,
      }
   },
   'v10': {
     'dex' : {
+      'flags': '--no-desugaring',
       'inputs': [os.path.join(V10_BASE, 'armv7_GmsCore_prod_alldpi_release.apk')],
+      'main-dex-list': os.path.join(V10_BASE, 'main_dex_list.txt') ,
       'pgmap': '%s_proguard.map' % V10_PREFIX,
-      'libraries' : [ANDROID_JAR],
-      'min-api' : ANDROID_L_API,
     },
     'deploy' : {
       'inputs': ['%s_deploy.jar' % V10_PREFIX],
@@ -102,10 +102,10 @@
       'min-api' : ANDROID_L_API,
     },
     'proguarded' : {
+      'flags': '--no-desugaring',
       'inputs': ['%s_proguard.jar' % V10_PREFIX],
       'main-dex-list': os.path.join(V10_BASE, 'main_dex_list.txt') ,
       'pgmap': '%s_proguard.map' % V10_PREFIX,
-      'min-api' : ANDROID_L_API,
     }
   },
   'latest': {
@@ -117,10 +117,10 @@
       'min-api' : ANDROID_L_API,
     },
     'proguarded' : {
+      'flags': '--no-desugaring',
       'inputs': ['%s_proguard.jar' % LATEST_PREFIX],
       'main-dex-list': os.path.join(LATEST_BASE, 'main_dex_list.txt') ,
       'pgmap': '%s_proguard.map' % LATEST_PREFIX,
-      'min-api' : ANDROID_L_API,
     }
   },
 }
diff --git a/tools/run_on_app.py b/tools/run_on_app.py
index 6493b2c..83163cb 100755
--- a/tools/run_on_app.py
+++ b/tools/run_on_app.py
@@ -215,6 +215,9 @@
   if 'min-api' in values:
     args.extend(['--min-api', values['min-api']])
 
+  if 'main-dex-list' in values:
+    args.extend(['--main-dex-list', values['main-dex-list']])
+
   if options.compiler == 'r8':
     if 'pgconf' in values and not options.k:
       for pgconf in values['pgconf']:
@@ -234,10 +237,14 @@
       and not os.path.exists(outdir):
     os.makedirs(outdir)
 
+  # Additional flags for the compiler from the configuration file.
+  if 'flags' in values:
+    args.extend(values['flags'].split(' '))
   if options.compiler == 'r8':
     if 'r8-flags' in values:
       args.extend(values['r8-flags'].split(' '))
 
+  # Additional flags for the compiler from the command line.
   if options.compiler_flags:
     args.extend(options.compiler_flags.split(' '))
   if options.r8_flags: