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.