Support '+' prefix on backported number parse methods on Dalvik

On Dalvik check for prefixed '+' before forwarding to corelib
implementation.

Bug: 182137865
Change-Id: Ibd08465a3e79b834c4acf6e7b7ac18dbb81c6389
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 5cd32f2..2fc5c3a 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
@@ -1059,10 +1059,15 @@
               factory.intType);
       DexMethod method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method,
-              BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
-              "parseIntSubsequenceWithRadix"));
+          appView.options().canParseNumbersWithPlusPrefix()
+              ? new MethodGenerator(
+                  method,
+                  BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadix,
+                  "parseIntSubsequenceWithRadix")
+              : new MethodGenerator(
+                  method,
+                  BackportedMethods::IntegerMethods_parseIntSubsequenceWithRadixDalvik,
+                  "parseIntSubsequenceWithRadix"));
 
       // Long
       type = factory.boxedLongType;
@@ -1077,10 +1082,15 @@
               factory.intType);
       method = factory.createMethod(type, proto, name);
       addProvider(
-          new MethodGenerator(
-              method,
-              BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
-              "parseLongSubsequenceWithRadix"));
+          appView.options().canParseNumbersWithPlusPrefix()
+              ? new MethodGenerator(
+                  method,
+                  BackportedMethods::LongMethods_parseLongSubsequenceWithRadix,
+                  "parseLongSubsequenceWithRadix")
+              : new MethodGenerator(
+                  method,
+                  BackportedMethods::LongMethods_parseLongSubsequenceWithRadixDalvik,
+                  "parseLongSubsequenceWithRadix"));
 
       // long Long.parseUnsignedLong(CharSequence s, int beginIndex, int endIndex, int radix)
       name = factory.createString("parseUnsignedLong");
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 b7b0db0..3f7a705 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
@@ -2162,6 +2162,113 @@
         ImmutableList.of());
   }
 
+  public static CfCode IntegerMethods_parseIntSubsequenceWithRadixDalvik(
+      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();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfConstNumber(2, ValueType.INT),
+            new CfIfCmp(If.Type.LT, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            label1,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfConstNumber(43, ValueType.INT),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.boxedCharType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.charType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("digit")),
+                false),
+            new CfIf(If.Type.LT, ValueType.INT, label4),
+            label3,
+            new CfIinc(1, 1),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Integer;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseInt")),
+                false),
+            new CfReturn(ValueType.INT),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode IntegerMethods_parseUnsignedInt(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
@@ -2737,6 +2844,113 @@
         ImmutableList.of());
   }
 
+  public static CfCode LongMethods_parseLongSubsequenceWithRadixDalvik(
+      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();
+    return new CfCode(
+        method.holder,
+        3,
+        4,
+        ImmutableList.of(
+            label0,
+            new CfLoad(ValueType.INT, 2),
+            new CfLoad(ValueType.INT, 1),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Sub, NumericType.INT),
+            new CfConstNumber(2, ValueType.INT),
+            new CfIfCmp(If.Type.LT, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            label1,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfConstNumber(43, ValueType.INT),
+            new CfIfCmp(If.Type.NE, ValueType.INT, label4),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfConstNumber(1, ValueType.INT),
+            new CfArithmeticBinop(CfArithmeticBinop.Opcode.Add, NumericType.INT),
+            label2,
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charType, options.itemFactory.intType),
+                    options.itemFactory.createString("charAt")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.boxedCharType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.intType,
+                        options.itemFactory.charType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("digit")),
+                false),
+            new CfIf(If.Type.LT, ValueType.INT, label4),
+            label3,
+            new CfIinc(1, 1),
+            label4,
+            new CfFrame(
+                new Int2ReferenceAVLTreeMap<>(
+                    new int[] {0, 1, 2, 3},
+                    new FrameType[] {
+                      FrameType.initialized(options.itemFactory.charSequenceType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType),
+                      FrameType.initialized(options.itemFactory.intType)
+                    }),
+                new ArrayDeque<>(Arrays.asList())),
+            new CfLoad(ValueType.OBJECT, 0),
+            new CfLoad(ValueType.INT, 1),
+            new CfLoad(ValueType.INT, 2),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(
+                        options.itemFactory.charSequenceType,
+                        options.itemFactory.intType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("subSequence")),
+                true),
+            new CfInvoke(
+                185,
+                options.itemFactory.createMethod(
+                    options.itemFactory.charSequenceType,
+                    options.itemFactory.createProto(options.itemFactory.stringType),
+                    options.itemFactory.createString("toString")),
+                true),
+            new CfLoad(ValueType.INT, 3),
+            new CfInvoke(
+                184,
+                options.itemFactory.createMethod(
+                    options.itemFactory.createType("Ljava/lang/Long;"),
+                    options.itemFactory.createProto(
+                        options.itemFactory.longType,
+                        options.itemFactory.stringType,
+                        options.itemFactory.intType),
+                    options.itemFactory.createString("parseLong")),
+                false),
+            new CfReturn(ValueType.LONG),
+            label5),
+        ImmutableList.of(),
+        ImmutableList.of());
+  }
+
   public static CfCode LongMethods_parseUnsignedLong(InternalOptions options, DexMethod method) {
     CfLabel label0 = new CfLabel();
     CfLabel label1 = new CfLabel();
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index 54bbbcc..4d95e6b 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1909,4 +1909,12 @@
   public boolean canHaveSwitchMaxIntBug() {
     return isGeneratingDex() && minApiLevel < AndroidApiLevel.K.getLevel();
   }
+
+  // On Dalvik the methods Integer.parseInt and Long.parseLong does not support strings with a '+'
+  // prefix
+  //
+  // See b/182137865.
+  public boolean canParseNumbersWithPlusPrefix() {
+    return minApiLevel > AndroidApiLevel.K.getLevel();
+  }
 }
diff --git a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
index 30e9086..46ec057 100644
--- a/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
+++ b/src/test/examplesJava9/backport/IntegerBackportJava9Main.java
@@ -22,10 +22,10 @@
   };
 
   public static void main(String[] args) {
-    testParseIntegerSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+    testParseIntegerSubsequenceWithRadix();
   }
 
-  private static void testParseIntegerSubsequenceWithRadix(boolean supportsPlusPrefix) {
+  private static void testParseIntegerSubsequenceWithRadix() {
     for (int value : interestingValues) {
       for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
         for (String prefix : new String[] {"", "x", "xxx"}) {
@@ -34,7 +34,7 @@
             int start = prefix.length();
             int end = valueString.length() - postfix.length();
             assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
-            if (value > 0 && supportsPlusPrefix) {
+            if (value > 0) {
               valueString = prefix + '+' + Long.toString(value, radix) + postfix;
               end++;
               assertEquals(valueString, value, Integer.parseInt(valueString, start, end, radix));
@@ -45,24 +45,29 @@
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+      throw new AssertionError(Integer.parseInt("0", 0, 1, Character.MIN_RADIX - 1));
     } catch (IllegalArgumentException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+      throw new AssertionError(Integer.parseInt("0", 0, 1, Character.MAX_RADIX + 1));
     } catch (IllegalArgumentException expected) {
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+      throw new AssertionError(Integer.parseInt("", 0, 0, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+      throw new AssertionError(Integer.parseInt("-", 0, 1, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+      throw new AssertionError(Integer.parseInt("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    try {
+      throw new AssertionError(Integer.parseInt("+a", 0, 1, 10));
     } catch (NumberFormatException expected) {
     }
 
diff --git a/src/test/examplesJava9/backport/LongBackportJava9Main.java b/src/test/examplesJava9/backport/LongBackportJava9Main.java
index 1634a43..cd3e5e1 100644
--- a/src/test/examplesJava9/backport/LongBackportJava9Main.java
+++ b/src/test/examplesJava9/backport/LongBackportJava9Main.java
@@ -24,11 +24,11 @@
   };
 
   public static void main(String[] args) {
-    testParseLongSubsequenceWithRadix(args.length == 0 || !args[0].startsWith("4."));
+    testParseLongSubsequenceWithRadix();
     testParseUnsignedLongSubsequenceWithRadix();
   }
 
-  private static void testParseLongSubsequenceWithRadix(boolean supportsPlusPrefix) {
+  private static void testParseLongSubsequenceWithRadix() {
     for (long value : interestingValues) {
       for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
         for (String prefix : new String[] {"", "x", "xxx"}) {
@@ -37,7 +37,7 @@
             int start = prefix.length();
             int end = valueString.length() - postfix.length();
             assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
-            if (value > 0 && supportsPlusPrefix) {
+            if (value > 0) {
               valueString = prefix + "+" + Long.toString(value, radix) + postfix;
               end++;
               assertEquals(valueString, value, Long.parseLong(valueString, start, end, radix));
@@ -48,24 +48,29 @@
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MIN_RADIX - 1));
+      throw new AssertionError(Long.parseLong("0", 0, 1, Character.MIN_RADIX - 1));
     } catch (IllegalArgumentException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("0", 0, 1, Character.MAX_RADIX + 1));
+      throw new AssertionError(Long.parseLong("0", 0, 1, Character.MAX_RADIX + 1));
     } catch (IllegalArgumentException expected) {
     }
 
     try {
-      throw new AssertionError(Long.parseUnsignedLong("", 0, 0, 16));
+      throw new AssertionError(Long.parseLong("", 0, 0, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("-", 0, 1, 16));
+      throw new AssertionError(Long.parseLong("-", 0, 1, 16));
     } catch (NumberFormatException expected) {
     }
     try {
-      throw new AssertionError(Long.parseUnsignedLong("+", 0, 1, 16));
+      throw new AssertionError(Long.parseLong("+", 0, 1, 16));
+    } catch (NumberFormatException expected) {
+    }
+
+    try {
+      throw new AssertionError(Long.parseLong("+a", 0, 1, 10));
     } catch (NumberFormatException expected) {
     }
 
@@ -122,6 +127,11 @@
     } catch (NumberFormatException expected) {
     }
 
+    try {
+      throw new AssertionError(Long.parseUnsignedLong("+a", 0, 1, 10));
+    } catch (NumberFormatException expected) {
+    }
+
     BigInteger overflow = new BigInteger("18446744073709551616");
     for (int radix = Character.MIN_RADIX; radix <= Character.MAX_RADIX; radix++) {
       for (String prefix : new String[] {"", "x", "xxx", "+", "x+", "xxx+"}) {
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 784e875..646dbf9 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
@@ -102,10 +102,7 @@
           .apply(this::configureProgram)
           .setIncludeClassesChecksum(true)
           .compile()
-          .run(
-              parameters.getRuntime(),
-              testClassName,
-              parameters.getRuntime().asDex().getVm().getVersion().toString())
+          .run(parameters.getRuntime(), testClassName)
           .assertSuccess()
           .inspect(this::assertDesugaring);
     }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
index a3bd157..bc84108 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/IntegerMethods.java
@@ -62,4 +62,15 @@
       CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
     return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
   }
+
+  public static int parseIntSubsequenceWithRadixDalvik(
+      CharSequence s, int beginIndex, int endIndex, int radix) throws NumberFormatException {
+    // Dalvik (API level 19 and below) does not support a '+' prefix.
+    if (endIndex - beginIndex >= 2
+        && s.charAt(beginIndex) == '+'
+        && Character.digit(s.charAt(beginIndex + 1), radix) >= 0) {
+      beginIndex++;
+    }
+    return Integer.parseInt(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
 }
diff --git a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
index 65a60ef..089fa86 100644
--- a/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
+++ b/src/test/java/com/android/tools/r8/ir/desugar/backports/LongMethods.java
@@ -15,6 +15,17 @@
     return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
   }
 
+  public static long parseLongSubsequenceWithRadixDalvik(
+      CharSequence s, int beginIndex, int endIndex, int radix) {
+    // Dalvik (API level 19 and below) does not support a '+' prefix.
+    if (endIndex - beginIndex >= 2
+        && s.charAt(beginIndex) == '+'
+        && Character.digit(s.charAt(beginIndex + 1), radix) >= 0) {
+      beginIndex++;
+    }
+    return Long.parseLong(s.subSequence(beginIndex, endIndex).toString(), radix);
+  }
+
   public static long divideUnsigned(long dividend, long divisor) {
     // This implementation is adapted from Guava's UnsignedLongs.java and Longs.java.