Version 1.4.27

Cherry pick: Disallow changing prototype of methods that override interface method
CL: https://r8-review.googlesource.com/c/r8/+/33226

Cherry pick: Explicitly lookup synthesized class in GraphLense.mapDexEncodedMethod()
CL: https://r8-review.googlesource.com/c/r8/+/33221

Cherry pick: Avoid merging classes if target has static initialization
CL: https://r8-review.googlesource.com/c/r8/+/33141

Cherry pick: Negate broken assertion
CL: https://r8-review.googlesource.com/c/r8/+/33142

Cherry pick: Map context during graph lense lookup
CL: https://r8-review.googlesource.com/c/r8/+/33112

Bug: 123012641, 122275808, 123158783
Change-Id: I2f6e632f189e5dd7a8fc1f43804a272c7ee41c54
diff --git a/src/main/java/com/android/tools/r8/Version.java b/src/main/java/com/android/tools/r8/Version.java
index 128245f..30db140 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.4.26";
+  public static final String LABEL = "1.4.27";
 
   private Version() {
   }
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 c76865e..d9940f3 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -448,6 +448,13 @@
   }
 
   public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+    return classInitializationMayHaveSideEffects(appInfo, Predicates.alwaysFalse());
+  }
+
+  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
+    if (ignore.test(type)) {
+      return false;
+    }
     if (hasNonTrivialClassInitializer()) {
       return true;
     }
@@ -455,11 +462,11 @@
       return true;
     }
     for (DexType iface : interfaces.values) {
-      if (iface.classInitializationMayHaveSideEffects(appInfo)) {
+      if (iface.classInitializationMayHaveSideEffects(appInfo, ignore)) {
         return true;
       }
     }
-    if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo)) {
+    if (superType != null && superType.classInitializationMayHaveSideEffects(appInfo, ignore)) {
       return true;
     }
     return false;
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 1d7555d..d5e2fed 100644
--- a/src/main/java/com/android/tools/r8/graph/DexType.java
+++ b/src/main/java/com/android/tools/r8/graph/DexType.java
@@ -17,6 +17,7 @@
 import java.util.TreeSet;
 import java.util.function.Consumer;
 import java.util.function.Function;
+import java.util.function.Predicate;
 
 public class DexType extends DexReference implements PresortedComparable<DexType> {
 
@@ -113,9 +114,9 @@
     return implementedInterfaces(appInfo).contains(appInfo.dexItemFactory.serializableType);
   }
 
-  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo) {
+  public boolean classInitializationMayHaveSideEffects(AppInfo appInfo, Predicate<DexType> ignore) {
     DexClass clazz = appInfo.definitionFor(this);
-    return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo);
+    return clazz == null || clazz.classInitializationMayHaveSideEffects(appInfo, ignore);
   }
 
   public boolean isUnknown() {
diff --git a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
index 012c4a4..09094aa 100644
--- a/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
+++ b/src/main/java/com/android/tools/r8/graph/DirectMappedDexApplication.java
@@ -73,22 +73,24 @@
   }
 
   public DirectMappedDexApplication rewrittenWithLense(GraphLense graphLense) {
-    assert mappingIsValid(graphLense, programClasses.getAllTypes());
-    assert mappingIsValid(graphLense, libraryClasses.keySet());
     // As a side effect, this will rebuild the program classes and library classes maps.
-    return this.builder().build().asDirect();
+    DirectMappedDexApplication rewrittenApplication = this.builder().build().asDirect();
+    assert rewrittenApplication.mappingIsValid(graphLense, programClasses.getAllTypes());
+    assert rewrittenApplication.mappingIsValid(graphLense, libraryClasses.keySet());
+    return rewrittenApplication;
   }
 
   private boolean mappingIsValid(GraphLense graphLense, Iterable<DexType> types) {
-    // The lense might either map to a different type that is already present in the application
+    // The lens might either map to a different type that is already present in the application
     // (e.g. relinking a type) or it might encode a type that was renamed, in which case the
     // original type will point to a definition that was renamed.
     for (DexType type : types) {
       DexType renamed = graphLense.lookupType(type);
       if (renamed != type) {
-        if (definitionFor(type).type != renamed && definitionFor(renamed) == null) {
-          return false;
+        if (definitionFor(type) == null && definitionFor(renamed) != null) {
+          continue;
         }
+        assert definitionFor(type).type == renamed || definitionFor(renamed) != null;
       }
     }
     return true;
diff --git a/src/main/java/com/android/tools/r8/graph/GraphLense.java b/src/main/java/com/android/tools/r8/graph/GraphLense.java
index 9a373fa..c6a527f 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -356,6 +356,8 @@
     return new Builder();
   }
 
+  public abstract DexType getOriginalType(DexType type);
+
   public abstract DexField getOriginalFieldSignature(DexField field);
 
   public abstract DexMethod getOriginalMethodSignature(DexMethod method);
@@ -365,18 +367,27 @@
   public abstract DexMethod getRenamedMethodSignature(DexMethod originalMethod);
 
   public DexEncodedMethod mapDexEncodedMethod(
-      AppInfo appInfo, DexEncodedMethod originalEncodedMethod) {
+      DexEncodedMethod originalEncodedMethod,
+      AppInfo appInfo,
+      Map<DexType, DexProgramClass> synthesizedClasses) {
     DexMethod newMethod = getRenamedMethodSignature(originalEncodedMethod.method);
-    if (newMethod != originalEncodedMethod.method) {
-      // We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
-      // updated either yet.
-      DexClass newHolder = appInfo.definitionFor(newMethod.holder);
-      assert newHolder != null;
-      DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
-      assert newEncodedMethod != null;
-      return newEncodedMethod;
+    // Note that:
+    // * Even if `newMethod` is the same as `originalEncodedMethod.method`, we still need to look it
+    //   up, since `originalEncodedMethod` may be obsolete.
+    // * We can't directly use AppInfo#definitionFor(DexMethod) since definitions may not be
+    //   updated either yet.
+    DexClass newHolder = appInfo.definitionFor(newMethod.holder);
+
+    // TODO(b/120130831): Need to ensure that all synthesized classes are part of the application.
+    if (newHolder == null) {
+      newHolder = synthesizedClasses.get(newMethod.holder);
     }
-    return originalEncodedMethod;
+
+    assert newHolder != null;
+
+    DexEncodedMethod newEncodedMethod = newHolder.lookupMethod(newMethod);
+    assert newEncodedMethod != null;
+    return newEncodedMethod;
   }
 
   public abstract DexType lookupType(DexType type);
@@ -388,7 +399,7 @@
   }
 
   public abstract GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type);
+      DexMethod method, DexMethod context, Type type);
 
   public abstract RewrittenPrototypeDescription lookupPrototypeChanges(DexMethod method);
 
@@ -547,6 +558,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return type;
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       return field;
     }
@@ -572,8 +588,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
       return new GraphLenseLookupResult(method, type);
     }
 
@@ -594,14 +609,14 @@
   }
 
   /**
-   * GraphLense implementation with a parent lense using a simple mapping for type, method and
-   * field mapping.
+   * GraphLense implementation with a parent lense using a simple mapping for type, method and field
+   * mapping.
    *
-   * Subclasses can override the lookup methods.
+   * <p>Subclasses can override the lookup methods.
    *
-   * For method mapping where invocation type can change just override
-   * {@link #mapInvocationType(DexMethod, DexMethod, DexEncodedMethod, Type)} if
-   * the default name mapping applies, and only invocation type might need to change.
+   * <p>For method mapping where invocation type can change just override {@link
+   * #mapInvocationType(DexMethod, DexMethod, DexMethod, Type)} if the default name mapping applies,
+   * and only invocation type might need to change.
    */
   public static class NestedGraphLense extends GraphLense {
 
@@ -636,6 +651,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      return previousLense.getOriginalType(type);
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       DexField originalField =
           originalFieldSignatures != null
@@ -693,9 +713,12 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
-      GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
+    public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
+      DexMethod previousContext =
+          originalMethodSignatures != null
+              ? originalMethodSignatures.getOrDefault(context, context)
+              : context;
+      GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
       DexMethod newMethod = methodMap.get(previous.getMethod());
       if (newMethod == null) {
         return previous;
@@ -703,7 +726,7 @@
       // TODO(sgjesse): Should we always do interface to virtual mapping? Is it a performance win
       // that only subclasses which are known to need it actually do it?
       return new GraphLenseLookupResult(
-          newMethod, mapInvocationType(newMethod, method, context, previous.getType()));
+          newMethod, mapInvocationType(newMethod, method, previous.getType()));
     }
 
     @Override
@@ -714,22 +737,20 @@
     /**
      * Default invocation type mapping.
      *
-     * This is an identity mapping. If a subclass need invocation type mapping either override
-     * this method or {@link #lookupMethod(DexMethod, DexEncodedMethod, Type)}
+     * <p>This is an identity mapping. If a subclass need invocation type mapping either override
+     * this method or {@link #lookupMethod(DexMethod, DexMethod, Type)}
      */
-    protected Type mapInvocationType(
-        DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+    protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
       return type;
     }
 
     /**
      * Standard mapping between interface and virtual invoke type.
      *
-     * Handle methods moved from interface to class or class to interface.
+     * <p>Handle methods moved from interface to class or class to interface.
      */
-    final protected Type mapVirtualInterfaceInvocationTypes(
-        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod,
-        DexEncodedMethod context, Type type) {
+    protected final Type mapVirtualInterfaceInvocationTypes(
+        AppInfo appInfo, DexMethod newMethod, DexMethod originalMethod, Type type) {
       if (type == Type.VIRTUAL || type == Type.INTERFACE) {
         // Get the invoke type of the actual definition.
         DexClass newTargetClass = appInfo.definitionFor(newMethod.holder);
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 d9040c5..c12ea40 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
@@ -532,7 +532,7 @@
 
     private void processInvoke(Type type, DexMethod method) {
       DexEncodedMethod source = caller.method;
-      GraphLenseLookupResult result = graphLense.lookupMethod(method, source, type);
+      GraphLenseLookupResult result = graphLense.lookupMethod(method, source.method, type);
       method = result.getMethod();
       type = result.getType();
       DexEncodedMethod definition = appInfo.lookup(type, method, source.method.holder);
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 4ecda95..9cb4d46 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
@@ -89,6 +89,7 @@
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
+import java.util.IdentityHashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -359,9 +360,18 @@
       InterfaceMethodRewriter.Flavor includeAllResources,
       ExecutorService executorService)
       throws ExecutionException {
+    desugarInterfaceMethods(builder, includeAllResources, executorService, null);
+  }
+
+  private void desugarInterfaceMethods(
+      Builder<?> builder,
+      InterfaceMethodRewriter.Flavor includeAllResources,
+      ExecutorService executorService,
+      Map<DexType, DexProgramClass> synthesizedClasses)
+      throws ExecutionException {
     if (interfaceMethodRewriter != null) {
       interfaceMethodRewriter.desugarInterfaceMethods(
-          builder, includeAllResources, executorService);
+          builder, includeAllResources, executorService, synthesizedClasses);
     }
   }
 
@@ -583,18 +593,21 @@
     synthesizeLambdaClasses(builder, executorService);
 
     printPhase("Interface method desugaring");
-    desugarInterfaceMethods(builder, IncludeAllResources, executorService);
+    Map<DexType, DexProgramClass> synthesizedClasses = new IdentityHashMap<>();
+    desugarInterfaceMethods(builder, IncludeAllResources, executorService, synthesizedClasses);
+
     printPhase("Twr close resource utility class synthesis");
     synthesizeTwrCloseResourceUtilityClass(builder);
     synthesizeJava8UtilityClass(builder);
     handleSynthesizedClassMapping(builder);
+
     printPhase("Lambda merging finalization");
-    finalizeLambdaMerging(application, feedback, builder, executorService);
+    finalizeLambdaMerging(application, feedback, builder, executorService, synthesizedClasses);
 
     if (outliner != null) {
       printPhase("Outlining");
       timing.begin("IR conversion phase 2");
-      if (outliner.selectMethodsForOutlining()) {
+      if (outliner.selectMethodsForOutlining(synthesizedClasses)) {
         forEachSelectedOutliningMethod(
             executorService,
             (code, method) -> {
@@ -679,11 +692,12 @@
       DexApplication application,
       OptimizationFeedback directFeedback,
       Builder<?> builder,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      Map<DexType, DexProgramClass> synthesizedClasses)
       throws ExecutionException {
     if (lambdaMerger != null) {
       lambdaMerger.applyLambdaClassMapping(
-          application, this, directFeedback, builder, executorService);
+          application, this, directFeedback, builder, executorService, synthesizedClasses);
     }
   }
 
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 62cddc9..7c22bbf 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
@@ -156,7 +156,7 @@
             checkInvokeDirect(method.method, invoke.asInvokeDirect());
           }
           GraphLenseLookupResult lenseLookup =
-              graphLense.lookupMethod(invokedMethod, method, invoke.getType());
+              graphLense.lookupMethod(invokedMethod, method.method, invoke.getType());
           DexMethod actualTarget = lenseLookup.getMethod();
           Invoke.Type actualInvokeType = lenseLookup.getType();
           if (actualInvokeType == Type.VIRTUAL) {
@@ -306,10 +306,11 @@
           }
         } else if (current.isMoveException()) {
           MoveException moveException = current.asMoveException();
-          if (moveException.hasOutValue()) {
-            // Conservatively add the out-value to `newSSAValues` since the catch handler guards
-            // may have been renamed as a result of class merging.
-            newSSAValues.add(moveException.outValue());
+          DexType newExceptionType = graphLense.lookupType(moveException.getExceptionType());
+          if (newExceptionType != moveException.getExceptionType()) {
+            iterator.replaceCurrentInstruction(
+                new MoveException(
+                    makeOutValue(moveException, code, newSSAValues), newExceptionType, options));
           }
         } else if (current.isNewArrayEmpty()) {
           NewArrayEmpty newArrayEmpty = current.asNewArrayEmpty();
@@ -320,7 +321,7 @@
             iterator.replaceCurrentInstruction(newNewArray);
           }
         } else if (current.isNewInstance()) {
-          NewInstance newInstance= current.asNewInstance();
+          NewInstance newInstance = current.asNewInstance();
           DexType newClazz = graphLense.lookupType(newInstance.clazz);
           if (newClazz != newInstance.clazz) {
             NewInstance newNewInstance = new NewInstance(
@@ -456,7 +457,7 @@
       DexMethod invokedMethod = methodHandle.asMethod();
       MethodHandleType oldType = methodHandle.type;
       GraphLenseLookupResult lenseLookup =
-          graphLense.lookupMethod(invokedMethod, context, oldType.toInvokeType());
+          graphLense.lookupMethod(invokedMethod, context.method, oldType.toInvokeType());
       DexMethod rewrittenTarget = lenseLookup.getMethod();
       DexMethod actualTarget;
       MethodHandleType newType;
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
index a8616fb..b708ae9 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceMethodDesugaringLense.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -13,14 +11,11 @@
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.ImmutableMap;
-import java.util.Map;
 
 class InterfaceMethodDesugaringLense extends NestedGraphLense {
-  private final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode;
 
   InterfaceMethodDesugaringLense(
       BiMap<DexMethod, DexMethod> methodMapping,
-      Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode,
       GraphLense previous, DexItemFactory factory) {
     super(
         ImmutableMap.of(),
@@ -30,12 +25,5 @@
         methodMapping.inverse(),
         previous,
         factory);
-    this.methodsWithMovedCode = methodsWithMovedCode;
-  }
-
-  @Override
-  public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
-    return super.mapDexEncodedMethod(
-        appInfo, methodsWithMovedCode.getOrDefault(original, original));
   }
 }
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 7a19fb8..4e91c62 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
@@ -6,6 +6,8 @@
 
 import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unimplemented;
+import com.android.tools.r8.graph.AppInfoWithSubtyping;
+import com.android.tools.r8.graph.AppView;
 import com.android.tools.r8.graph.DexApplication.Builder;
 import com.android.tools.r8.graph.DexCallSite;
 import com.android.tools.r8.graph.DexClass;
@@ -18,6 +20,8 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
+import com.android.tools.r8.graph.GraphLense;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.BasicBlock;
 import com.android.tools.r8.ir.code.IRCode;
 import com.android.tools.r8.ir.code.Instruction;
@@ -373,11 +377,14 @@
   }
 
   /**
-   * Move static and default interface methods to companion classes,
-   * add missing methods to forward to moved default methods implementation.
+   * Move static and default interface methods to companion classes, add missing methods to forward
+   * to moved default methods implementation.
    */
   public void desugarInterfaceMethods(
-      Builder<?> builder, Flavor flavour, ExecutorService executorService)
+      Builder<?> builder,
+      Flavor flavour,
+      ExecutorService executorService,
+      Map<DexType, DexProgramClass> synthesizedClasses)
       throws ExecutionException {
     // Process all classes first. Add missing forwarding methods to
     // replace desugared default interface methods.
@@ -387,12 +394,15 @@
     // methods to companion class, copy default interface methods to companion classes,
     // make original default methods abstract, remove bridge methods, create dispatch
     // classes if needed.
-    Map<DexType, DexProgramClass> synthesizedClasses = processInterfaces(builder, flavour);
-
-    for (Map.Entry<DexType, DexProgramClass> entry : synthesizedClasses.entrySet()) {
+    for (Entry<DexType, DexProgramClass> entry : processInterfaces(builder, flavour).entrySet()) {
       // Don't need to optimize synthesized class since all of its methods
       // are just moved from interfaces and don't need to be re-processed.
-      builder.addSynthesizedClass(entry.getValue(), isInMainDexList(entry.getKey()));
+      DexProgramClass synthesizedClass = entry.getValue();
+      builder.addSynthesizedClass(synthesizedClass, isInMainDexList(entry.getKey()));
+
+      if (synthesizedClasses != null) {
+        synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
+      }
     }
 
     converter.optimizeSynthesizedMethods(synthesizedMethods, executorService);
@@ -414,23 +424,19 @@
   }
 
   private Map<DexType, DexProgramClass> processInterfaces(Builder<?> builder, Flavor flavour) {
+    NestedGraphLense.Builder graphLensBuilder = GraphLense.builder();
     InterfaceProcessor processor = new InterfaceProcessor(this);
     for (DexProgramClass clazz : builder.getProgramClasses()) {
       if (shouldProcess(clazz, flavour, true)) {
-        processor.process(clazz.asProgramClass());
+        processor.process(clazz.asProgramClass(), graphLensBuilder);
       }
     }
     for (Entry<DexLibraryClass, Set<DexProgramClass>> entry : requiredDispatchClasses.entrySet()) {
       synthesizedMethods.addAll(processor.process(entry.getKey(), entry.getValue()));
     }
-    if (converter.enableWholeProgramOptimizations &&
-        (!processor.methodsWithMovedCode.isEmpty() || !processor.movedMethods.isEmpty())) {
-      converter.appView.setGraphLense(
-          new InterfaceMethodDesugaringLense(
-              processor.movedMethods,
-              processor.methodsWithMovedCode,
-              converter.appView.graphLense(),
-              factory));
+    if (converter.enableWholeProgramOptimizations) {
+      AppView<? extends AppInfoWithSubtyping> appView = converter.appView;
+      appView.setGraphLense(graphLensBuilder.build(appView.dexItemFactory(), appView.graphLense()));
     }
     return processor.syntheticClasses;
   }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
index 6ba9b6a..f39e1df 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/InterfaceProcessor.java
@@ -23,14 +23,13 @@
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexTypeList;
+import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.graph.MethodAccessFlags;
 import com.android.tools.r8.graph.ParameterAnnotationsList;
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.android.tools.r8.ir.synthetic.ForwardMethodSourceCode;
 import com.android.tools.r8.ir.synthetic.SynthesizedCode;
 import com.android.tools.r8.origin.SynthesizedOrigin;
-import com.google.common.collect.BiMap;
-import com.google.common.collect.HashBiMap;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -52,14 +51,11 @@
   // All created companion and dispatch classes indexed by interface type.
   final Map<DexType, DexProgramClass> syntheticClasses = new IdentityHashMap<>();
 
-  final BiMap<DexMethod, DexMethod> movedMethods = HashBiMap.create();
-  final Map<DexEncodedMethod, DexEncodedMethod> methodsWithMovedCode = new IdentityHashMap<>();
-
   InterfaceProcessor(InterfaceMethodRewriter rewriter) {
     this.rewriter = rewriter;
   }
 
-  void process(DexProgramClass iface) {
+  void process(DexProgramClass iface, NestedGraphLense.Builder graphLensBuilder) {
     assert iface.isInterface();
 
     // The list of methods to be created in companion class.
@@ -101,8 +97,7 @@
         DexEncodedMethod implMethod = new DexEncodedMethod(
             companionMethod, newFlags, virtual.annotations, virtual.parameterAnnotationsList, code);
         companionMethods.add(implMethod);
-
-        methodsWithMovedCode.put(virtual, implMethod);
+        graphLensBuilder.move(virtual.method, implMethod.method);
       }
 
       // Remove bridge methods.
@@ -136,8 +131,7 @@
         DexMethod companionMethod = rewriter.staticAsMethodOfCompanionClass(oldMethod);
         companionMethods.add(new DexEncodedMethod(companionMethod, newFlags,
             direct.annotations, direct.parameterAnnotationsList, direct.getCode()));
-        movedMethods.put(oldMethod, companionMethod);
-
+        graphLensBuilder.move(oldMethod, companionMethod);
       } else {
         if (originalFlags.isPrivate()) {
           assert !rewriter.factory.isClassConstructor(oldMethod)
@@ -160,8 +154,7 @@
 
           companionMethods.add(new DexEncodedMethod(companionMethod,
               newFlags, direct.annotations, direct.parameterAnnotationsList, code));
-          movedMethods.put(oldMethod, companionMethod);
-
+          graphLensBuilder.move(oldMethod, companionMethod);
         } else {
           // Since there are no interface constructors at this point,
           // this should only be class constructor.
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
index 26f890e..d5ceeb1 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaRewriterGraphLense.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.ir.desugar;
 
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.GraphLense;
@@ -28,13 +27,12 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 
 }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
index 49c740c..ca38f09 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/MethodPoolCollection.java
@@ -203,6 +203,10 @@
       return hasSeenUpwardRecursive(method) || hasSeenDownwardRecursive(method);
     }
 
+    public boolean hasSeenDirectly(Wrapper<DexMethod> method) {
+      return methodPool.contains(method);
+    }
+
     private boolean hasSeenUpwardRecursive(Wrapper<DexMethod> method) {
       return methodPool.contains(method)
           || (superType != null && superType.hasSeenUpwardRecursive(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 7966d3a..87b2537 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
@@ -1228,14 +1228,16 @@
     }
   }
 
-  public boolean selectMethodsForOutlining() {
+  public boolean selectMethodsForOutlining(Map<DexType, DexProgramClass> synthesizedClasses) {
     assert methodsSelectedForOutlining.size() == 0;
     assert outlineSites.size() == 0;
     for (List<DexEncodedMethod> outlineMethods : candidateMethodLists) {
       if (outlineMethods.size() >= options.outline.threshold) {
         for (DexEncodedMethod outlineMethod : outlineMethods) {
           methodsSelectedForOutlining.add(
-              converter.graphLense().mapDexEncodedMethod(appInfo, outlineMethod));
+              converter
+                  .graphLense()
+                  .mapDexEncodedMethod(outlineMethod, appInfo, synthesizedClasses));
         }
       }
     }
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
index 6a43c03..11e0e59 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/UninstantiatedTypeOptimization.java
@@ -138,12 +138,27 @@
         appView,
         appView.appInfo().classes(),
         clazz -> {
+          MethodPool methodPool = methodPoolCollection.get(clazz);
+
           if (clazz.isInterface()) {
+            // Do not allow changing the prototype of methods that override an interface method.
+            // This achieved by faking that there is already a method with the given signature.
+            for (DexEncodedMethod virtualMethod : clazz.virtualMethods()) {
+              RewrittenPrototypeDescription prototypeChanges =
+                  new RewrittenPrototypeDescription(
+                      isAlwaysNull(virtualMethod.method.proto.returnType),
+                      getRemovedArgumentsInfo(virtualMethod, ALLOW_ARGUMENT_REMOVAL));
+              if (!prototypeChanges.isEmpty()) {
+                DexMethod newMethod = getNewMethodSignature(virtualMethod, prototypeChanges);
+                Wrapper<DexMethod> wrapper = equivalence.wrap(newMethod);
+                if (!methodPool.hasSeenDirectly(wrapper)) {
+                  methodPool.seen(wrapper);
+                }
+              }
+            }
             return;
           }
 
-          MethodPool methodPool = methodPoolCollection.get(clazz);
-
           Map<DexEncodedMethod, RewrittenPrototypeDescription> prototypeChangesPerMethod =
               new IdentityHashMap<>();
           for (DexEncodedMethod directMethod : clazz.directMethods()) {
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
index 50d22e1..b988a89 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/lambda/LambdaMerger.java
@@ -194,7 +194,8 @@
       IRConverter converter,
       OptimizationFeedback feedback,
       Builder<?> builder,
-      ExecutorService executorService)
+      ExecutorService executorService,
+      Map<DexType, DexProgramClass> synthesizedClasses)
       throws ExecutionException {
     if (lambdas.isEmpty()) {
       return;
@@ -218,14 +219,17 @@
 
     // Add synthesized lambda group classes to the builder.
     converter.optimizeSynthesizedClasses(lambdaGroupsClasses.values(), executorService);
+
     for (Entry<LambdaGroup, DexProgramClass> entry : lambdaGroupsClasses.entrySet()) {
-      builder.addSynthesizedClass(entry.getValue(),
-          entry.getKey().shouldAddToMainDex(converter.appInfo));
+      DexProgramClass synthesizedClass = entry.getValue();
+      synthesizedClasses.put(synthesizedClass.type, synthesizedClass);
+      builder.addSynthesizedClass(
+          synthesizedClass, entry.getKey().shouldAddToMainDex(converter.appInfo));
     }
 
     // Rewrite lambda class references into lambda group class
     // references inside methods from the processing queue.
-    rewriteLambdaReferences(converter, feedback);
+    rewriteLambdaReferences(converter, synthesizedClasses, feedback);
     this.strategyFactory = null;
   }
 
@@ -300,7 +304,10 @@
     }
   }
 
-  private void rewriteLambdaReferences(IRConverter converter, OptimizationFeedback feedback) {
+  private void rewriteLambdaReferences(
+      IRConverter converter,
+      Map<DexType, DexProgramClass> synthesizedClasses,
+      OptimizationFeedback feedback) {
     List<DexEncodedMethod> methods =
         methodsToReprocess
             .stream()
@@ -308,7 +315,7 @@
             .collect(Collectors.toList());
     for (DexEncodedMethod method : methods) {
       DexEncodedMethod mappedMethod =
-          converter.graphLense().mapDexEncodedMethod(converter.appInfo, method);
+          converter.graphLense().mapDexEncodedMethod(method, converter.appInfo, synthesizedClasses);
       converter.processMethod(mappedMethod, feedback,
           x -> false, CallSiteInformation.empty(), Outliner::noProcessing);
       assert mappedMethod.isProcessed();
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
index 9bdd55e..96bb394 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/staticizer/ClassStaticizerGraphLense.java
@@ -4,8 +4,6 @@
 
 package com.android.tools.r8.ir.optimize.staticizer;
 
-import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -14,17 +12,14 @@
 import com.android.tools.r8.ir.code.Invoke.Type;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableMap;
-import java.util.Map;
 
 class ClassStaticizerGraphLense extends NestedGraphLense {
-  private final Map<DexEncodedMethod, DexEncodedMethod> staticizedMethods;
 
   ClassStaticizerGraphLense(
       GraphLense previous,
       DexItemFactory factory,
       BiMap<DexField, DexField> fieldMapping,
-      BiMap<DexMethod, DexMethod> methodMapping,
-      Map<DexEncodedMethod, DexEncodedMethod> encodedMethodMapping) {
+      BiMap<DexMethod, DexMethod> methodMapping) {
     super(ImmutableMap.of(),
         methodMapping,
         fieldMapping,
@@ -32,22 +27,14 @@
         methodMapping.inverse(),
         previous,
         factory);
-    staticizedMethods = encodedMethodMapping;
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod,
-      DexEncodedMethod context, Type type) {
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
     if (methodMap.get(originalMethod) == newMethod) {
       assert type == Type.VIRTUAL || type == Type.DIRECT;
       return Type.STATIC;
     }
-    return super.mapInvocationType(newMethod, originalMethod, context, type);
-  }
-
-  @Override
-  public DexEncodedMethod mapDexEncodedMethod(AppInfo appInfo, DexEncodedMethod original) {
-    return super.mapDexEncodedMethod(appInfo, staticizedMethods.getOrDefault(original, original));
+    return super.mapInvocationType(newMethod, originalMethod, type);
   }
 }
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 351ec8c..6dac0a0 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
@@ -34,7 +34,6 @@
 import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import java.util.ArrayList;
-import java.util.HashMap;
 import java.util.HashSet;
 import java.util.IdentityHashMap;
 import java.util.Iterator;
@@ -463,7 +462,6 @@
 
   private Set<DexEncodedMethod> staticizeMethodSymbols() {
     BiMap<DexMethod, DexMethod> methodMapping = HashBiMap.create();
-    Map<DexEncodedMethod, DexEncodedMethod> encodedMethodMapping = new HashMap<>();
     BiMap<DexField, DexField> fieldMapping = HashBiMap.create();
 
     Set<DexEncodedMethod> staticizedMethods = Sets.newIdentityHashSet();
@@ -480,7 +478,6 @@
           newDirectMethods.add(staticizedMethod);
           staticizedMethods.add(staticizedMethod);
           methodMapping.put(method.method, staticizedMethod.method);
-          encodedMethodMapping.put(method, staticizedMethod);
         }
       }
       candidateClass.setVirtualMethods(DexEncodedMethod.EMPTY_ARRAY);
@@ -506,8 +503,7 @@
               classStaticizer.converter.graphLense(),
               classStaticizer.factory,
               fieldMapping,
-              methodMapping,
-              encodedMethodMapping));
+              methodMapping));
     }
     return staticizedMethods;
   }
diff --git a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
index 98a0da4..5e19d48 100644
--- a/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/MemberRebindingLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.optimize;
 
 import com.android.tools.r8.graph.AppInfo;
-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.GraphLense;
@@ -47,11 +46,8 @@
     return new Builder(appInfo);
   }
 
-
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 }
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 e51b38d..f6d8fa3 100644
--- a/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
+++ b/src/main/java/com/android/tools/r8/optimize/PublicizerLense.java
@@ -15,6 +15,7 @@
 import java.util.Set;
 
 final class PublicizerLense extends NestedGraphLense {
+
   private final AppView appView;
   private final Set<DexMethod> publicizedMethods;
 
@@ -34,8 +35,7 @@
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
     method = previous.getMethod();
     type = previous.getType();
@@ -46,7 +46,7 @@
     return super.lookupMethod(method, context, type);
   }
 
-  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexEncodedMethod context) {
+  private boolean publicizedMethodIsPresentOnHolder(DexMethod method, DexMethod context) {
     GraphLenseLookupResult lookup =
         appView.graphLense().lookupMethod(method, context, Type.VIRTUAL);
     DexMethod signatureInCurrentWorld = lookup.getMethod();
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 45ca1af..fbe8968 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -6,7 +6,6 @@
 import static com.android.tools.r8.ir.code.Invoke.Type.DIRECT;
 import static com.android.tools.r8.ir.code.Invoke.Type.STATIC;
 
-import com.android.tools.r8.errors.CompilationError;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.graph.AppInfo;
 import com.android.tools.r8.graph.AppInfo.ResolutionResult;
@@ -53,6 +52,7 @@
 import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Maps;
+import com.google.common.collect.Sets;
 import com.google.common.collect.Streams;
 import it.unimi.dsi.fastutil.ints.Int2IntMap;
 import it.unimi.dsi.fastutil.ints.Int2IntOpenHashMap;
@@ -211,13 +211,13 @@
   private final Set<DexProgramClass> mergeCandidates = new LinkedHashSet<>();
 
   // Map from source class to target class.
-  private final Map<DexType, DexType> mergedClasses = new HashMap<>();
+  private final Map<DexType, DexType> mergedClasses = new IdentityHashMap<>();
 
   // Map from target class to the super classes that have been merged into the target class.
-  private final Map<DexType, Set<DexType>> mergedClassesInverse = new HashMap<>();
+  private final Map<DexType, Set<DexType>> mergedClassesInverse = new IdentityHashMap<>();
 
   // Set of types that must not be merged into their subtype.
-  private final Set<DexType> pinnedTypes = new HashSet<>();
+  private final Set<DexType> pinnedTypes = Sets.newIdentityHashSet();
 
   // The resulting graph lense that should be used after class merging.
   private final VerticalClassMergerGraphLense.Builder renamedMembersLense;
@@ -423,7 +423,8 @@
       return false;
     }
     DexClass targetClass = appInfo.definitionFor(clazz.type.getSingleSubtype());
-    if (clazz.hasClassInitializer() && targetClass.hasClassInitializer()) {
+    if ((clazz.hasClassInitializer() && targetClass.hasClassInitializer())
+        || targetClass.classInitializationMayHaveSideEffects(appInfo, type -> type == clazz.type)) {
       // TODO(herhut): Handle class initializers.
       if (Log.ENABLED) {
         AbortReason.STATIC_INITIALIZERS.printLogMessageForClass(clazz);
@@ -685,7 +686,7 @@
     if (Log.ENABLED) {
       Log.debug(getClass(), "Merged %d classes.", mergedClasses.size());
     }
-    return renamedMembersLense.build(graphLense, mergedClasses, synthesizedBridges, appInfo);
+    return renamedMembersLense.build(graphLense, mergedClasses, appInfo);
   }
 
   private boolean methodResolutionMayChange(DexClass source, DexClass target) {
@@ -1432,42 +1433,19 @@
       for (DexProgramClass clazz : appInfo.classes()) {
         clazz.setDirectMethods(substituteTypesIn(clazz.directMethods()));
         clazz.setVirtualMethods(substituteTypesIn(clazz.virtualMethods()));
-        clazz.setVirtualMethods(removeDupes(clazz.virtualMethods()));
         clazz.setStaticFields(substituteTypesIn(clazz.staticFields()));
         clazz.setInstanceFields(substituteTypesIn(clazz.instanceFields()));
       }
+      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
+        synthesizedBridge.updateMethodSignatures(this::fixupMethod);
+      }
       // Record type renamings so check-cast and instance-of checks are also fixed.
       for (DexType type : mergedClasses.keySet()) {
-        DexType fixed = fixupType(type);
-        lense.map(type, fixed);
+        lense.map(type, fixupType(type));
       }
       return lense.build(application.dexItemFactory, graphLense);
     }
 
-    private DexEncodedMethod[] removeDupes(DexEncodedMethod[] methods) {
-      if (methods == null) {
-        return null;
-      }
-      Map<DexMethod, DexEncodedMethod> filtered = new IdentityHashMap<>();
-      for (DexEncodedMethod method : methods) {
-        DexEncodedMethod previous = filtered.put(method.method, method);
-        if (previous != null) {
-          if (!previous.accessFlags.isBridge()) {
-            if (!method.accessFlags.isBridge()) {
-              throw new CompilationError(
-                  "Class merging produced invalid result on: " + previous.toSourceString());
-            } else {
-              filtered.put(previous.method, previous);
-            }
-          }
-        }
-      }
-      if (filtered.size() == methods.length) {
-        return methods;
-      }
-      return filtered.values().toArray(DexEncodedMethod.EMPTY_ARRAY);
-    }
-
     private DexEncodedMethod[] substituteTypesIn(DexEncodedMethod[] methods) {
       if (methods == null) {
         return null;
@@ -1475,12 +1453,9 @@
       for (int i = 0; i < methods.length; i++) {
         DexEncodedMethod encodedMethod = methods[i];
         DexMethod method = encodedMethod.method;
-        DexProto newProto = getUpdatedProto(method.proto);
-        DexType newHolder = fixupType(method.holder);
-        DexMethod newMethod = application.dexItemFactory.createMethod(newHolder, newProto,
-            method.name);
-        if (newMethod != encodedMethod.method) {
-          lense.move(encodedMethod.method, newMethod);
+        DexMethod newMethod = fixupMethod(method);
+        if (newMethod != method) {
+          lense.move(method, newMethod);
           methods[i] = encodedMethod.toTypeSubstitutedMethod(newMethod);
         }
       }
@@ -1505,7 +1480,12 @@
       return fields;
     }
 
-    private DexProto getUpdatedProto(DexProto proto) {
+    private DexMethod fixupMethod(DexMethod method) {
+      return application.dexItemFactory.createMethod(
+          fixupType(method.holder), fixupProto(method.proto), method.name);
+    }
+
+    private DexProto fixupProto(DexProto proto) {
       DexProto result = protoFixupCache.get(proto);
       if (result == null) {
         DexType returnType = fixupType(proto.returnType);
@@ -1522,12 +1502,13 @@
         DexType fixed = fixupType(base);
         if (base == fixed) {
           return type;
-        } else {
-          return type.replaceBaseType(fixed, application.dexItemFactory);
         }
+        return type.replaceBaseType(fixed, application.dexItemFactory);
       }
-      while (mergedClasses.containsKey(type)) {
-        type = mergedClasses.get(type);
+      if (type.isClassType()) {
+        while (mergedClasses.containsKey(type)) {
+          type = mergedClasses.get(type);
+        }
       }
       return type;
     }
@@ -1680,6 +1661,11 @@
     }
 
     @Override
+    public DexType getOriginalType(DexType type) {
+      throw new Unreachable();
+    }
+
+    @Override
     public DexField getOriginalFieldSignature(DexField field) {
       throw new Unreachable();
     }
@@ -1705,8 +1691,7 @@
     }
 
     @Override
-    public GraphLenseLookupResult lookupMethod(
-        DexMethod method, DexEncodedMethod context, Type type) {
+    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);
@@ -1750,7 +1735,7 @@
   public static class IllegalAccessDetector extends UseRegistry {
 
     private boolean foundIllegalAccess = false;
-    private DexEncodedMethod context = null;
+    private DexMethod context = null;
 
     private final AppView<? extends AppInfo> appView;
     private final DexClass source;
@@ -1766,7 +1751,7 @@
     }
 
     public void setContext(DexEncodedMethod context) {
-      this.context = context;
+      this.context = context.method;
     }
 
     private boolean checkFieldReference(DexField field) {
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 20a05ab..6576433 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -5,7 +5,6 @@
 package com.android.tools.r8.shaking;
 
 import com.android.tools.r8.graph.AppInfo;
-import com.android.tools.r8.graph.DexEncodedMethod;
 import com.android.tools.r8.graph.DexField;
 import com.android.tools.r8.graph.DexItemFactory;
 import com.android.tools.r8.graph.DexMethod;
@@ -14,17 +13,14 @@
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.graph.GraphLense.NestedGraphLense;
 import com.android.tools.r8.ir.code.Invoke.Type;
-import com.android.tools.r8.shaking.VerticalClassMerger.SynthesizedBridgeCode;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.HashBiMap;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
 import java.util.HashMap;
 import java.util.IdentityHashMap;
-import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.function.Function;
 
 // This graph lense is instantiated during vertical class merging. The graph lense is context
 // sensitive in the enclosing class of a given invoke *and* the type of the invoke (e.g., invoke-
@@ -55,7 +51,7 @@
 
   private final Map<DexType, Map<DexMethod, GraphLenseLookupResult>>
       contextualVirtualToDirectMethodMaps;
-  private final Set<DexMethod> mergedMethods;
+  private Set<DexMethod> mergedMethods;
   private final Map<DexMethod, DexMethod> originalMethodSignaturesForBridges;
 
   public VerticalClassMergerGraphLense(
@@ -83,19 +79,29 @@
   }
 
   @Override
+  public DexType getOriginalType(DexType type) {
+    return previousLense.getOriginalType(type);
+  }
+
+  @Override
   public DexMethod getOriginalMethodSignature(DexMethod method) {
     return super.getOriginalMethodSignature(
         originalMethodSignaturesForBridges.getOrDefault(method, method));
   }
 
   @Override
-  public GraphLenseLookupResult lookupMethod(
-      DexMethod method, DexEncodedMethod context, Type type) {
+  public GraphLenseLookupResult lookupMethod(DexMethod method, DexMethod context, Type type) {
     assert isContextFreeForMethod(method) || (context != null && type != null);
-    GraphLenseLookupResult previous = previousLense.lookupMethod(method, context, type);
-    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context.method)) {
+    DexMethod previousContext =
+        originalMethodSignaturesForBridges.containsKey(context)
+            ? originalMethodSignaturesForBridges.get(context)
+            : originalMethodSignatures != null
+                ? originalMethodSignatures.getOrDefault(context, context)
+                : context;
+    GraphLenseLookupResult previous = previousLense.lookupMethod(method, previousContext, type);
+    if (previous.getType() == Type.SUPER && !mergedMethods.contains(context)) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
-          contextualVirtualToDirectMethodMaps.get(context.method.holder);
+          contextualVirtualToDirectMethodMaps.get(context.holder);
       if (virtualToDirectMethodMap != null) {
         GraphLenseLookupResult lookup = virtualToDirectMethodMap.get(previous.getMethod());
         if (lookup != null) {
@@ -113,10 +119,8 @@
   }
 
   @Override
-  protected Type mapInvocationType(
-      DexMethod newMethod, DexMethod originalMethod, DexEncodedMethod context, Type type) {
-    return super.mapVirtualInterfaceInvocationTypes(
-        appInfo, newMethod, originalMethod, context, type);
+  protected Type mapInvocationType(DexMethod newMethod, DexMethod originalMethod, Type type) {
+    return super.mapVirtualInterfaceInvocationTypes(appInfo, newMethod, originalMethod, type);
   }
 
   @Override
@@ -170,7 +174,6 @@
     public GraphLense build(
         GraphLense previousLense,
         Map<DexType, DexType> mergedClasses,
-        List<SynthesizedBridgeCode> synthesizedBridges,
         AppInfo appInfo) {
       if (fieldMap.isEmpty()
           && methodMap.isEmpty()
@@ -179,14 +182,6 @@
       }
       Map<DexProto, DexProto> cache = new HashMap<>();
       BiMap<DexField, DexField> originalFieldSignatures = fieldMap.inverse();
-      // Update all synthesized bridges.
-      Function<DexMethod, DexMethod> synthesizedBridgeTransformer =
-          method ->
-              getMethodSignatureAfterClassMerging(
-                  method, mergedClasses, appInfo.dexItemFactory, cache);
-      for (SynthesizedBridgeCode synthesizedBridge : synthesizedBridges) {
-        synthesizedBridge.updateMethodSignatures(synthesizedBridgeTransformer);
-      }
       // Build new graph lense.
       return new VerticalClassMergerGraphLense(
           appInfo,
diff --git a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
index ec12f82..c9be1b8 100644
--- a/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
+++ b/src/main/java/com/android/tools/r8/utils/LineNumberOptimizer.java
@@ -28,6 +28,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexProgramClass;
 import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.GraphLense;
 import com.android.tools.r8.ir.code.Position;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -150,16 +151,17 @@
       // At this point we don't know if we really need to add this class to the builder.
       // It depends on whether any methods/fields are renamed or some methods contain positions.
       // Create a supplier which creates a new, cached ClassNaming.Builder on-demand.
+      DexType originalType = graphLense.getOriginalType(clazz.type);
       DexString renamedClassName = namingLens.lookupDescriptor(clazz.getType());
       Supplier<ClassNaming.Builder> onDemandClassNamingBuilder =
           Suppliers.memoize(
               () ->
                   classNameMapperBuilder.classNamingBuilder(
                       DescriptorUtils.descriptorToJavaType(renamedClassName.toString()),
-                      clazz.toString()));
+                      originalType.toSourceString()));
 
       // If the class is renamed add it to the classNamingBuilder.
-      addClassToClassNaming(clazz, renamedClassName, onDemandClassNamingBuilder);
+      addClassToClassNaming(originalType, renamedClassName, onDemandClassNamingBuilder);
 
       // First transfer renamed fields to classNamingBuilder.
       addFieldsToClassNaming(graphLense, namingLens, clazz, onDemandClassNamingBuilder);
@@ -306,10 +308,12 @@
   }
 
   @SuppressWarnings("ReturnValueIgnored")
-  private static void addClassToClassNaming(DexProgramClass clazz, DexString renamedClassName,
+  private static void addClassToClassNaming(
+      DexType originalType,
+      DexString renamedClassName,
       Supplier<Builder> onDemandClassNamingBuilder) {
     // We do know we need to create a ClassNaming.Builder if the class itself had been renamed.
-    if (!clazz.toString().equals(renamedClassName.toString())) {
+    if (originalType.descriptor != renamedClassName) {
       // Not using return value, it's registered in classNameMapperBuilder
       onDemandClassNamingBuilder.get();
     }
diff --git a/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
new file mode 100644
index 0000000..ce89fe1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/classmerging/StaticInitializerTest.java
@@ -0,0 +1,73 @@
+// 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.classmerging;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.utils.StringUtils;
+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 StaticInitializerTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public StaticInitializerTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("In A.m()", "In B.<clinit>()", "In B.m()");
+
+    if (backend == Backend.CF) {
+      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    }
+
+    testForR8(backend)
+        .addInnerClasses(StaticInitializerTest.class)
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .run(TestClass.class)
+        .assertSuccessWithOutput(expectedOutput);
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      A.m();
+      B.m();
+    }
+  }
+
+  // Cannot be merged into B because that would change the semantics due to <clinit>.
+  static class A {
+
+    @NeverInline
+    public static void m() {
+      System.out.println("In A.m()");
+    }
+  }
+
+  static class B extends A {
+
+    static {
+      System.out.println("In B.<clinit>()");
+    }
+
+    @NeverInline
+    public static void m() {
+      System.out.println("In B.m()");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
index 2284fa5..c4151d1 100644
--- a/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/VerticalClassMergerTest.java
@@ -86,16 +86,13 @@
 
   private void runR8(Path proguardConfig, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException, CompilationFailedException {
-    ToolHelper.runR8(
-        R8Command.builder()
-            .setOutput(Paths.get(temp.getRoot().getCanonicalPath()), OutputMode.DexIndexed)
+    inspector =
+        testForR8(Backend.DEX)
             .addProgramFiles(EXAMPLE_JAR)
-            .addProguardConfigurationFiles(proguardConfig)
-            .setDisableMinification(true)
-            .build(),
-        optionsConsumer);
-    inspector = new CodeInspector(
-        Paths.get(temp.getRoot().getCanonicalPath()).resolve("classes.dex"));
+            .addKeepRuleFiles(proguardConfig)
+            .addOptionsModification(optionsConsumer)
+            .compile()
+            .inspector();
   }
 
   private CodeInspector inspector;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
new file mode 100644
index 0000000..b609c1e
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/uninstantiatedtypes/InterfaceMethodTest.java
@@ -0,0 +1,107 @@
+// 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.uninstantiatedtypes;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertThat;
+
+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.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+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 InterfaceMethodTest extends TestBase {
+
+  private final Backend backend;
+
+  @Parameters(name = "Backend: {0}")
+  public static Backend[] data() {
+    return Backend.values();
+  }
+
+  public InterfaceMethodTest(Backend backend) {
+    this.backend = backend;
+  }
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput = StringUtils.lines("In A.m()", "In B.m()");
+
+    if (backend == Backend.CF) {
+      testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+    }
+
+    CodeInspector inspector =
+        testForR8(backend)
+            .addInnerClasses(InterfaceMethodTest.class)
+            .addKeepMainRule(TestClass.class)
+            .enableInliningAnnotations()
+            .enableMergeAnnotations()
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject interfaceSubject = inspector.clazz(I.class);
+    assertThat(interfaceSubject, isPresent());
+    assertThat(interfaceSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+
+    for (Class<?> clazz : ImmutableList.of(A.class, B.class)) {
+      ClassSubject classSubject = inspector.clazz(clazz);
+      assertThat(classSubject, isPresent());
+      assertThat(classSubject.method(Uninstantiated.class.getTypeName(), "m"), isPresent());
+    }
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      test(new A());
+      test(new B());
+    }
+
+    @NeverInline
+    private static void test(I obj) {
+      obj.m();
+    }
+  }
+
+  @NeverMerge
+  interface I {
+
+    Uninstantiated m();
+  }
+
+  static class A implements I {
+
+    @NeverInline
+    @Override
+    public Uninstantiated m() {
+      System.out.println("In A.m()");
+      return null;
+    }
+  }
+
+  // The purpose of this class is merely to avoid that the invoke-interface instruction in
+  // TestClass.test() gets devirtualized to an invoke-virtual instruction. Otherwise the method
+  // I.m() would not be present in the output.
+  static class B implements I {
+
+    @Override
+    public Uninstantiated m() {
+      System.out.println("In B.m()");
+      return null;
+    }
+  }
+
+  static class Uninstantiated {}
+}
diff --git a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
index e9bf581..378cf0e 100644
--- a/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
+++ b/src/test/java/com/android/tools/r8/proguard/printmapping/PrintMappingTest.java
@@ -27,14 +27,11 @@
   private void test(Path mapping) throws Exception {
     testForR8(Backend.DEX)
         .addInnerClasses(PrintMappingTest.class)
-        .addKeepMainRule(TestClass.class)
+        .addKeepRules("-keep,allowobfuscation class " + TestClass.class.getTypeName())
         .addKeepRules("-printmapping " + mapping)
         .compile();
     assertTrue(mapping.toFile().exists());
   }
 
-  static class TestClass {
-
-    public static void main(String[] args) {}
-  }
+  static class TestClass {}
 }
diff --git a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
index 1c28054..5b7a11e 100644
--- a/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
+++ b/src/test/java/com/android/tools/r8/resource/KeepDirectoriesTest.java
@@ -15,6 +15,7 @@
 import com.android.tools.r8.DataResourceConsumer;
 import com.android.tools.r8.DiagnosticsHandler;
 import com.android.tools.r8.R8Command;
+import com.android.tools.r8.StringResource;
 import com.android.tools.r8.ToolHelper;
 import com.android.tools.r8.errors.Unreachable;
 import com.android.tools.r8.naming.ClassNameMapper;
@@ -65,10 +66,15 @@
 
   // Return the package name in the app for this package.
   private String pathForThisPackage(AndroidApp app) throws Exception {
-    ClassNameMapper mapper =
-        ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
-    String x = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
-    return x.substring(0, x.lastIndexOf('.')).replace('.', '/');
+    String name;
+    if (app.getProguardMapOutputData() != null) {
+      ClassNameMapper mapper =
+          ClassNameMapper.mapperFromString(app.getProguardMapOutputData().getString());
+      name = mapper.getObfuscatedToOriginalMapping().inverse.get(Main.class.getCanonicalName());
+    } else {
+      name = Main.class.getTypeName();
+    }
+    return name.substring(0, name.lastIndexOf('.')).replace('.', '/');
   }
 
   private void checkResourceNames(Set<String> resourceNames, String expectedPackageName) {
diff --git a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
index b302615..0b246a5 100644
--- a/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
+++ b/src/test/java/com/android/tools/r8/shaking/TreeShakingTest.java
@@ -260,9 +260,8 @@
     }
 
     if (inspection != null) {
-      CodeInspector inspector = new CodeInspector(out,
-          minify.isMinify() ? proguardMap.toString()
-              : null);
+      CodeInspector inspector =
+          new CodeInspector(out, minify.isMinify() ? proguardMap.toString() : null);
       inspection.accept(inspector);
     }
   }
diff --git a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
index f65c1d4..3833645 100644
--- a/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
+++ b/src/test/java/com/android/tools/r8/shaking/examples/TreeShaking11Test.java
@@ -3,7 +3,6 @@
 // BSD-style license that can be found in the LICENSE file.
 package com.android.tools.r8.shaking.examples;
 
-import com.android.tools.r8.TestBase.MinifyMode;
 import com.android.tools.r8.shaking.TreeShakingTest;
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.google.common.collect.ImmutableList;
diff --git a/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
new file mode 100644
index 0000000..07d6d22
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/ifrule/interfacemethoddesugaring/IfRuleWithInterfaceMethodDesugaringTest.java
@@ -0,0 +1,107 @@
+// 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.shaking.ifrule.interfacemethoddesugaring;
+
+import static com.android.tools.r8.ir.desugar.InterfaceMethodRewriter.COMPANION_CLASS_NAME_SUFFIX;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPublic;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isStatic;
+import static org.hamcrest.CoreMatchers.allOf;
+import static org.hamcrest.CoreMatchers.not;
+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.AndroidApiLevel;
+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.MethodSubject;
+import org.junit.Test;
+
+public class IfRuleWithInterfaceMethodDesugaringTest extends TestBase {
+
+  @Test
+  public void test() throws Exception {
+    String expectedOutput =
+        StringUtils.lines("In Interface.staticMethod()", "In Interface.virtualMethod()");
+
+    testForJvm().addTestClasspath().run(TestClass.class).assertSuccessWithOutput(expectedOutput);
+
+    CodeInspector inspector =
+        testForR8(Backend.DEX)
+            .addInnerClasses(IfRuleWithInterfaceMethodDesugaringTest.class)
+            .addKeepMainRule(TestClass.class)
+            .addKeepRules(
+                "-if class " + Interface.class.getTypeName() + " {",
+                "  !public static void staticMethod();",
+                "}",
+                "-keep class " + Unused1.class.getTypeName(),
+                "-if class " + Interface.class.getTypeName() + " {",
+                "  !public !static void virtualMethod();",
+                "}",
+                "-keep class " + Unused2.class.getTypeName())
+            .enableInliningAnnotations()
+            .enableClassInliningAnnotations()
+            .enableMergeAnnotations()
+            .setMinApi(AndroidApiLevel.M)
+            .run(TestClass.class)
+            .assertSuccessWithOutput(expectedOutput)
+            .inspector();
+
+    ClassSubject classSubject =
+        inspector.clazz(Interface.class.getTypeName() + COMPANION_CLASS_NAME_SUFFIX);
+    assertThat(classSubject, isPresent());
+    assertEquals(2, classSubject.allMethods().size());
+
+    MethodSubject staticMethodSubject = classSubject.uniqueMethodWithName("staticMethod");
+    assertThat(staticMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+    // TODO(b/120764902): MethodSubject.getOriginalName() not working in presence of desugaring.
+    MethodSubject virtualMethodSubject =
+        classSubject.allMethods().stream()
+            .filter(subject -> subject != staticMethodSubject)
+            .findFirst()
+            .get();
+    assertThat(virtualMethodSubject, allOf(isPresent(), isPublic(), isStatic()));
+
+    // TODO(b/122875545): The Unused class should be present due to the -if rule.
+    assertThat(inspector.clazz(Unused1.class), not(isPresent()));
+    assertThat(inspector.clazz(Unused2.class), not(isPresent()));
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      Interface.staticMethod();
+      new InterfaceImpl().virtualMethod();
+    }
+  }
+
+  @NeverClassInline
+  @NeverMerge
+  interface Interface {
+
+    @NeverInline
+    static void staticMethod() {
+      System.out.println("In Interface.staticMethod()");
+    }
+
+    @NeverInline
+    default void virtualMethod() {
+      System.out.println("In Interface.virtualMethod()");
+    }
+  }
+
+  @NeverClassInline
+  static class InterfaceImpl implements Interface {}
+
+  static class Unused1 {}
+
+  static class Unused2 {}
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
index 1f520ff..d870316 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/CodeInspector.java
@@ -45,6 +45,7 @@
 import com.google.common.collect.ImmutableList;
 import java.io.IOException;
 import java.lang.reflect.Method;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import java.util.Collections;
@@ -82,13 +83,14 @@
   public CodeInspector(
       List<Path> files, String mappingFile, Consumer<InternalOptions> optionsConsumer)
       throws IOException, ExecutionException {
-    if (mappingFile != null) {
-      this.mapping = ClassNameMapper.mapperFromFile(Paths.get(mappingFile));
-      BiMapContainer<String, String> nameMapping = this.mapping.getObfuscatedToOriginalMapping();
+    Path mappingPath = mappingFile != null ? Paths.get(mappingFile) : null;
+    if (mappingPath != null && Files.exists(mappingPath)) {
+      mapping = ClassNameMapper.mapperFromFile(mappingPath);
+      BiMapContainer<String, String> nameMapping = mapping.getObfuscatedToOriginalMapping();
       obfuscatedToOriginalMapping = nameMapping.original;
       originalToObfuscatedMapping = nameMapping.inverse;
     } else {
-      this.mapping = null;
+      mapping = null;
       originalToObfuscatedMapping = null;
       obfuscatedToOriginalMapping = null;
     }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
index 1167d8f..ed4a67f 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundClassSubject.java
@@ -97,7 +97,7 @@
   public MethodSubject uniqueMethodWithName(String name) {
     MethodSubject methodSubject = null;
     for (FoundMethodSubject candidate : allMethods()) {
-      if (candidate.getOriginalName().equals(name)) {
+      if (candidate.getOriginalName(false).equals(name)) {
         assert methodSubject == null;
         methodSubject = candidate;
       }
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
index a9abba1..d407e80 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/Matchers.java
@@ -124,6 +124,28 @@
     };
   }
 
+  public static Matcher<MemberSubject> isStatic() {
+    return new TypeSafeMatcher<MemberSubject>() {
+      @Override
+      public boolean matchesSafely(final MemberSubject subject) {
+        return subject.isPresent() && subject.isStatic();
+      }
+
+      @Override
+      public void describeTo(final Description description) {
+        description.appendText(" present");
+      }
+
+      @Override
+      public void describeMismatchSafely(final MemberSubject subject, Description description) {
+        description
+            .appendText(type(subject) + " ")
+            .appendValue(name(subject))
+            .appendText(" was not");
+      }
+    };
+  }
+
   public static Matcher<ClassSubject> hasDefaultConstructor() {
     return new TypeSafeMatcher<ClassSubject>() {
       @Override
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
index a95b6e6..bc933b1 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/MemberSubject.java
@@ -25,8 +25,21 @@
   public abstract Signature getFinalSignature();
 
   public String getOriginalName() {
+    return getOriginalName(true);
+  }
+  public String getOriginalName(boolean qualified) {
     Signature originalSignature = getOriginalSignature();
-    return originalSignature == null ? null : originalSignature.name;
+    if (originalSignature != null) {
+      String name = originalSignature.name;
+      if (!qualified) {
+        int index = name.lastIndexOf(".");
+        if (index >= 0) {
+          return name.substring(index + 1);
+        }
+      }
+      return name;
+    }
+    return null;
   }
 
   public String getFinalName() {