Backport String.repeat(int)
Test: tools/test.py --dex_vm all --no-internal -v *Backport*Test*
Test: tools/test.py --no-internal -v *GenerateBackportMethods*
Change-Id: I20cc8116d8333ac4b54dc62122067c159688dc54
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 bdc98f0..985195b 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
@@ -1417,6 +1417,17 @@
addProvider(
new MethodGenerator(
method, BackportedMethods::CharacterMethods_toStringCodepoint, "toStringCodepoint"));
+
+ // String
+ type = factory.stringType;
+
+ // String String.repeat(int)
+ name = factory.createString("repeat");
+ proto = factory.createProto(factory.stringType, factory.intType);
+ method = factory.createMethod(type, proto, name);
+ addProvider(
+ new StatifyingMethodGenerator(
+ method, BackportedMethods::StringMethods_repeat, "repeat", type));
}
private void initializeJava9OptionalMethodProviders(DexItemFactory factory) {
diff --git a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
index 8440fc2..ce1d21f 100644
--- a/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
+++ b/src/main/java/com/android/tools/r8/ir/desugar/backports/BackportedMethods.java
@@ -5960,4 +5960,156 @@
ImmutableList.of(),
ImmutableList.of());
}
+
+ public static CfCode StringMethods_repeat(InternalOptions options, DexMethod method) {
+ CfLabel label0 = new CfLabel();
+ CfLabel label1 = new CfLabel();
+ CfLabel label2 = new CfLabel();
+ CfLabel label3 = new CfLabel();
+ CfLabel label4 = new CfLabel();
+ CfLabel label5 = new CfLabel();
+ CfLabel label6 = new CfLabel();
+ CfLabel label7 = new CfLabel();
+ CfLabel label8 = new CfLabel();
+ CfLabel label9 = new CfLabel();
+ CfLabel label10 = new CfLabel();
+ CfLabel label11 = new CfLabel();
+ CfLabel label12 = new CfLabel();
+ CfLabel label13 = new CfLabel();
+ return new CfCode(
+ method.holder,
+ 4,
+ 5,
+ ImmutableList.of(
+ label0,
+ new CfLoad(ValueType.INT, 1),
+ new CfIf(If.Type.GE, ValueType.INT, label2),
+ label1,
+ new CfNew(options.itemFactory.createType("Ljava/lang/IllegalArgumentException;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(options.itemFactory.createType("V")),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfConstString(options.itemFactory.createString("count is negative: ")),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createType("Ljava/lang/String;")),
+ options.itemFactory.createString("append")),
+ false),
+ new CfLoad(ValueType.INT, 1),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createType("I")),
+ options.itemFactory.createString("append")),
+ false),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/String;")),
+ options.itemFactory.createString("toString")),
+ false),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/IllegalArgumentException;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("V"),
+ options.itemFactory.createType("Ljava/lang/String;")),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfThrow(),
+ label2,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/String;"),
+ options.itemFactory.createProto(options.itemFactory.createType("I")),
+ options.itemFactory.createString("length")),
+ false),
+ new CfStore(ValueType.INT, 2),
+ label3,
+ new CfLoad(ValueType.INT, 1),
+ new CfIf(If.Type.EQ, ValueType.INT, label4),
+ new CfLoad(ValueType.INT, 2),
+ new CfIf(If.Type.NE, ValueType.INT, label5),
+ label4,
+ new CfConstString(options.itemFactory.createString("")),
+ new CfReturn(ValueType.OBJECT),
+ label5,
+ new CfLoad(ValueType.INT, 1),
+ new CfConstNumber(1, ValueType.INT),
+ new CfIfCmp(If.Type.NE, ValueType.INT, label7),
+ label6,
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfReturn(ValueType.OBJECT),
+ label7,
+ new CfNew(options.itemFactory.createType("Ljava/lang/StringBuilder;")),
+ new CfStackInstruction(CfStackInstruction.Opcode.Dup),
+ new CfLoad(ValueType.INT, 2),
+ new CfLoad(ValueType.INT, 1),
+ new CfArithmeticBinop(CfArithmeticBinop.Opcode.Mul, NumericType.INT),
+ new CfInvoke(
+ 183,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("V"), options.itemFactory.createType("I")),
+ options.itemFactory.createString("<init>")),
+ false),
+ new CfStore(ValueType.OBJECT, 3),
+ label8,
+ new CfConstNumber(0, ValueType.INT),
+ new CfStore(ValueType.INT, 4),
+ label9,
+ new CfLoad(ValueType.INT, 4),
+ new CfLoad(ValueType.INT, 1),
+ new CfIfCmp(If.Type.GE, ValueType.INT, label12),
+ label10,
+ new CfLoad(ValueType.OBJECT, 3),
+ new CfLoad(ValueType.OBJECT, 0),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createType("Ljava/lang/String;")),
+ options.itemFactory.createString("append")),
+ false),
+ new CfStackInstruction(CfStackInstruction.Opcode.Pop),
+ label11,
+ new CfIinc(4, 1),
+ new CfGoto(label9),
+ label12,
+ new CfLoad(ValueType.OBJECT, 3),
+ new CfInvoke(
+ 182,
+ options.itemFactory.createMethod(
+ options.itemFactory.createType("Ljava/lang/StringBuilder;"),
+ options.itemFactory.createProto(
+ options.itemFactory.createType("Ljava/lang/String;")),
+ options.itemFactory.createString("toString")),
+ false),
+ new CfReturn(ValueType.OBJECT),
+ label13),
+ ImmutableList.of(),
+ ImmutableList.of());
+ }
}
diff --git a/src/test/examplesJava11/backport/StringBackportJava11Main.java b/src/test/examplesJava11/backport/StringBackportJava11Main.java
new file mode 100644
index 0000000..2af923e
--- /dev/null
+++ b/src/test/examplesJava11/backport/StringBackportJava11Main.java
@@ -0,0 +1,31 @@
+package backport;
+
+public final class StringBackportJava11Main {
+ public static void main(String[] args) {
+ testRepeat();
+ }
+
+ private static void testRepeat() {
+ try {
+ throw new AssertionError("hey".repeat(-1));
+ } catch (IllegalArgumentException e) {
+ assertEquals("count is negative: -1", e.getMessage());
+ }
+
+ assertEquals("", "".repeat(0));
+ assertEquals("", "".repeat(1));
+ assertEquals("", "".repeat(2));
+
+ assertEquals("", "hey".repeat(0));
+ assertEquals("hey", "hey".repeat(1));
+ assertEquals("heyhey", "hey".repeat(2));
+ assertEquals("heyheyhey", "hey".repeat(3));
+ assertEquals("heyheyheyhey", "hey".repeat(4));
+ }
+
+ private static void assertEquals(Object expected, Object actual) {
+ if (expected != actual && (expected == null || !expected.equals(actual))) {
+ throw new AssertionError("Expected <" + expected + "> but was <" + actual + '>');
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java
new file mode 100644
index 0000000..b7f4b6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/backports/StringBackportJava11Test.java
@@ -0,0 +1,37 @@
+// 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.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.ToolHelper;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+import static com.android.tools.r8.utils.FileUtils.JAR_EXTENSION;
+
+@RunWith(Parameterized.class)
+public final class StringBackportJava11Test extends AbstractBackportTest {
+ @Parameters(name = "{0}")
+ public static Iterable<?> data() {
+ return getTestParameters()
+ .withDexRuntimes()
+ .withAllApiLevels()
+ .withCfRuntimesStartingFromIncluding(CfVm.JDK11)
+ .build();
+ }
+
+ private static final Path TEST_JAR =
+ Paths.get(ToolHelper.EXAMPLES_JAVA11_JAR_DIR).resolve("backport" + JAR_EXTENSION);
+
+ public StringBackportJava11Test(TestParameters parameters) {
+ super(parameters, String.class, TEST_JAR, "backport.StringBackportJava11Main");
+ // Note: None of the methods in this test exist in the latest android.jar. If/when they ship in
+ // an actual API level, migrate these tests to CharacterBackportTest.
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
index 566f6d6..476e321 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/StringMethods.java
@@ -35,4 +35,22 @@
}
return builder.toString();
}
+
+ public static String repeat(String receiver, int count) {
+ if (count < 0) {
+ throw new IllegalArgumentException("count is negative: " + count);
+ }
+ int length = receiver.length();
+ if (count == 0 || length == 0) {
+ return "";
+ }
+ if (count == 1) {
+ return receiver;
+ }
+ StringBuilder builder = new StringBuilder(length * count);
+ for (int i = 0; i < count; i++) {
+ builder.append(receiver);
+ }
+ return builder.toString();
+ }
}