Merge "Update assert in vertical class merger"
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 1c74f79..247a5fe 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -424,14 +424,30 @@
     return this == getIdentityLense();
   }
 
-  public <T extends DexDefinition> boolean assertDefinitionNotModified(Iterable<T> items) {
-    for (DexDefinition item : items) {
-      DexReference dexReference = item.toReference();
-      DexReference lookupedReference = lookupReference(dexReference);
+  public <T extends DexDefinition> boolean assertDefinitionsNotModified(Iterable<T> definitions) {
+    for (DexDefinition definition : definitions) {
+      DexReference reference = definition.toReference();
       // We allow changes to bridge methods as these get retargeted even if they are kept.
       boolean isBridge =
-          item.isDexEncodedMethod() && item.asDexEncodedMethod().accessFlags.isBridge();
-      assert isBridge || dexReference == lookupedReference;
+          definition.isDexEncodedMethod() && definition.asDexEncodedMethod().accessFlags.isBridge();
+      assert isBridge || lookupReference(reference) == reference;
+    }
+    return true;
+  }
+
+  public <T extends DexReference> boolean assertReferencesNotModified(Iterable<T> references) {
+    for (DexReference reference : references) {
+      if (reference.isDexField()) {
+        DexField field = reference.asDexField();
+        assert getRenamedFieldSignature(field) == field;
+      } else if (reference.isDexMethod()) {
+        DexMethod method = reference.asDexMethod();
+        assert getRenamedMethodSignature(method) == method;
+      } else {
+        assert reference.isDexType();
+        DexType type = reference.asDexType();
+        assert lookupType(type) == type;
+      }
     }
     return true;
   }
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 2ca33b0..6a90323 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -2111,26 +2111,32 @@
       this.callSites = previous.callSites;
       this.brokenSuperInvokes = lense.rewriteMethodsConservatively(previous.brokenSuperInvokes);
       this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
-      assert lense.assertDefinitionNotModified(previous.noSideEffects.keySet());
+      assert lense.assertDefinitionsNotModified(previous.noSideEffects.keySet());
       this.noSideEffects = previous.noSideEffects;
-      assert lense.assertDefinitionNotModified(previous.assumedValues.keySet());
+      assert lense.assertDefinitionsNotModified(previous.assumedValues.keySet());
       this.assumedValues = previous.assumedValues;
-      assert lense.assertDefinitionNotModified(
-          previous.alwaysInline.stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.alwaysInline.stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.alwaysInline = previous.alwaysInline;
       this.forceInline = lense.rewriteMethodsWithRenamedSignature(previous.forceInline);
       this.neverInline = lense.rewriteMethodsWithRenamedSignature(previous.neverInline);
-      assert lense.assertDefinitionNotModified(
-          previous.neverMerge.stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.neverMerge.stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.neverClassInline = rewriteItems(previous.neverClassInline, lense::lookupType);
       this.neverMerge = previous.neverMerge;
       this.identifierNameStrings =
           lense.rewriteReferencesConservatively(previous.identifierNameStrings);
       // Switchmap classes should never be affected by renaming.
-      assert lense.assertDefinitionNotModified(
-          previous.switchMaps.keySet().stream().map(this::definitionFor).filter(Objects::nonNull)
+      assert lense.assertDefinitionsNotModified(
+          previous.switchMaps.keySet().stream()
+              .map(this::definitionFor)
+              .filter(Objects::nonNull)
               .collect(Collectors.toList()));
       this.switchMaps = previous.switchMaps;
       this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
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 0d69178..45ca1af 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -608,28 +608,67 @@
     timing.begin("fixup");
     GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
     timing.end();
-    assert result.assertDefinitionNotModified(
-        appInfo
-            .alwaysInline
-            .stream()
+    assert result.assertDefinitionsNotModified(
+        appInfo.alwaysInline.stream()
             .map(appInfo::definitionFor)
             .filter(Objects::nonNull)
             .collect(Collectors.toList()));
-    assert result.assertDefinitionNotModified(appInfo.noSideEffects.keySet());
-    // TODO(christofferqa): Enable this assert.
-    // assert result.assertNotModified(appInfo.pinnedItems);
-    assert verifyGraphLense(graphLense);
+    assert verifyGraphLense(result);
     return result;
   }
 
   private boolean verifyGraphLense(GraphLense graphLense) {
+    assert graphLense.assertDefinitionsNotModified(appInfo.noSideEffects.keySet());
+
+    // Note that the method assertReferencesNotModified() relies on getRenamedFieldSignature() and
+    // getRenamedMethodSignature() instead of lookupField() and lookupMethod(). This is important
+    // for this check to succeed, since it is not guaranteed that calling lookupMethod() with a
+    // pinned method will return the method itself.
+    //
+    // Consider the following example.
+    //
+    //   class A {
+    //     public void method() {}
+    //   }
+    //   class B extends A {
+    //     @Override
+    //     public void method() {}
+    //   }
+    //   class C extends B {
+    //     @Override
+    //     public void method() {}
+    //   }
+    //
+    // If A.method() is pinned, then A cannot be merged into B, but B can still be merged into C.
+    // Now, if there is an invoke-super instruction in C that hits B.method(), then this needs to
+    // be rewritten into an invoke-direct instruction. In particular, there could be an instruction
+    // `invoke-super A.method` in C. This would hit B.method(). Therefore, the graph lens records
+    // that `invoke-super A.method` instructions, which are in one of the methods from C, needs to
+    // be rewritten to `invoke-direct C.method$B`. This is valid even though A.method() is actually
+    // pinned, because this rewriting does not affect A.method() in any way.
+    assert graphLense.assertReferencesNotModified(appInfo.pinnedItems);
+
     for (DexProgramClass clazz : appInfo.classes()) {
       for (DexEncodedMethod encodedMethod : clazz.methods()) {
         DexMethod method = encodedMethod.method;
         DexMethod originalMethod = graphLense.getOriginalMethodSignature(method);
+        DexMethod renamedMethod = graphLense.getRenamedMethodSignature(originalMethod);
 
-        // Must be able to map back.
-        assert method == graphLense.getRenamedMethodSignature(originalMethod);
+        // Must be able to map back and forth.
+        if (encodedMethod.hasCode() && encodedMethod.getCode() instanceof SynthesizedBridgeCode) {
+          // For virtual methods, the vertical class merger creates two methods in the sub class
+          // in order to deal with invoke-super instructions (one that is private and one that is
+          // virtual). Therefore, it is not possible to go back and forth. Instead, we check that
+          // the two methods map back to the same original method, and that the original method
+          // can be mapped to the implementation method.
+          DexMethod implementationMethod =
+              ((SynthesizedBridgeCode) encodedMethod.getCode()).invocationTarget;
+          DexMethod originalImplementationMethod = graphLense.getOriginalMethodSignature(method);
+          assert originalMethod == originalImplementationMethod;
+          assert implementationMethod == renamedMethod;
+        } else {
+          assert method == renamedMethod;
+        }
 
         // Verify that all types are up-to-date. After vertical class merging, there should be no
         // more references to types that have been merged into another type.
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 6b66465..20a05ab 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMergerGraphLense.java
@@ -222,16 +222,32 @@
         Map<DexType, DexType> mergedClasses,
         DexItemFactory dexItemFactory,
         Map<DexProto, DexProto> cache) {
+      assert !signature.holder.isArrayType();
       DexType newHolder = mergedClasses.getOrDefault(signature.holder, signature.holder);
       DexProto newProto =
           dexItemFactory.applyClassMappingToProto(
-              signature.proto, type -> mergedClasses.getOrDefault(type, type), cache);
+              signature.proto,
+              type -> getTypeAfterClassMerging(type, mergedClasses, dexItemFactory),
+              cache);
       if (signature.holder.equals(newHolder) && signature.proto.equals(newProto)) {
         return signature;
       }
       return dexItemFactory.createMethod(newHolder, newProto, signature.name);
     }
 
+    private static DexType getTypeAfterClassMerging(
+        DexType type, Map<DexType, DexType> mergedClasses, DexItemFactory dexItemFactory) {
+      if (type.isArrayType()) {
+        DexType baseType = type.toBaseType(dexItemFactory);
+        DexType newBaseType = mergedClasses.getOrDefault(baseType, baseType);
+        if (newBaseType != baseType) {
+          return type.replaceBaseType(newBaseType, dexItemFactory);
+        }
+        return type;
+      }
+      return mergedClasses.getOrDefault(type, type);
+    }
+
     public boolean hasMappingForSignatureInContext(DexType context, DexMethod signature) {
       Map<DexMethod, GraphLenseLookupResult> virtualToDirectMethodMap =
           contextualVirtualToDirectMethodMaps.get(context);
diff --git a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
index c7a8ef2..b8c4b47 100644
--- a/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/KotlinLambdaMergingTest.java
@@ -22,7 +22,6 @@
 import java.util.List;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
-import org.junit.Ignore;
 import org.junit.Test;
 
 public class KotlinLambdaMergingTest extends AbstractR8KotlinTestBase {
@@ -455,7 +454,6 @@
     });
   }
 
-  @Ignore("b/121107286: assertion failure in VerticalClassMerger#verifyGraphLense")
   @Test
   public void testSingleton() throws Exception {
     final String mainClassName = "lambdas_singleton.MainKt";