Regression test for invalid optimization in D8
Bug: b/316744331
Change-Id: I3c5ea85c6a8aa32627d4c464a8b3ff475dc85cf5
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 e043042..867d2a7 100644
--- a/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
+++ b/src/test/java/com/android/tools/r8/debug/DebugTestBase.java
@@ -1572,6 +1572,14 @@
return internalInstanceField(getMirror(), thisObjectId, fieldId);
}
+ public void setFieldOnThis(String fieldName, String fieldSignature, Value value) {
+ long thisObjectId = getMirror().getThisObject(getThreadId(), getFrameId());
+ long classId = getMirror().getReferenceType(thisObjectId);
+ // TODO(zerny): Search supers too. This will only get the field if directly on the class.
+ long fieldId = findField(getMirror(), classId, fieldName, fieldSignature);
+ internalSetInstanceField(getMirror(), thisObjectId, fieldId, value);
+ }
+
private long findField(VmMirror mirror, long classId, String fieldName,
String fieldSignature) {
@@ -1633,6 +1641,19 @@
}
}
+ private static void internalSetInstanceField(
+ VmMirror mirror, long objectId, long fieldId, Value value) {
+ CommandPacket commandPacket =
+ new CommandPacket(
+ ObjectReferenceCommandSet.CommandSetID, ObjectReferenceCommandSet.SetValuesCommand);
+ commandPacket.setNextValueAsObjectID(objectId);
+ commandPacket.setNextValueAsInt(1); // field count.
+ commandPacket.setNextValueAsFieldID(fieldId);
+ commandPacket.setNextValueAsUntaggedValue(value);
+ ReplyPacket replyPacket = mirror.performCommand(commandPacket);
+ assert replyPacket.getErrorCode() == Error.NONE : "Error code: " + replyPacket.getErrorCode();
+ }
+
private static Value internalInstanceField(VmMirror mirror, long objectId, long fieldId) {
CommandPacket commandPacket =
new CommandPacket(
diff --git a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
new file mode 100644
index 0000000..bf6229b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331Test.java
@@ -0,0 +1,112 @@
+// Copyright (c) 2023, 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.regress.b316744331;
+
+import static junit.framework.TestCase.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.debug.DebugTestBase;
+import com.android.tools.r8.references.MethodReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.StringUtils;
+import org.apache.harmony.jpda.tests.framework.jdwp.JDWPConstants.Tag;
+import org.apache.harmony.jpda.tests.framework.jdwp.Value;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class Regress316744331Test extends DebugTestBase {
+
+ static final String EXPECTED = StringUtils.lines("No null fields");
+
+ static final int ENTRY_LINE = 22;
+ static final int IN_STREAM_CHECK_LINE = 23;
+ static final int IN_STREAM_IS_NULL_LINE = 24;
+ static final int OUT_STREAM_CHECK_LINE = 29;
+ static final int OUT_STREAM_IS_NULL_LINE = 30;
+ static final int NORMAL_EXIT_LINE = 33;
+
+ static final Value NULL_VALUE = Value.createObjectValue(Tag.OBJECT_TAG, 0);
+
+ private final TestParameters parameters;
+ private final MethodReference fooMethod;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters()
+ .withDefaultCfRuntime()
+ .withDefaultDexRuntime()
+ .withAllApiLevels()
+ .build();
+ }
+
+ public Regress316744331Test(TestParameters parameters) {
+ this.parameters = parameters;
+ try {
+ this.fooMethod = Reference.methodFromMethod(Regress316744331TestClass.class.getMethod("foo"));
+ } catch (NoSuchMethodException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private TestBuilder<? extends SingleTestRunResult<?>, ?> getTestBuilder() {
+ return testForRuntime(parameters)
+ .addClasspathClasses(Regress316744331TestClass.class)
+ .addProgramClasses(Regress316744331TestClass.class);
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ getTestBuilder()
+ .run(parameters.getRuntime(), Regress316744331TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testModifyInStreamField() throws Throwable {
+ runDebugTest(
+ getTestBuilder().debugConfig(parameters.getRuntime()),
+ Regress316744331TestClass.class,
+ breakpoint(fooMethod, ENTRY_LINE),
+ run(),
+ checkLine(ENTRY_LINE),
+ inspect(t -> assertEquals(NULL_VALUE, t.getFieldOnThis("m_instream", null))),
+ stepOver(),
+ checkLine(IN_STREAM_CHECK_LINE),
+ inspect(t -> assertNotEquals(NULL_VALUE, t.getFieldOnThis("m_instream", null))),
+ inspect(t -> t.setFieldOnThis("m_instream", null, NULL_VALUE)),
+ // Install a break point on the possible exits.
+ breakpoint(fooMethod, IN_STREAM_IS_NULL_LINE),
+ breakpoint(fooMethod, NORMAL_EXIT_LINE),
+ run(),
+ // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
+ checkLine(parameters.isCfRuntime() ? IN_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+ run());
+ }
+
+ @Test
+ public void testModifyOutStreamField() throws Throwable {
+ runDebugTest(
+ getTestBuilder().debugConfig(parameters.getRuntime()),
+ Regress316744331TestClass.class,
+ breakpoint(fooMethod, OUT_STREAM_CHECK_LINE),
+ run(),
+ checkLine(OUT_STREAM_CHECK_LINE),
+ inspect(t -> assertNotEquals(NULL_VALUE, t.getFieldOnThis("m_outstream", null))),
+ inspect(t -> t.setFieldOnThis("m_outstream", null, NULL_VALUE)),
+ // Install a break point on the possible exits.
+ breakpoint(fooMethod, OUT_STREAM_IS_NULL_LINE),
+ breakpoint(fooMethod, NORMAL_EXIT_LINE),
+ run(),
+ // TODO(b/316744331): D8 incorrectly optimizing out the code after the null check.
+ checkLine(parameters.isCfRuntime() ? OUT_STREAM_IS_NULL_LINE : NORMAL_EXIT_LINE),
+ run());
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java
new file mode 100644
index 0000000..e7d2583
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/regress/b316744331/Regress316744331TestClass.java
@@ -0,0 +1,48 @@
+// Copyright (c) 2023, 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.regress.b316744331;
+
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.PrintWriter;
+
+public class Regress316744331TestClass {
+
+ String filename;
+ FileReader m_instream;
+ PrintWriter m_outstream;
+
+ Regress316744331TestClass(String filename) {
+ this.filename = filename;
+ }
+
+ public void foo() throws IOException {
+ m_instream = new FileReader(filename);
+ if (null == m_instream) {
+ System.out.println("Reader is null!");
+ return;
+ }
+ m_outstream =
+ new PrintWriter(new java.io.BufferedWriter(new java.io.FileWriter(filename + ".java")));
+ if (null == m_outstream) {
+ System.out.println("Writer is null!");
+ return;
+ }
+ System.out.println("No null fields");
+ }
+
+ public static void main(String[] args) throws IOException {
+ // The debugger testing infra does not allow passing runtime arguments.
+ // Classpath should have an entry in normal and debugger runs so use it as the "file".
+ String cp = System.getProperty("java.class.path");
+ int jarIndex = cp.indexOf(".jar");
+ if (jarIndex < 0) {
+ jarIndex = cp.indexOf(".zip");
+ }
+ int start = cp.lastIndexOf(':', jarIndex);
+ String filename = cp.substring(Math.max(start, 0), jarIndex + 4);
+ new Regress316744331TestClass(filename).foo();
+ }
+}