Merge "Set class file version in VerticalClassMerger"
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 096de5a..2c12a90 100644
--- a/src/main/java/com/android/tools/r8/graph/GraphLense.java
+++ b/src/main/java/com/android/tools/r8/graph/GraphLense.java
@@ -99,6 +99,25 @@
     return this instanceof IdentityGraphLense;
   }
 
+  public boolean assertNotModified(Iterable<DexItem> items) {
+    for (DexItem item : items) {
+      if (item instanceof DexClass) {
+        DexType type = ((DexClass) item).type;
+        assert lookupType(type) == type;
+      } else if (item instanceof DexEncodedMethod) {
+        DexEncodedMethod method = (DexEncodedMethod) item;
+        // We allow changes to bridge methods as these get retargeted even if they are kept.
+        assert method.accessFlags.isBridge() || lookupMethod(method.method) == method.method;
+      } else if (item instanceof DexEncodedField) {
+        DexField field = ((DexEncodedField) item).field;
+        assert lookupField(field) == field;
+      } else {
+        assert false;
+      }
+    }
+    return true;
+  }
+
   private static class IdentityGraphLense extends GraphLense {
 
     @Override
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 0e4be8b..4c0be09 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
@@ -11,6 +11,7 @@
 import com.android.tools.r8.graph.DexMethod;
 import com.android.tools.r8.graph.DexMethodHandle;
 import com.android.tools.r8.graph.DexMethodHandle.MethodHandleType;
+import com.android.tools.r8.graph.DexProto;
 import com.android.tools.r8.graph.DexType;
 import com.android.tools.r8.graph.DexValue;
 import com.android.tools.r8.graph.DexValue.DexValueMethodHandle;
@@ -73,6 +74,13 @@
         if (current.isInvokeCustom()) {
           InvokeCustom invokeCustom = current.asInvokeCustom();
           DexCallSite callSite = invokeCustom.getCallSite();
+          DexType[] newParameters = new DexType[callSite.methodProto.parameters.size()];
+          for (int i = 0; i < callSite.methodProto.parameters.size(); i++) {
+            newParameters[i] = graphLense.lookupType(callSite.methodProto.parameters.values[i]);
+          }
+          DexProto newMethodProto =
+              appInfo.dexItemFactory.createProto(
+                  graphLense.lookupType(callSite.methodProto.returnType), newParameters);
           DexMethodHandle newBootstrapMethod = rewriteDexMethodHandle(method,
               callSite.bootstrapMethod);
           List<DexValue> newArgs = callSite.bootstrapArgs.stream().map(
@@ -85,10 +93,12 @@
               })
               .collect(Collectors.toList());
 
-          if (newBootstrapMethod != callSite.bootstrapMethod
+          if (!newMethodProto.equals(callSite.methodProto)
+              || newBootstrapMethod != callSite.bootstrapMethod
               || !newArgs.equals(callSite.bootstrapArgs)) {
-            DexCallSite newCallSite = appInfo.dexItemFactory.createCallSite(
-                callSite.methodName, callSite.methodProto, newBootstrapMethod, newArgs);
+            DexCallSite newCallSite =
+                appInfo.dexItemFactory.createCallSite(
+                    callSite.methodName, newMethodProto, newBootstrapMethod, newArgs);
             InvokeCustom newInvokeCustom = new InvokeCustom(newCallSite, invokeCustom.outValue(),
                 invokeCustom.inValues());
             iterator.replaceCurrentInstruction(newInvokeCustom);
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 dae98ad..d42c68b 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -1659,18 +1659,18 @@
       this.staticInvokes = rewriteMethodsConservatively(previous.staticInvokes, lense);
       this.prunedTypes = rewriteItems(previous.prunedTypes, lense::lookupType);
       // TODO(herhut): Migrate these to Descriptors, as well.
-      assert assertNotModifiedByLense(previous.noSideEffects.keySet(), lense);
+      assert lense.assertNotModified(previous.noSideEffects.keySet());
       this.noSideEffects = previous.noSideEffects;
-      assert assertNotModifiedByLense(previous.assumedValues.keySet(), lense);
+      assert lense.assertNotModified(previous.assumedValues.keySet());
       this.assumedValues = previous.assumedValues;
-      assert assertNotModifiedByLense(previous.alwaysInline, lense);
+      assert lense.assertNotModified(previous.alwaysInline);
       this.alwaysInline = previous.alwaysInline;
       this.identifierNameStrings =
           rewriteMixedItemsConservatively(previous.identifierNameStrings, lense);
       // Switchmap classes should never be affected by renaming.
-      assert assertNotModifiedByLense(
+      assert lense.assertNotModified(
           previous.switchMaps.keySet().stream().map(this::definitionFor).filter(Objects::nonNull)
-              .collect(Collectors.toList()), lense);
+              .collect(Collectors.toList()));
       this.switchMaps = previous.switchMaps;
       this.ordinalsMaps = rewriteKeys(previous.ordinalsMaps, lense::lookupType);
       this.protoLiteFields = previous.protoLiteFields;
@@ -1735,27 +1735,6 @@
       return true;
     }
 
-    private boolean assertNotModifiedByLense(Iterable<DexItem> items, GraphLense lense) {
-      for (DexItem item : items) {
-        if (item instanceof DexClass) {
-          DexType type = ((DexClass) item).type;
-          assert lense.lookupType(type) == type;
-        } else if (item instanceof DexEncodedMethod) {
-          DexEncodedMethod method = (DexEncodedMethod) item;
-          // We only allow changes to bridge methods, as these get retargeted even if they
-          // are kept.
-          assert method.accessFlags.isBridge()
-              || lense.lookupMethod(method.method) == method.method;
-        } else if (item instanceof DexEncodedField) {
-          DexField field = ((DexEncodedField) item).field;
-          assert lense.lookupField(field) == field;
-        } else {
-          assert false;
-        }
-      }
-      return true;
-    }
-
     private SortedSet<DexMethod> joinInvokedMethods(Map<DexType, Set<DexMethod>> invokes) {
       return joinInvokedMethods(invokes, Function.identity());
     }
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 637f763..08722c3 100644
--- a/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
+++ b/src/main/java/com/android/tools/r8/shaking/VerticalClassMerger.java
@@ -96,8 +96,7 @@
     extractPinnedItems(appInfo.pinnedItems, pinnedTypes);
 
     // TODO(christofferqa): Remove the invariant that the graph lense should not modify any
-    // methods from the sets alwaysInline and noSideEffects (see use of assertNotModifiedBy-
-    // Lense).
+    // methods from the sets alwaysInline and noSideEffects (see use of assertNotModified).
     extractPinnedItems(appInfo.alwaysInline, pinnedTypes);
     extractPinnedItems(appInfo.noSideEffects.keySet(), pinnedTypes);
 
@@ -139,36 +138,41 @@
 
   private void extractPinnedItems(Iterable<DexItem> items, Set<DexType> pinnedTypes) {
     for (DexItem item : items) {
-      // Note: Nothing to do for the case where item is a DexType, since we check for this case
-      // using appInfo.isPinned.
-      if (item instanceof DexField) {
-        // Pin the type of the field.
-        DexField field = (DexField) item;
-        DexClass clazz = appInfo.definitionFor(field.type);
-        if (clazz != null && clazz.isProgramClass()) {
-          pinnedTypes.add(clazz.type);
+      if (item instanceof DexType || item instanceof DexClass) {
+        DexType type = item instanceof DexType ? (DexType) item : ((DexClass) item).type;
+        // We check for the case where the type is pinned according to appInfo.isPinned, so only
+        // add it here if it is not the case.
+        if (!appInfo.isPinned(type)) {
+          markTypeAsPinned(type, pinnedTypes);
         }
-      } else if (item instanceof DexMethod) {
-        // Pin the return type and the parameter types of the method.
-        DexMethod method = (DexMethod) item;
-        DexClass clazz = appInfo.definitionFor(method.proto.returnType);
-        if (clazz != null && clazz.isProgramClass()) {
-          // If we were to merge [other] into its sub class, then we would implicitly change the
-          // signature of this method, and therefore break the invariant.
-          pinnedTypes.add(clazz.type);
-        }
+      } else if (item instanceof DexField || item instanceof DexEncodedField) {
+        // Pin the holder and the type of the field.
+        DexField field =
+            item instanceof DexField ? (DexField) item : ((DexEncodedField) item).field;
+        markTypeAsPinned(field.clazz, pinnedTypes);
+        markTypeAsPinned(field.type, pinnedTypes);
+      } else if (item instanceof DexMethod || item instanceof DexEncodedMethod) {
+        // Pin the holder, the return type and the parameter types of the method. If we were to
+        // merge any of these types into their sub classes, then we would implicitly change the
+        // signature of this method.
+        DexMethod method =
+            item instanceof DexMethod ? (DexMethod) item : ((DexEncodedMethod) item).method;
+        markTypeAsPinned(method.holder, pinnedTypes);
+        markTypeAsPinned(method.proto.returnType, pinnedTypes);
         for (DexType parameterType : method.proto.parameters.values) {
-          clazz = appInfo.definitionFor(parameterType);
-          if (clazz != null && clazz.isProgramClass()) {
-            // If we were to merge [other] into its sub class, then we would implicitly change the
-            // signature of this method, and therefore break the invariant.
-            pinnedTypes.add(clazz.type);
-          }
+          markTypeAsPinned(parameterType, pinnedTypes);
         }
       }
     }
   }
 
+  private void markTypeAsPinned(DexType type, Set<DexType> pinnedTypes) {
+    DexClass clazz = appInfo.definitionFor(type);
+    if (clazz != null && clazz.isProgramClass()) {
+      pinnedTypes.add(type);
+    }
+  }
+
   private boolean isMergeCandidate(DexProgramClass clazz, Set<DexType> pinnedTypes) {
     if (appInfo.instantiatedTypes.contains(clazz.type)
         || appInfo.isPinned(clazz.type)
@@ -269,6 +273,10 @@
     timing.begin("fixup");
     GraphLense result = new TreeFixer().fixupTypeReferences(mergingGraphLense);
     timing.end();
+    assert result.assertNotModified(appInfo.alwaysInline);
+    assert result.assertNotModified(appInfo.noSideEffects.keySet());
+    // TODO(christofferqa): Enable this assert.
+    // assert result.assertNotModified(appInfo.pinnedItems);
     return result;
   }
 
diff --git a/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
new file mode 100644
index 0000000..79e98d1
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/LambdaRewritingTest.java
@@ -0,0 +1,38 @@
+// 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 classmerging;
+
+public class LambdaRewritingTest {
+
+  public static void main(String[] args) {
+    Interface obj = new InterfaceImpl();
+
+    // Leads to an invoke-custom instruction that mentions the type of `obj` since it is captured.
+    invoke(() -> obj.foo());
+  }
+
+  private static void invoke(Function f) {
+    f.accept();
+  }
+
+  public interface Function {
+
+    void accept();
+  }
+
+  // Will be merged into InterfaceImpl.
+  public interface Interface {
+
+    void foo();
+  }
+
+  public static class InterfaceImpl implements Interface {
+
+    @Override
+    public void foo() {
+      System.out.println("In InterfaceImpl.foo()");
+    }
+  }
+}
diff --git a/src/test/examplesAndroidO/classmerging/keep-rules.txt b/src/test/examplesAndroidO/classmerging/keep-rules.txt
new file mode 100644
index 0000000..9868dcf
--- /dev/null
+++ b/src/test/examplesAndroidO/classmerging/keep-rules.txt
@@ -0,0 +1,12 @@
+# 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.
+
+# Keep the application entry point. Get rid of everything that is not
+# reachable from there.
+-keep public class classmerging.LambdaRewritingTest {
+  public static void main(...);
+}
+
+# TODO(herhut): Consider supporting merging of inner-class attributes.
+# -keepattributes *
\ No newline at end of file
diff --git a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
index 1e71a82..2e983bd 100644
--- a/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
+++ b/src/test/java/com/android/tools/r8/classmerging/ClassMergingTest.java
@@ -35,6 +35,7 @@
 import java.util.Set;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Consumer;
+import java.util.function.Predicate;
 import org.junit.Ignore;
 import org.junit.Test;
 
@@ -44,11 +45,15 @@
 public class ClassMergingTest extends TestBase {
 
   private static final Path CF_DIR =
-      Paths.get(ToolHelper.BUILD_DIR).resolve("classes/examples/classmerging");
+      Paths.get(ToolHelper.BUILD_DIR).resolve("test/examples/classes/classmerging");
+  private static final Path JAVA8_CF_DIR =
+      Paths.get(ToolHelper.BUILD_DIR).resolve("test/examplesAndroidO/classes/classmerging");
   private static final Path EXAMPLE_JAR = Paths.get(ToolHelper.EXAMPLES_BUILD_DIR)
       .resolve("classmerging.jar");
   private static final Path EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_DIR)
       .resolve("classmerging").resolve("keep-rules.txt");
+  private static final Path JAVA8_EXAMPLE_KEEP = Paths.get(ToolHelper.EXAMPLES_ANDROID_O_DIR)
+      .resolve("classmerging").resolve("keep-rules.txt");
   private static final Path DONT_OPTIMIZE = Paths.get(ToolHelper.EXAMPLES_DIR)
       .resolve("classmerging").resolve("keep-rules-dontoptimize.txt");
 
@@ -109,6 +114,28 @@
   }
 
   @Test
+  public void testLambdaRewriting() throws Exception {
+    String main = "classmerging.LambdaRewritingTest";
+    Path[] programFiles =
+        new Path[] {
+          JAVA8_CF_DIR.resolve("LambdaRewritingTest.class"),
+          JAVA8_CF_DIR.resolve("LambdaRewritingTest$Function.class"),
+          JAVA8_CF_DIR.resolve("LambdaRewritingTest$Interface.class"),
+          JAVA8_CF_DIR.resolve("LambdaRewritingTest$InterfaceImpl.class")
+        };
+    Set<String> preservedClassNames =
+        ImmutableSet.of(
+            "classmerging.LambdaRewritingTest",
+            "classmerging.LambdaRewritingTest$Function",
+            "classmerging.LambdaRewritingTest$InterfaceImpl");
+    runTest(
+        main,
+        programFiles,
+        name -> preservedClassNames.contains(name) || name.contains("$Lambda$"),
+        getProguardConfig(JAVA8_EXAMPLE_KEEP));
+  }
+
+  @Test
   public void testSuperCallWasDetected() throws Exception {
     String main = "classmerging.SuperCallRewritingTest";
     Path[] programFiles =
@@ -121,7 +148,7 @@
         ImmutableSet.of(
             "classmerging.SubClassThatReferencesSuperMethod",
             "classmerging.SuperCallRewritingTest");
-    runTest(main, programFiles, preservedClassNames);
+    runTest(main, programFiles, preservedClassNames::contains);
   }
 
   // When a subclass A has been merged into its subclass B, we rewrite invoke-super calls that hit
@@ -177,7 +204,7 @@
     runTestOnInput(
         main,
         builder.build(),
-        preservedClassNames,
+        preservedClassNames::contains,
         // Prevent class merging, such that the generated code would be invalid if we rewrite the
         // invoke-super instruction into an invoke-direct instruction.
         getProguardConfig(EXAMPLE_KEEP, "-keep class *"));
@@ -197,7 +224,7 @@
         ImmutableSet.of(
             "classmerging.ConflictingInterfaceSignaturesTest",
             "classmerging.ConflictingInterfaceSignaturesTest$InterfaceImpl");
-    runTest(main, programFiles, preservedClassNames);
+    runTest(main, programFiles, preservedClassNames::contains);
   }
 
   // If an exception class A is merged into another exception class B, then all exception tables
@@ -218,7 +245,7 @@
             "classmerging.ExceptionTest",
             "classmerging.ExceptionTest$ExceptionB",
             "classmerging.ExceptionTest$Exception2");
-    DexInspector inspector = runTest(main, programFiles, preservedClassNames);
+    DexInspector inspector = runTest(main, programFiles, preservedClassNames::contains);
 
     ClassSubject mainClass = inspector.clazz(main);
     assertThat(mainClass, isPresent());
@@ -256,7 +283,7 @@
             "classmerging.SimpleInterfaceAccessTest",
             "classmerging.pkg.SimpleInterfaceImplRetriever",
             "classmerging.pkg.SimpleInterfaceImplRetriever$SimpleInterfaceImpl");
-    runTest(main, programFiles, preservedClassNames);
+    runTest(main, programFiles, preservedClassNames::contains);
   }
 
   @Ignore("b/73958515")
@@ -282,7 +309,7 @@
     runTest(
         main,
         programFiles,
-        preservedClassNames,
+        preservedClassNames::contains,
         getProguardConfig(
             EXAMPLE_KEEP,
             "-allowaccessmodification",
@@ -310,7 +337,7 @@
     runTest(
         main,
         programFiles,
-        preservedClassNames,
+        preservedClassNames::contains,
         getProguardConfig(
             EXAMPLE_KEEP, "-keep class classmerging.RewritePinnedMethodTest$A { *; }"));
   }
@@ -327,40 +354,42 @@
     Set<String> preservedClassNames =
         ImmutableSet.of(
             "classmerging.TemplateMethodTest", "classmerging.TemplateMethodTest$AbstractClassImpl");
-    runTest(main, programFiles, preservedClassNames);
+    runTest(main, programFiles, preservedClassNames::contains);
   }
 
-  private DexInspector runTest(String main, Path[] programFiles, Set<String> preservedClassNames)
-      throws Exception {
+  private DexInspector runTest(
+      String main, Path[] programFiles, Predicate<String> preservedClassNames) throws Exception {
     return runTest(main, programFiles, preservedClassNames, getProguardConfig(EXAMPLE_KEEP));
   }
 
   private DexInspector runTest(
-      String main, Path[] programFiles, Set<String> preservedClassNames, String proguardConfig)
+      String main,
+      Path[] programFiles,
+      Predicate<String> preservedClassNames,
+      String proguardConfig)
       throws Exception {
     return runTestOnInput(
         main, readProgramFiles(programFiles), preservedClassNames, proguardConfig);
   }
 
   private DexInspector runTestOnInput(
-      String main, AndroidApp input, Set<String> preservedClassNames, String proguardConfig)
+      String main, AndroidApp input, Predicate<String> preservedClassNames, String proguardConfig)
       throws Exception {
     AndroidApp output = compileWithR8(input, proguardConfig, this::configure);
-    DexInspector inspector = new DexInspector(output);
+    DexInspector inputInspector = new DexInspector(input);
+    DexInspector outputInspector = new DexInspector(output);
     // Check that all classes in [preservedClassNames] are in fact preserved.
-    for (String className : preservedClassNames) {
-      assertTrue(
-          "Class " + className + " should be present", inspector.clazz(className).isPresent());
-    }
-    // Check that all other classes have been removed.
-    for (FoundClassSubject classSubject : inspector.allClasses()) {
-      String className = classSubject.getDexClass().toSourceString();
-      assertTrue(
-          "Class " + className + " should be absent", preservedClassNames.contains(className));
+    for (FoundClassSubject classSubject : inputInspector.allClasses()) {
+      String className = classSubject.getOriginalName();
+      boolean shouldBePresent = preservedClassNames.test(className);
+      assertEquals(
+          "Class " + className + " should be " + (shouldBePresent ? "present" : "absent"),
+          shouldBePresent,
+          outputInspector.clazz(className).isPresent());
     }
     // Check that the R8-generated code produces the same result as D8-generated code.
     assertEquals(runOnArt(compileWithD8(input), main), runOnArt(output, main));
-    return inspector;
+    return outputInspector;
   }
 
   private String getProguardConfig(Path path, String... additionalRules) throws IOException {
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index d41ee84..5707812 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1943,6 +1943,7 @@
             .addAll(super.getExcludedClasses())
             .add("libcore.*")
             .add("dalvik.*")
+            .add("com.android.dex.*") // Android 6.0.1 - 7.0.0 use this for reflection.
             .build();
       }
     }