Enable access modification for Kotlin $lambda$ methods

This unifies the handling of Java and Kotlin lambda synthetic methods in lambda desugaring.

Change-Id: I1707484cbba23bab511ee9c04d2ca7a0e4e59778
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index da79f26..e53e662 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -351,6 +351,7 @@
   public final DexString thisName = createString("this");
   public final DexString javacLambdaMethodPrefix =
       createString(LambdaClass.JAVAC_EXPECTED_LAMBDA_METHOD_PREFIX);
+  public final DexString kotlinLambdaMethodIdentifier = createString("$lambda$");
 
   public final DexString enabledFieldName = createString("ENABLED");
 
diff --git a/src/main/java/com/android/tools/r8/graph/DexString.java b/src/main/java/com/android/tools/r8/graph/DexString.java
index aa0355a..e5ce12a 100644
--- a/src/main/java/com/android/tools/r8/graph/DexString.java
+++ b/src/main/java/com/android/tools/r8/graph/DexString.java
@@ -596,6 +596,29 @@
     return true;
   }
 
+  // Returns the index at which all remaining characters are numbers. Returns -1 if the string does
+  // not end with a number.
+  public int getNumberSuffixStartIndex() {
+    // TODO(b/146621590): This does not handle character boundaries correctly.
+    if (content.length == 1) {
+      assert isEmpty();
+      return -1;
+    }
+    int i = content.length - 2;
+    if (!isMutf8EncodedNumber(i)) {
+      return -1;
+    }
+    while (i >= 1 && isMutf8EncodedNumber(i - 1)) {
+      i--;
+    }
+    return i;
+  }
+
+  private boolean isMutf8EncodedNumber(int index) {
+    byte element = content[index];
+    return '0' <= element && element <= '9';
+  }
+
   public DexString prepend(String prefix, DexItemFactory dexItemFactory) {
     return prepend(dexItemFactory.createString(prefix), dexItemFactory);
   }
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 2032108..88e9773 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
@@ -26,6 +26,7 @@
 import com.android.tools.r8.utils.SetUtils;
 import com.google.common.collect.Sets;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Set;
 import java.util.function.BiConsumer;
@@ -210,9 +211,36 @@
 
   /** If the lambda delegates to lambda$ method. */
   public boolean delegatesToLambdaImplMethod(DexItemFactory factory) {
+    return delegatesToJavaLambdaImplMethod(factory) || delegatesToKotlinLambdaImplMethod(factory);
+  }
+
+  private boolean delegatesToJavaLambdaImplMethod(DexItemFactory factory) {
     return implHandle.asMethod().getName().startsWith(factory.javacLambdaMethodPrefix);
   }
 
+  private boolean delegatesToKotlinLambdaImplMethod(DexItemFactory factory) {
+    DexString methodName = implHandle.asMethod().getName();
+    int numberSuffixStartIndex = methodName.getNumberSuffixStartIndex();
+    if (numberSuffixStartIndex < 0) {
+      return false;
+    }
+    // Subtract the length of "$lambda$" (account for the EOF character).
+    assert factory.kotlinLambdaMethodIdentifier.content.length == 9;
+    int kotlinLambdaMethodIdentifierStartIndex = numberSuffixStartIndex - 8;
+    if (kotlinLambdaMethodIdentifierStartIndex <= 0) {
+      return false;
+    }
+    return Arrays.equals(
+        methodName.content,
+        kotlinLambdaMethodIdentifierStartIndex,
+        // To index (exclusive).
+        numberSuffixStartIndex,
+        factory.kotlinLambdaMethodIdentifier.content,
+        0,
+        // To index (exclusive). Use the index containing the EOF character as the to index.
+        8);
+  }
+
   public void forEachErasedAndEnforcedTypes(BiConsumer<DexType, DexType> consumer) {
     DexProto erasedProto = getErasedProto();
     consumer.accept(erasedProto.getReturnType(), enforcedProto.getReturnType());