Merge "Remove end-marker before removing chained check-cast."
diff --git a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
index 30244bf..02c06da 100644
--- a/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
+++ b/src/main/java/com/android/tools/r8/ir/optimize/CodeRewriter.java
@@ -1651,7 +1651,7 @@
&& outValue.isUsed()
&& outValue.numberOfPhiUsers() == 0
&& outValue.uniqueUsers().stream().allMatch(isCheckcastToSubtype)) {
- removeOrReplaceByDebugLocalWrite(it, inValue, outValue);
+ removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
continue;
}
@@ -1671,7 +1671,7 @@
if (TypeLatticeElement.lessThanOrEqual(appInfo, inTypeLattice, castTypeLattice)) {
assert outTypeLattice.equals(inTypeLattice);
needToRemoveTrivialPhis = needToRemoveTrivialPhis || outValue.numberOfPhiUsers() != 0;
- removeOrReplaceByDebugLocalWrite(it, inValue, outValue);
+ removeOrReplaceByDebugLocalWrite(checkCast, it, inValue, outValue);
continue;
}
// Otherwise, keep the checkcast to preserve verification errors. E.g., down-cast:
@@ -1691,16 +1691,20 @@
if (needToRemoveTrivialPhis) {
code.removeAllTrivialPhis();
}
- it = code.instructionIterator();
assert code.isConsistentSSA();
}
private void removeOrReplaceByDebugLocalWrite(
- InstructionIterator it, Value inValue, Value outValue) {
- if (outValue.getLocalInfo() != inValue.getLocalInfo() && outValue.hasLocalInfo()) {
+ Instruction currentInstruction, InstructionIterator it, Value inValue, Value outValue) {
+ if (outValue.hasLocalInfo() && outValue.getLocalInfo() != inValue.getLocalInfo()) {
DebugLocalWrite debugLocalWrite = new DebugLocalWrite(outValue, inValue);
it.replaceCurrentInstruction(debugLocalWrite);
} else {
+ if (outValue.hasLocalInfo()) {
+ assert outValue.getLocalInfo() == inValue.getLocalInfo();
+ // Should remove the end-marker before replacing the current instruction.
+ currentInstruction.removeDebugValue(outValue.getLocalInfo());
+ }
outValue.replaceUsers(inValue);
it.removeOrReplaceByDebugLocalRead();
}
diff --git a/src/test/java/com/android/tools/r8/TestBase.java b/src/test/java/com/android/tools/r8/TestBase.java
index 403ee70..fa35d0d 100644
--- a/src/test/java/com/android/tools/r8/TestBase.java
+++ b/src/test/java/com/android/tools/r8/TestBase.java
@@ -12,8 +12,11 @@
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
import com.android.tools.r8.ToolHelper.ProcessResult;
+import com.android.tools.r8.cf.code.CfInstruction;
import com.android.tools.r8.code.Instruction;
import com.android.tools.r8.errors.Unreachable;
+import com.android.tools.r8.graph.CfCode;
+import com.android.tools.r8.graph.Code;
import com.android.tools.r8.graph.DexCode;
import com.android.tools.r8.graph.DexEncodedMethod;
import com.android.tools.r8.graph.SmaliWriter;
@@ -827,6 +830,13 @@
.map(kind::cast);
}
+ protected Stream<CfInstruction> filterInstructionKind(
+ CfCode code, Class<? extends CfInstruction> kind) {
+ return code.getInstructions().stream()
+ .filter(kind::isInstance)
+ .map(kind::cast);
+ }
+
public enum MinifyMode {
NONE,
JAVA,
diff --git a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
index 99222d0..b6cdb48 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -3,6 +3,7 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.debug;
+import com.android.tools.r8.TestBase;
import com.android.tools.r8.ToolHelper;
import com.android.tools.r8.ToolHelper.ArtCommandBuilder;
import com.android.tools.r8.ToolHelper.DexVm;
@@ -79,7 +80,7 @@
* The protocol messages are described here:
* https://docs.oracle.com/javase/8/docs/platform/jpda/jdwp/jdwp-protocol.html
*/
-public abstract class DebugTestBase {
+public abstract class DebugTestBase extends TestBase {
// Set to true to enable verbose logs
private static final boolean DEBUG_TESTS = false;
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTest.java
new file mode 100644
index 0000000..6d6b4c7
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTest.java
@@ -0,0 +1,55 @@
+// Copyright (c) 2018, 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.checkcast;
+
+import com.android.tools.r8.NeverInline;
+
+class A {
+ @NeverInline
+ @Override
+ public String toString() {
+ return "A";
+ }
+}
+
+class B extends A {
+ @NeverInline
+ @Override
+ public String toString() {
+ return super.toString() + "B";
+ }
+}
+
+class C extends B {
+ @NeverInline
+ @Override
+ public String toString() {
+ return super.toString() + "C";
+ }
+}
+
+class CheckCastDebugTest {
+ @NeverInline
+ static void differentLocals() {
+ Object obj = new C();
+ A a = (A) obj;
+ B b = (B) a;
+ C c = (C) b;
+ System.out.println(c.toString());
+ }
+
+ @NeverInline
+ static void sameLocal() {
+ Object obj = new C();
+ obj = (A) obj;
+ obj = (B) obj;
+ obj = (C) obj;
+ System.out.println(obj.toString());
+ }
+
+ public static void main(String[] args) {
+ differentLocals();
+ sameLocal();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
new file mode 100644
index 0000000..44720ed
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastDebugTestRunner.java
@@ -0,0 +1,178 @@
+// Copyright (c) 2018, 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.checkcast;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+import com.android.tools.r8.ClassFileConsumer;
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.DexIndexedConsumer;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.R8Command;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.cf.code.CfCheckCast;
+import com.android.tools.r8.debug.CfDebugTestConfig;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.debug.DebugTestConfig;
+import com.android.tools.r8.debug.DexDebugTestConfig;
+import com.android.tools.r8.graph.Code;
+import com.android.tools.r8.code.CheckCast;
+import com.android.tools.r8.origin.Origin;
+import com.android.tools.r8.utils.AndroidApp;
+import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import com.google.common.collect.ImmutableList;
+import java.nio.file.Path;
+import java.util.Arrays;
+import java.util.Collection;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class CheckCastDebugTestRunner extends DebugTestBase {
+ private static final Class<?> MAIN = CheckCastDebugTest.class;
+ private final Backend backend;
+
+ private Path r8Out;
+ private CodeInspector inspector;
+
+ @Parameterized.Parameters(name = "Backend: {0}")
+ public static Collection<Backend> data() {
+ return Arrays.asList(Backend.values());
+ }
+
+ public CheckCastDebugTestRunner(Backend backend) {
+ this.backend = backend;
+ }
+
+ private static int testCounter = 0;
+
+ @Before
+ public void setUp() throws Exception {
+ AndroidApp app = readClasses(NeverInline.class, A.class, B.class, C.class, MAIN);
+ r8Out = temp.newFile(String.format("r8Out-%s-%d.zip", backend, testCounter++)).toPath();
+ R8Command.Builder builder = ToolHelper.prepareR8CommandBuilder(app);
+ if (backend == Backend.DEX) {
+ builder.setProgramConsumer(new DexIndexedConsumer.ArchiveConsumer(r8Out));
+ } else {
+ assert backend == Backend.CF;
+ builder.setProgramConsumer(new ClassFileConsumer.ArchiveConsumer(r8Out));
+ }
+ builder.addProguardConfiguration(
+ ImmutableList.of(
+ "-dontobfuscate",
+ keepMainProguardConfigurationWithInliningAnnotation(MAIN)),
+ Origin.unknown());
+ builder.setMode(CompilationMode.DEBUG);
+ ToolHelper.allowTestProguardOptions(builder);
+ ToolHelper.runR8(builder.build(), o -> o.enableVerticalClassMerging = false);
+ inspector = new CodeInspector(r8Out);
+ ClassSubject classSubject = inspector.clazz(MAIN);
+ assertThat(classSubject, isPresent());
+ }
+
+ @Test
+ public void test_differentLocals() throws Throwable {
+ ClassSubject classSubject = inspector.clazz(MAIN);
+ MethodSubject method = classSubject.method("void", "differentLocals", ImmutableList.of());
+ assertThat(method, isPresent());
+ long count = countCheckCast(method.getMethod().getCode());
+ assertEquals(1, count);
+
+ DebugTestConfig config = backend == Backend.CF
+ ? new CfDebugTestConfig()
+ : new DexDebugTestConfig();
+ config.addPaths(r8Out);
+ runDebugTest(config, MAIN.getCanonicalName(),
+ // Object obj = new C();
+ breakpoint(MAIN.getCanonicalName(), "differentLocals", "()V", 35),
+ run(),
+ checkNoLocal("obj"),
+ checkNoLocal("a"),
+ checkNoLocal("b"),
+ checkNoLocal("c"),
+ // A a = (A) obj;
+ breakpoint(MAIN.getCanonicalName(), "differentLocals", "()V", 36),
+ run(),
+ checkLocal("obj"),
+ checkNoLocal("a"),
+ checkNoLocal("b"),
+ checkNoLocal("c"),
+ // B b = (B) a;
+ breakpoint(MAIN.getCanonicalName(), "differentLocals", "()V", 37),
+ run(),
+ checkLocal("obj"),
+ checkLocal("a"),
+ checkNoLocal("b"),
+ checkNoLocal("c"),
+ // C c = (C) b;
+ breakpoint(MAIN.getCanonicalName(), "differentLocals", "()V", 38),
+ run(),
+ checkLocal("obj"),
+ checkLocal("a"),
+ checkLocal("b"),
+ checkNoLocal("c"),
+ // System.out.println(c.toString());
+ breakpoint(MAIN.getCanonicalName(), "differentLocals", "()V", 39),
+ run(),
+ checkLocal("obj"),
+ checkLocal("a"),
+ checkLocal("b"),
+ checkLocal("c"),
+ run()
+ );
+ }
+
+ @Test
+ public void test_sameLocal() throws Throwable {
+ ClassSubject classSubject = inspector.clazz(MAIN);
+ MethodSubject method = classSubject.method("void", "sameLocal", ImmutableList.of());
+ assertThat(method, isPresent());
+ long count = countCheckCast(method.getMethod().getCode());
+ assertEquals(1, count);
+
+ DebugTestConfig config = backend == Backend.CF
+ ? new CfDebugTestConfig()
+ : new DexDebugTestConfig();
+ config.addPaths(r8Out);
+ runDebugTest(config, MAIN.getCanonicalName(),
+ // Object obj = new C();
+ breakpoint(MAIN.getCanonicalName(), "sameLocal", "()V", 44),
+ run(),
+ checkNoLocal("obj"),
+ // obj = (A) obj;
+ breakpoint(MAIN.getCanonicalName(), "sameLocal", "()V", 45),
+ run(),
+ checkLocal("obj"),
+ // obj = (B) obj;
+ breakpoint(MAIN.getCanonicalName(), "sameLocal", "()V", 46),
+ run(),
+ checkLocal("obj"),
+ // obj = (C) obj;
+ breakpoint(MAIN.getCanonicalName(), "sameLocal", "()V", 47),
+ run(),
+ checkLocal("obj"),
+ // System.out.println(obj.toString());
+ breakpoint(MAIN.getCanonicalName(), "sameLocal", "()V", 48),
+ run(),
+ checkLocal("obj"),
+ run()
+ );
+ }
+
+ private long countCheckCast(Code code) {
+ if (backend == Backend.DEX) {
+ return filterInstructionKind(code.asDexCode(), CheckCast.class).count();
+ } else {
+ assert backend == Backend.CF;
+ return filterInstructionKind(code.asCfCode(), CfCheckCast.class).count();
+ }
+ }
+
+}
diff --git a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
index b141955..c953515 100644
--- a/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
+++ b/src/test/java/com/android/tools/r8/ir/optimize/checkcast/CheckCastRemovalTest.java
@@ -99,7 +99,7 @@
}
@Test
- public void downCasts() throws Exception {
+ public void downCasts_noLocal() throws Exception {
JasminBuilder builder = new JasminBuilder();
// C < B < A
ClassBuilder a = builder.addClass("A");
@@ -133,6 +133,91 @@
}
@Test
+ public void downCasts_differentLocals() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ // C < B < A
+ ClassBuilder a = builder.addClass("A");
+ a.addDefaultConstructor();
+ ClassBuilder b = builder.addClass("B", "A");
+ b.addDefaultConstructor();
+ ClassBuilder c = builder.addClass("C", "B");
+ c.addDefaultConstructor();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ MethodSignature main = classBuilder.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 3",
+ ".var 0 is a LA; from Label1 to Label2",
+ ".var 1 is b LB; from Label1 to Label2",
+ ".var 2 is c LC; from Label1 to Label2",
+ "Label1:",
+ "new A",
+ "dup",
+ "invokespecial A/<init>()V",
+ "astore_0",
+ "aload_0",
+ "checkcast B", // Gone
+ "astore_1",
+ "aload_1",
+ "checkcast C", // Should be kept to preserve cast exception
+ "astore_2",
+ "Label2:",
+ "return");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep class " + CLASS_NAME + " { *; }",
+ "-keep class A { *; }",
+ "-keep class B { *; }",
+ "-keep class C { *; }",
+ "-dontoptimize",
+ "-dontshrink");
+ AndroidApp app = compileWithR8InDebugMode(builder, pgConfigs, null, backend);
+
+ checkCheckCasts(app, main, "C");
+ checkRuntimeException(builder, app, CLASS_NAME, "ClassCastException");
+ }
+
+ @Test
+ public void downCasts_sameLocal() throws Exception {
+ JasminBuilder builder = new JasminBuilder();
+ // C < B < A
+ ClassBuilder a = builder.addClass("A");
+ a.addDefaultConstructor();
+ ClassBuilder b = builder.addClass("B", "A");
+ b.addDefaultConstructor();
+ ClassBuilder c = builder.addClass("C", "B");
+ c.addDefaultConstructor();
+ ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
+ MethodSignature main = classBuilder.addMainMethod(
+ ".limit stack 3",
+ ".limit locals 1",
+ ".var 0 is a LA; from Label1 to Label2",
+ "Label1:",
+ "new A",
+ "dup",
+ "invokespecial A/<init>()V",
+ "astore_0",
+ "aload_0",
+ "checkcast B", // Gone
+ "astore_0",
+ "aload_0",
+ "checkcast C", // Should be kept to preserve cast exception
+ "Label2:",
+ "return");
+
+ List<String> pgConfigs = ImmutableList.of(
+ "-keep class " + CLASS_NAME + " { *; }",
+ "-keep class A { *; }",
+ "-keep class B { *; }",
+ "-keep class C { *; }",
+ "-dontoptimize",
+ "-dontshrink");
+ AndroidApp app = compileWithR8InDebugMode(builder, pgConfigs, null, backend);
+
+ checkCheckCasts(app, main, "C");
+ checkRuntimeException(builder, app, CLASS_NAME, "ClassCastException");
+ }
+
+ @Test
public void bothUpAndDowncast() throws Exception {
JasminBuilder builder = new JasminBuilder();
ClassBuilder classBuilder = builder.addClass(CLASS_NAME);
@@ -180,7 +265,7 @@
"-dontshrink");
AndroidApp app = compileWithR8(builder, pgConfigs, null, backend);
- checkCheckCasts(app, main, "Example");
+ checkCheckCasts(app, main, null);
checkRuntimeException(builder, app, CLASS_NAME, "NullPointerException");
}
@@ -201,6 +286,7 @@
assertTrue(!found && instruction.isCheckCast(maybeType));
found = true;
}
+ assertTrue(found);
}
private void checkRuntime(JasminBuilder builder, AndroidApp app, String className)
diff --git a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
index f106793..cfc49fe 100644
--- a/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
+++ b/src/test/java/com/android/tools/r8/jasmin/JasminTestBase.java
@@ -5,6 +5,7 @@
import static org.junit.Assert.fail;
+import com.android.tools.r8.CompilationMode;
import com.android.tools.r8.OutputMode;
import com.android.tools.r8.R8Command;
import com.android.tools.r8.TestBase;
@@ -106,6 +107,21 @@
return ToolHelper.runR8(builder.build(), optionsConsumer);
}
+ protected AndroidApp compileWithR8InDebugMode(
+ JasminBuilder builder,
+ List<String> proguardConfigs,
+ Consumer<InternalOptions> optionsConsumer,
+ Backend backend)
+ throws Exception {
+ R8Command command =
+ ToolHelper.prepareR8CommandBuilder(builder.build(), emptyConsumer(backend))
+ .addLibraryFiles(runtimeJar(backend))
+ .addProguardConfiguration(proguardConfigs, Origin.unknown())
+ .setMode(CompilationMode.DEBUG)
+ .build();
+ return ToolHelper.runR8(command, optionsConsumer);
+ }
+
protected AndroidApp compileWithR8(
JasminBuilder builder,
List<String> proguardConfigs,