Model Boolean.parseBoolean()
Fixes: 158646791
Change-Id: I90abf274406fb45dc1b88563341ba7e7d2e4ec35
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 e0beea9..8b2bd65 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -764,6 +764,8 @@
public final DexMethod booleanValue =
createMethod(boxedBooleanType, createProto(booleanType), "booleanValue");
+ public final DexMethod parseBoolean =
+ createMethod(boxedBooleanType, createProto(booleanType, stringType), "parseBoolean");
public final DexMethod valueOf =
createMethod(boxedBooleanType, createProto(boxedBooleanType, booleanType), "valueOf");
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
index a560844..dcb128a 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/library/BooleanMethodOptimizer.java
@@ -10,6 +10,7 @@
import com.android.tools.r8.graph.DexItemFactory;
import com.android.tools.r8.graph.DexType;
import com.android.tools.r8.ir.analysis.value.AbstractValue;
+import com.android.tools.r8.ir.code.ConstString;
import com.android.tools.r8.ir.code.IRCode;
import com.android.tools.r8.ir.code.Instruction;
import com.android.tools.r8.ir.code.InstructionListIterator;
@@ -42,6 +43,8 @@
Set<Value> affectedValues) {
if (singleTarget.method == dexItemFactory.booleanMembers.booleanValue) {
optimizeBooleanValue(code, instructionIterator, invoke);
+ } else if (singleTarget.method == dexItemFactory.booleanMembers.parseBoolean) {
+ optimizeParseBoolean(code, instructionIterator, invoke);
} else if (singleTarget.method == dexItemFactory.booleanMembers.valueOf) {
optimizeValueOf(code, instructionIterator, invoke, affectedValues);
}
@@ -64,6 +67,25 @@
}
}
+ private void optimizeParseBoolean(
+ IRCode code, InstructionListIterator instructionIterator, InvokeMethod invoke) {
+ Value argument = invoke.arguments().get(0).getAliasedValue();
+ if (!argument.isPhi()) {
+ Instruction definition = argument.definition;
+ if (definition.isConstString()) {
+ ConstString constString = definition.asConstString();
+ if (!constString.instructionInstanceCanThrow()) {
+ String value = constString.getValue().toString().toLowerCase();
+ if (value.equals("true")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 1);
+ } else if (value.equals("false")) {
+ instructionIterator.replaceCurrentInstructionWithConstInt(code, 0);
+ }
+ }
+ }
+ }
+ }
+
private void optimizeValueOf(
IRCode code,
InstructionListIterator instructionIterator,
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
new file mode 100644
index 0000000..24894bb
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/library/BooleanParseBooleanTest.java
@@ -0,0 +1,121 @@
+// Copyright (c) 2020, 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.optimize.library;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexMethod;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+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 org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class BooleanParseBooleanTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public BooleanParseBooleanTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8()
+ .addProgramClasses(TestClass.class)
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(TestClass.class)
+ .addKeepMainRule(TestClass.class)
+ .enableInliningAnnotations()
+ .setMinApi(parameters.getApiLevel())
+ .compile()
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutputLines("true", "true", "true", "false", "false", "false", "true");
+ }
+
+ private void inspect(CodeInspector inspector) {
+ ClassSubject testClassSubject = inspector.clazz(TestClass.class);
+ assertThat(testClassSubject, isPresent());
+
+ MethodSubject testOptimizedMethodSubject =
+ testClassSubject.uniqueMethodWithName("testOptimized");
+ assertThat(testOptimizedMethodSubject, isPresent());
+ assertTrue(
+ testOptimizedMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeStatic)
+ .map(InstructionSubject::getMethod)
+ .map(DexMethod::toSourceString)
+ .noneMatch(method -> method.contains("parseBoolean")));
+
+ MethodSubject testNotOptimizedMethodSubject =
+ testClassSubject.uniqueMethodWithName("testNotOptimized");
+ assertThat(testNotOptimizedMethodSubject, isPresent());
+ assertEquals(
+ 1,
+ testNotOptimizedMethodSubject
+ .streamInstructions()
+ .filter(InstructionSubject::isInvokeStatic)
+ .map(InstructionSubject::getMethod)
+ .map(DexMethod::toSourceString)
+ .filter(method -> method.contains("parseBoolean"))
+ .count());
+ }
+
+ static class TestClass {
+
+ public static void main(String[] args) {
+ testOptimized();
+ testNotOptimized();
+ }
+
+ @NeverInline
+ static void testOptimized() {
+ System.out.println(Boolean.parseBoolean("true"));
+ System.out.println(Boolean.parseBoolean("tRuE"));
+ System.out.println(Boolean.parseBoolean("TRUE"));
+ System.out.println(Boolean.parseBoolean("false"));
+ System.out.println(Boolean.parseBoolean("fAlSe"));
+ System.out.println(Boolean.parseBoolean("FALSE"));
+ }
+
+ @NeverInline
+ static void testNotOptimized() {
+ System.out.println(Boolean.parseBoolean(unknown()));
+ }
+
+ static String unknown() {
+ return System.currentTimeMillis() >= 0 ? "true" : "false";
+ }
+ }
+}