Fix Lambda desugaring with JDK17

Bug: b/236695789
Change-Id: I4826d8c005b6bc8aefe6064207e43ad395a1fdba
diff --git a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
index e195eac..36f717a 100644
--- a/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
+++ b/src/main/java/com/android/tools/r8/ir/conversion/ClassConverter.java
@@ -174,6 +174,9 @@
                 definition.markNotProcessed();
               }
               methodProcessor.processMethod(method, instructionDesugaringEventConsumer);
+              if (interfaceProcessor != null) {
+                interfaceProcessor.processMethod(method, instructionDesugaringEventConsumer);
+              }
             },
             executorService);
 
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
index 8236617..6137206 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/AccessorMethodSourceCode.java
@@ -24,6 +24,7 @@
         ForwardMethodBuilder.builder(appView.dexItemFactory()).setStaticSource(accessor);
     switch (type) {
       case INVOKE_INSTANCE:
+      case INVOKE_INTERFACE:
         {
           forwardMethodBuilder.setVirtualTarget(target, isInterface);
           break;
@@ -43,8 +44,6 @@
           forwardMethodBuilder.setConstructorTarget(target, appView.dexItemFactory());
           break;
         }
-      case INVOKE_INTERFACE:
-        throw new Unreachable("Accessor for an interface method?");
       default:
         throw new Unreachable();
     }
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
index fb51c23..46266fc 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaClass.java
@@ -322,6 +322,8 @@
       case INVOKE_SUPER:
         throw new Unimplemented("Method references to super methods are not yet supported");
       case INVOKE_INTERFACE:
+        // TODO(b/238085938): Investigate if we can do something smart if
+        // canAccessModifyLambdaImplMethod().
         return createInterfaceMethodTarget(accessedFrom);
       case INVOKE_CONSTRUCTOR:
         return createConstructorTarget(accessedFrom);
@@ -423,19 +425,23 @@
     }
   }
 
+  // Create targets for interface methods.
+  private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
+    assert descriptor.implHandle.type.isInvokeInterface();
+    return createInstanceOrInterfaceTarget(accessedFrom);
+  }
+
   // Create targets for instance method referenced directly without
   // lambda$ methods. It may require creation of accessors in some cases.
   private Target createInstanceMethodTarget(ProgramMethod accessedFrom) {
     assert descriptor.implHandle.type.isInvokeInstance() ||
         descriptor.implHandle.type.isInvokeDirect();
+    return createInstanceOrInterfaceTarget(accessedFrom);
+  }
 
+  private Target createInstanceOrInterfaceTarget(ProgramMethod accessedFrom) {
     if (doesNotNeedAccessor(accessedFrom)) {
-      return new NoAccessorMethodTarget(
-          descriptor.implHandle.asMethod(),
-          descriptor.implHandle.type.isInvokeDirect()
-              ? Type.DIRECT
-              : descriptor.implHandle.type.isInvokeStatic() ? Type.STATIC : Type.VIRTUAL,
-          descriptor.implHandle.isInterface);
+      return NoAccessorMethodTarget.create(descriptor);
     }
 
     // We need to generate an accessor method in `accessedFrom` class/interface
@@ -459,12 +465,8 @@
             .createMethod(
                 accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
 
-    return new ClassMethodWithAccessorTarget(
-        descriptor.implHandle.asMethod(),
-        descriptor.implHandle.isInterface,
-        descriptor.implHandle.type,
-        accessorMethod,
-        appView);
+    return ClassMethodWithAccessorTarget.create(
+        descriptor.implHandle, accessedFrom, accessorMethod, appView);
   }
 
   // Create targets for static method referenced directly without
@@ -473,8 +475,7 @@
     assert descriptor.implHandle.type.isInvokeStatic();
 
     if (doesNotNeedAccessor(accessedFrom)) {
-      return new NoAccessorMethodTarget(
-          descriptor.implHandle.asMethod(), Type.STATIC, descriptor.implHandle.isInterface);
+      return NoAccessorMethodTarget.create(descriptor);
     }
 
     // We need to generate an accessor method in `accessedFrom` class/interface
@@ -487,12 +488,8 @@
                 accessedFrom.getHolderType(),
                 descriptor.implHandle.asMethod().proto,
                 generateUniqueLambdaMethodName());
-    return new ClassMethodWithAccessorTarget(
-        descriptor.implHandle.asMethod(),
-        descriptor.implHandle.isInterface,
-        descriptor.implHandle.type,
-        accessorMethod,
-        appView);
+    return ClassMethodWithAccessorTarget.create(
+        descriptor.implHandle, accessedFrom, accessorMethod, appView);
   }
 
   // Create targets for constructor referenced directly without lambda$ methods.
@@ -503,8 +500,7 @@
     assert implHandle.type.isInvokeConstructor();
 
     if (doesNotNeedAccessor(accessedFrom)) {
-      return new NoAccessorMethodTarget(
-          descriptor.implHandle.asMethod(), Type.DIRECT, descriptor.implHandle.isInterface);
+      return NoAccessorMethodTarget.create(descriptor);
     }
 
     // We need to generate an accessor method in `accessedFrom` class/interface for
@@ -520,20 +516,8 @@
             .dexItemFactory()
             .createMethod(
                 accessedFrom.getHolderType(), accessorProto, generateUniqueLambdaMethodName());
-    return new ClassMethodWithAccessorTarget(
-        descriptor.implHandle.asMethod(),
-        descriptor.implHandle.isInterface,
-        descriptor.implHandle.type,
-        accessorMethod,
-        appView);
-  }
-
-  // Create targets for interface methods.
-  private Target createInterfaceMethodTarget(ProgramMethod accessedFrom) {
-    assert descriptor.implHandle.type.isInvokeInterface();
-    assert doesNotNeedAccessor(accessedFrom);
-    return new NoAccessorMethodTarget(
-        descriptor.implHandle.asMethod(), Type.INTERFACE, descriptor.implHandle.isInterface);
+    return ClassMethodWithAccessorTarget.create(
+        descriptor.implHandle, accessedFrom, accessorMethod, appView);
   }
 
   private DexString generateUniqueLambdaMethodName() {
@@ -594,6 +578,13 @@
   // Used for targeting methods referenced directly without creating accessors.
   public static final class NoAccessorMethodTarget extends Target {
 
+    static NoAccessorMethodTarget create(LambdaDescriptor descriptor) {
+      return new NoAccessorMethodTarget(
+          descriptor.implHandle.asMethod(),
+          descriptor.implHandle.type.toInvokeType(),
+          descriptor.implHandle.isInterface);
+    }
+
     NoAccessorMethodTarget(DexMethod method, Type invokeType, boolean isInterface) {
       super(method, invokeType, isInterface);
     }
@@ -789,21 +780,37 @@
 
     private final AppView<?> appView;
     private final DexMethod implMethod;
+    private final boolean implMethodIsInterface;
     private final MethodHandleType type;
 
-    ClassMethodWithAccessorTarget(
+    static ClassMethodWithAccessorTarget create(
+        DexMethodHandle implHandle,
+        ProgramMethod accessedFrom,
+        DexMethod accessorMethod,
+        AppView<?> appView) {
+      return new ClassMethodWithAccessorTarget(
+          implHandle.asMethod(),
+          implHandle.isInterface,
+          implHandle.type,
+          accessorMethod,
+          accessedFrom.getHolder().isInterface(),
+          appView);
+    }
+
+    private ClassMethodWithAccessorTarget(
         DexMethod implMethod,
         boolean isInterface,
         MethodHandleType type,
         DexMethod accessorMethod,
+        boolean accessorIsInterface,
         AppView<?> appView) {
-      super(accessorMethod, Invoke.Type.STATIC, isInterface);
+      super(accessorMethod, Invoke.Type.STATIC, accessorIsInterface);
       this.appView = appView;
       this.implMethod = implMethod;
+      this.implMethodIsInterface = isInterface;
       this.type = type;
     }
 
-
     @Override
     ProgramMethod ensureAccessibility(
         ForcefullyMovedLambdaMethodConsumer forcefullyMovedLambdaMethodConsumer,
@@ -831,7 +838,7 @@
                   .setAccessFlags(MethodAccessFlags.createPublicStaticSynthetic())
                   .setCode(
                       AccessorMethodSourceCode.build(
-                          implMethod, isInterface, type, callTarget, appView))
+                          implMethod, implMethodIsInterface, type, callTarget, appView))
                   // The api level is computed when tracing.
                   .disableAndroidApiLevelCheck()
                   .build());
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
index d6b10b0..df7be1b 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/LambdaDescriptor.java
@@ -203,15 +203,18 @@
 
   /** Checks if call site needs a accessor when referenced from `accessedFrom`. */
   boolean needsAccessor(ProgramMethod accessedFrom) {
-    if (implHandle.type.isInvokeInterface()) {
-      // Interface methods must be public.
+    // The invoke-interface may target a private method through nest based access in JDK 17.
+    // If the targetAccessFlags are missing we assume no accessor is needed since that was D8/R8's
+    // behavior prior to the introduction of bridges on invoke-interface.
+    if (implHandle.type.isInvokeInterface()
+        && (targetAccessFlags == null || targetAccessFlags.isPublic())) {
       return false;
     }
 
     boolean staticTarget = implHandle.type.isInvokeStatic();
     boolean instanceTarget = implHandle.type.isInvokeInstance() || implHandle.type.isInvokeDirect();
     boolean initTarget = implHandle.type.isInvokeConstructor();
-    assert instanceTarget || staticTarget || initTarget;
+    assert instanceTarget || staticTarget || initTarget || implHandle.type.isInvokeInterface();
     assert !implHandle.type.isInvokeDirect()
         || (targetAccessFlags.isPrivate()
             && !targetAccessFlags.isConstructor()
diff --git a/src/test/examplesJava17/lambda/Lambda.java b/src/test/examplesJava17/lambda/Lambda.java
new file mode 100644
index 0000000..d0c1035
--- /dev/null
+++ b/src/test/examplesJava17/lambda/Lambda.java
@@ -0,0 +1,46 @@
+// Copyright (c) 2022, 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 lambda;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Lambda {
+
+  interface StringPredicate {
+
+    boolean test(String t);
+
+    default StringPredicate or(StringPredicate other) {
+      return (t) -> test(t) || other.test(t);
+    }
+  }
+
+  public static void main(String[] args) {
+    ArrayList<String> strings = new ArrayList<>();
+    strings.add("abc");
+    strings.add("abb");
+    strings.add("bbc");
+    strings.add("aac");
+    strings.add("acc");
+    StringPredicate aaStart = Lambda::aaStart;
+    StringPredicate bbNot = Lambda::bbNot;
+    StringPredicate full = aaStart.or(bbNot);
+    for (String string : ((List<String>) strings.clone())) {
+      if (full.test(string)) {
+        strings.remove(string);
+      }
+    }
+    System.out.println(strings);
+  }
+
+  private static boolean aaStart(String str) {
+    return str.startsWith("aa");
+  }
+
+  private static boolean bbNot(String str) {
+    return !str.contains("bb");
+  }
+}
diff --git a/src/test/examplesJava17/nest/NestLambda.java b/src/test/examplesJava17/nest/NestLambda.java
index 2c690b5..c60a8fb 100644
--- a/src/test/examplesJava17/nest/NestLambda.java
+++ b/src/test/examplesJava17/nest/NestLambda.java
@@ -4,8 +4,6 @@
 
 package nest;
 
-import java.util.function.Consumer;
-
 public class NestLambda {
 
   private void print(Object o) {
@@ -16,9 +14,14 @@
     return new Inner();
   }
 
-  class Inner {
+  // Avoids java.util.Consumer to run below on Apis below 24.
+  interface ThisConsumer<T> {
+    void accept(T t);
+  }
 
-    void exec(Consumer<Object> consumer) {
+  class Inner implements Itf {
+
+    void exec(ThisConsumer<String> consumer) {
       consumer.accept("inner");
     }
 
@@ -27,7 +30,22 @@
     }
   }
 
+  interface Itf {
+    private void printItf(Object o) {
+      System.out.println("printed from itf: " + o);
+    }
+  }
+
+  void exec(ThisConsumer<String> consumer) {
+    consumer.accept("here");
+  }
+
+  void execItfLambda(Itf itf) {
+    exec(itf::printItf);
+  }
+
   public static void main(String[] args) {
     new NestLambda().getInner().execLambda();
+    new NestLambda().execItfLambda(new Itf() {});
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaJava17Test.java b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaJava17Test.java
new file mode 100644
index 0000000..6c9e899
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/lambdas/LambdaJava17Test.java
@@ -0,0 +1,79 @@
+// Copyright (c) 2022, 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.desugar.lambdas;
+
+import static com.android.tools.r8.utils.AndroidApiLevel.B;
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.Assume;
+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 LambdaJava17Test extends TestBase {
+
+  public LambdaJava17Test(TestParameters parameters) {
+    this.parameters = parameters;
+  }
+
+  private static final Path JDK17_JAR =
+      Paths.get(ToolHelper.TESTS_BUILD_DIR, "examplesJava17").resolve("lambda" + JAR_EXTENSION);
+  private static final String MAIN = "lambda.Lambda";
+  private static final String EXPECTED_RESULT = "[abb, bbc]";
+
+  private final TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters()
+        .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
+        .withDexRuntimes()
+        .withAllApiLevels()
+        .enableApiLevelsForCf()
+        .build();
+  }
+
+  @Test
+  public void testReference() throws Exception {
+    Assume.assumeTrue(parameters.isCfRuntime());
+    testForJvm()
+        .addProgramFiles(JDK17_JAR)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testJavaD8() throws Exception {
+    testForDesugaring(parameters)
+        .addProgramFiles(JDK17_JAR)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    Assume.assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().equals(B));
+    testForR8(parameters.getBackend())
+        .addProgramFiles(JDK17_JAR)
+        .applyIf(
+            parameters.isCfRuntime() && !parameters.getApiLevel().isEqualTo(AndroidApiLevel.B),
+            // Alternatively we need to pass Jdk17 as library.
+            b -> b.addKeepRules("-dontwarn java.lang.invoke.StringConcatFactory"))
+        .setMinApi(parameters.getApiLevel())
+        .addKeepMainRule(MAIN)
+        .run(parameters.getRuntime(), MAIN)
+        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
index 94fbe71..a166409 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestLambdaJava17Test.java
@@ -4,7 +4,6 @@
 
 package com.android.tools.r8.desugar.nestaccesscontrol;
 
-import static com.android.tools.r8.utils.AndroidApiLevel.B;
 import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
 
 import com.android.tools.r8.TestBase;
@@ -12,8 +11,7 @@
 import com.android.tools.r8.TestParametersCollection;
 import com.android.tools.r8.TestRuntime.CfVm;
 import com.android.tools.r8.ToolHelper;
-import com.android.tools.r8.ToolHelper.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.StringUtils;
 import java.nio.file.Path;
 import java.nio.file.Paths;
 import org.junit.Assume;
@@ -32,7 +30,8 @@
   private static final Path JDK17_JAR =
       Paths.get(ToolHelper.TESTS_BUILD_DIR, "examplesJava17").resolve("nest" + JAR_EXTENSION);
   private static final String MAIN = "nest.NestLambda";
-  private static final String EXPECTED_RESULT = "printed: inner";
+  private static final String EXPECTED_RESULT =
+      StringUtils.lines("printed: inner", "printed from itf: here");
 
   private final TestParameters parameters;
 
@@ -40,10 +39,8 @@
   public static TestParametersCollection data() {
     return getTestParameters()
         .withCfRuntimesStartingFromIncluding(CfVm.JDK17)
-        // The test requires the java.util.function. package.
-        .withDexRuntimesStartingFromIncluding(Version.V7_0_0)
-        .withApiLevel(AndroidApiLevel.N)
-        .enableApiLevelsForCf()
+        .withDexRuntimes()
+        .withAllApiLevelsAlsoForCf()
         .build();
   }
 
@@ -53,20 +50,20 @@
     testForJvm()
         .addProgramFiles(JDK17_JAR)
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testJavaD8() throws Exception {
-    testForDesugaring(parameters)
+    testForD8(parameters.getBackend())
         .addProgramFiles(JDK17_JAR)
+        .setMinApi(parameters.getApiLevel())
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 
   @Test
   public void testR8() throws Exception {
-    Assume.assumeTrue(parameters.isDexRuntime() || parameters.getApiLevel().equals(B));
     testForR8(parameters.getBackend())
         .addProgramFiles(JDK17_JAR)
         .applyIf(
@@ -76,6 +73,6 @@
         .setMinApi(parameters.getApiLevel())
         .addKeepMainRule(MAIN)
         .run(parameters.getRuntime(), MAIN)
-        .assertSuccessWithOutputLines(EXPECTED_RESULT);
+        .assertSuccessWithOutput(EXPECTED_RESULT);
   }
 }