Backport android.util.SparseArray#set

For Android S android.util.SparseArray#set was added as an alias for
android.util.SparseArray#put.

Bug: 185547135
Change-Id: I6b3558921a14fc3e0ac7766db8c6b6e614d0c236
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 ceb543f..646fca7 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -536,6 +536,8 @@
   public final DexType androidUtilPropertyType =
       createStaticallyKnownType("Landroid/util/Property;");
   public final DexType androidViewViewType = createStaticallyKnownType("Landroid/view/View;");
+  public final DexType androidUtilSparseArrayType =
+      createStaticallyKnownType("Landroid/util/SparseArray;");
 
   public final StringBuildingMethods stringBuilderMethods =
       new StringBuildingMethods(stringBuilderType);
@@ -581,6 +583,8 @@
   public final AndroidSystemOsConstantsMembers androidSystemOsConstantsMembers =
       new AndroidSystemOsConstantsMembers();
   public final AndroidViewViewMembers androidViewViewMembers = new AndroidViewViewMembers();
+  public final AndroidUtilSparseArrayMembers androidUtilSparseArrayMembers =
+      new AndroidUtilSparseArrayMembers();
 
   // java.**
   public final JavaIoFileMembers javaIoFileMembers = new JavaIoFileMembers();
@@ -959,6 +963,13 @@
     }
   }
 
+  public class AndroidUtilSparseArrayMembers extends LibraryMembers {
+    public final DexMethod put =
+        createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "put");
+    public final DexMethod set =
+        createMethod(androidUtilSparseArrayType, createProto(voidType, intType, objectType), "set");
+  }
+
   public class BooleanMembers extends LibraryMembers {
 
     public final DexField FALSE = createField(boxedBooleanType, boxedBooleanType, "FALSE");
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
index 7bb5741..832fd8c 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/BackportedMethodRewriter.java
@@ -31,6 +31,7 @@
 import com.android.tools.r8.ir.desugar.backports.NumericMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.ObjectsMethodRewrites;
 import com.android.tools.r8.ir.desugar.backports.OptionalMethodRewrites;
+import com.android.tools.r8.ir.desugar.backports.SparseArrayMethodRewrites;
 import com.android.tools.r8.synthesis.SyntheticNaming;
 import com.android.tools.r8.utils.AndroidApiLevel;
 import com.android.tools.r8.utils.AndroidApp;
@@ -1055,6 +1056,13 @@
             new MethodGenerator(
                 method, BackportedMethods::MathMethods_floorModLongInt, "floorModLongInt"));
       }
+
+      // android.util.SparseArray
+
+      // void android.util.SparseArray.set(int, Object))
+      addProvider(
+          new InvokeRewriter(
+              factory.androidUtilSparseArrayMembers.set, SparseArrayMethodRewrites.rewriteSet()));
     }
 
     private void initializeJava9MethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java
new file mode 100644
index 0000000..e1858e2
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/SparseArrayMethodRewrites.java
@@ -0,0 +1,20 @@
+// 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.desugar.backports;
+
+import com.android.tools.r8.cf.code.CfInvoke;
+import com.android.tools.r8.ir.desugar.BackportedMethodRewriter.MethodInvokeRewriter;
+import org.objectweb.asm.Opcodes;
+
+public final class SparseArrayMethodRewrites {
+
+  private SparseArrayMethodRewrites() {}
+
+  public static MethodInvokeRewriter rewriteSet() {
+    // Rewrite android/util/SparseArray#set to android/util/SparseArray#put
+    return (invoke, factory) ->
+        new CfInvoke(Opcodes.INVOKEVIRTUAL, factory.androidUtilSparseArrayMembers.put, false);
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
index 646dbf9..e4dd6d9 100644
--- a/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/backports/AbstractBackportTest.java
@@ -17,6 +17,7 @@
 import com.android.tools.r8.utils.codeinspector.CodeInspector;
 import com.android.tools.r8.utils.codeinspector.InstructionSubject;
 import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
 import it.unimi.dsi.fastutil.ints.Int2IntAVLTreeMap;
 import it.unimi.dsi.fastutil.ints.Int2IntSortedMap;
 import java.io.IOException;
@@ -29,25 +30,71 @@
 
 abstract class AbstractBackportTest extends TestBase {
   protected final TestParameters parameters;
-  private final Class<?> targetClass;
-  private final Class<?> testClass;
+  private final ClassInfo targetClass;
+  private final ClassInfo testClass;
   private final Path testJar;
   private final String testClassName;
   private final Int2IntSortedMap invokeStaticCounts = new Int2IntAVLTreeMap();
   private final Set<String> ignoredInvokes = new HashSet<>();
 
+  private static class ClassInfo {
+    private final Class<?> clazz;
+    private final List<byte[]> classFileData;
+
+    private ClassInfo(Class<?> clazz) {
+      this.clazz = clazz;
+      this.classFileData = null;
+    }
+
+    private ClassInfo(byte[] classFileData) {
+      this.clazz = null;
+      this.classFileData = ImmutableList.of(classFileData);
+    }
+
+    private ClassInfo(List<byte[]> classFileData) {
+      this.clazz = null;
+      this.classFileData = classFileData;
+    }
+
+    String getName() {
+      return clazz != null ? clazz.getName() : extractClassName(classFileData.get(0));
+    }
+
+    TestBuilder<?, ?> addAsProgramClass(TestBuilder<?, ?> builder) throws IOException {
+      if (clazz != null) {
+        return builder.addProgramClassesAndInnerClasses(clazz);
+      } else {
+        return builder.addProgramClassFileData(classFileData);
+      }
+    }
+  }
+
   AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
       Class<?> testClass) {
-    this(parameters, targetClass, testClass, null, null);
+    this(parameters, new ClassInfo(targetClass), new ClassInfo(testClass), null, null);
+  }
+
+  AbstractBackportTest(
+      TestParameters parameters, byte[] targetClassFileData, List<byte[]> testClassFileData) {
+    this(
+        parameters,
+        new ClassInfo(targetClassFileData),
+        new ClassInfo(testClassFileData),
+        null,
+        null);
   }
 
   AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
       Path testJar, String testClassName) {
-    this(parameters, targetClass, null, testJar, testClassName);
+    this(parameters, new ClassInfo(targetClass), null, testJar, testClassName);
   }
 
-  private AbstractBackportTest(TestParameters parameters, Class<?> targetClass,
-      Class<?> testClass, Path testJar, String testClassName) {
+  private AbstractBackportTest(
+      TestParameters parameters,
+      ClassInfo targetClass,
+      ClassInfo testClass,
+      Path testJar,
+      String testClassName) {
     this.parameters = parameters;
     this.targetClass = targetClass;
     this.testClass = testClass;
@@ -83,7 +130,7 @@
   private void configureProgram(TestBuilder<?, ?> builder) throws IOException {
     builder.addProgramClasses(MiniAssert.class, IgnoreInvokes.class);
     if (testClass != null) {
-      builder.addProgramClassesAndInnerClasses(testClass);
+      testClass.addAsProgramClass(builder);
     } else {
       builder.addProgramFiles(testJar);
     }
@@ -188,5 +235,9 @@
             "Expected <" + expected + "> to be same instance as <" + actual + '>');
       }
     }
+
+    static void fail(String message) {
+      throw new AssertionError("Failed: " + message);
+    }
   }
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
new file mode 100644
index 0000000..61cf95d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/SparseArrayBackportTest.java
@@ -0,0 +1,78 @@
+// 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.desugar.backports;
+
+import com.android.tools.r8.TestParameters;
+import com.google.common.collect.ImmutableList;
+import java.io.IOException;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class SparseArrayBackportTest extends AbstractBackportTest {
+
+  private static final String SPARSE_ARRAY_DESCRIPTOR = "Landroid/util/SparseArray;";
+
+  @Parameters(name = "{0}")
+  public static Iterable<?> data() {
+    return getTestParameters().withDexRuntimes().withAllApiLevels().build();
+  }
+
+  public SparseArrayBackportTest(TestParameters parameters) throws IOException {
+    super(
+        parameters,
+        SparseArrayBackportTest.getSparseArray(),
+        ImmutableList.of(
+            SparseArrayBackportTest.getTestRunner(), SparseArrayBackportTest.getSparseArray()));
+
+    // The constructor is used by the test and put has been available since API 1 and is the
+    // method set is rewritten to.
+    ignoreInvokes("<init>");
+    ignoreInvokes("put");
+  }
+
+  private static byte[] getSparseArray() throws IOException {
+    return transformer(SparseArray.class).setClassDescriptor(SPARSE_ARRAY_DESCRIPTOR).transform();
+  }
+
+  private static byte[] getTestRunner() throws IOException {
+    return transformer(TestRunner.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(SparseArray.class), SPARSE_ARRAY_DESCRIPTOR)
+        .transform();
+  }
+
+  public static class SparseArray {
+    public void set(int index, Object value) {
+      TestRunner.doFail("set should not be called");
+    }
+
+    public void put(int index, Object value) {
+      TestRunner.doAssertEquals(42, index);
+      TestRunner.doAssertEquals("Forty two", value);
+    }
+  }
+
+  public static class TestRunner extends MiniAssert {
+
+    public static void main(String[] args) {
+      new SparseArray().set(42, "Forty two");
+    }
+
+    // Forwards to MiniAssert to avoid having to make it public.
+    public static void doAssertEquals(int expected, int actual) {
+      MiniAssert.assertEquals(expected, actual);
+    }
+
+    public static void doAssertEquals(Object expected, Object actual) {
+      MiniAssert.assertEquals(expected, actual);
+    }
+
+    public static void doFail(String message) {
+      MiniAssert.fail(message);
+    }
+  }
+}