Add reproduction of b/274337639
Bug: b/274337639
Change-Id: I7573883d6a649eed47b10d7bf0cdea2e11710268
diff --git a/build.gradle b/build.gradle
index 280db74..9ceab6e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -30,6 +30,7 @@
ext {
androidSupportVersion = '25.4.0'
asmVersion = '9.4' // When updating update tools/asmifier.py, build.src and Toolhelper as well.
+ javassistVersion = '3.29.2-GA'
espressoVersion = '3.0.0'
fastutilVersion = '7.2.0'
guavaVersion = '30.1.1-jre'
@@ -269,6 +270,7 @@
testCompile group: 'org.ow2.asm', name: 'asm-analysis', version: asmVersion
testCompile group: 'org.ow2.asm', name: 'asm-util', version: asmVersion
testCompile group: 'it.unimi.dsi', name: 'fastutil', version: fastutilVersion
+ testCompile group: 'org.javassist', name: 'javassist', version: javassistVersion
examplesAndroidOCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
examplesAndroidPCompile group: 'org.ow2.asm', name: 'asm', version: asmVersion
diff --git a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
index a57c5e6..0d45c04 100644
--- a/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
+++ b/src/main/java/com/android/tools/r8/cf/code/frame/UninitializedNew.java
@@ -19,7 +19,7 @@
private final DexType type;
public UninitializedNew(CfLabel label, DexType type) {
- assert type.isClassType();
+ assert type == null || type.isClassType();
this.label = label;
this.type = type;
}
diff --git a/src/test/java/com/android/tools/r8/cf/frames/InitBeforeNewInInstructionStreamTest.java b/src/test/java/com/android/tools/r8/cf/frames/InitBeforeNewInInstructionStreamTest.java
new file mode 100644
index 0000000..65e0fb1
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/cf/frames/InitBeforeNewInInstructionStreamTest.java
@@ -0,0 +1,192 @@
+// 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.cf.frames;
+
+import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.fail;
+
+import com.android.tools.r8.CompilationFailedException;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import javassist.ByteArrayClassPath;
+import javassist.ClassPool;
+import javassist.CtClass;
+import javassist.bytecode.CodeAttribute;
+import javassist.bytecode.StackMapTable;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameter;
+import org.junit.runners.Parameterized.Parameters;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class InitBeforeNewInInstructionStreamTest extends TestBase implements Opcodes {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ public static final String MAIN_CLASS = "Test";
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testJvm() throws Exception {
+ parameters.assumeJvmTestParameters();
+ testForJvm(parameters)
+ .addProgramClassFileData(patchedDump())
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testD8() throws Exception {
+ parameters.assumeDexRuntime();
+ testForD8(parameters.getBackend())
+ .addProgramClassFileData(patchedDump())
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ parameters.assumeR8TestParameters();
+ assertThrows(
+ CompilationFailedException.class,
+ () ->
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(patchedDump())
+ .addKeepMainRule(MAIN_CLASS)
+ .setMinApi(parameters)
+ .run(parameters.getRuntime(), MAIN_CLASS)
+ .assertSuccessWithOutput(EXPECTED_OUTPUT));
+ }
+
+ // This is reproducing b/b274337639, where a new instruction is before the corresponding
+ // invokespecial of <init> in the instruction stream. The code is correct as control flow ensures
+ // new is called before init, and the stack map encodes this.
+ //
+ // Writing this code with ASM did not generate the correct stack map, so using javassist to patch
+ // it up.
+
+ public static byte[] patchedDump() throws Exception {
+ ClassPool classPool = new ClassPool();
+ classPool.insertClassPath(new ByteArrayClassPath(MAIN_CLASS, dump()));
+ CtClass clazz = classPool.get(MAIN_CLASS);
+ clazz.defrost();
+ CodeAttribute code =
+ (CodeAttribute) clazz.getClassFile().getMethod("main").getAttribute("Code");
+ StackMapTable stackMapTableAttribute = (StackMapTable) code.getAttribute("StackMapTable");
+ byte[] stackMapTable = stackMapTableAttribute.get();
+ // Uninitialized has type 8 in the stack map. See
+ // https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-4.html#jvms-4.7.4.
+ // The instruction index 0 generated by ASM is not correct, patch to index 12.
+ if (stackMapTable[15] == 8
+ && stackMapTable[16] == 0
+ && stackMapTable[17] == 0
+ && stackMapTable[18] == 8
+ && stackMapTable[19] == 0
+ && stackMapTable[20] == 0) {
+ stackMapTable[17] = 12;
+ stackMapTable[20] = 12;
+ } else {
+ // If an ASM update fails here maybe the stack map is generated correctly and the javassist
+ // patching can be removed.
+ fail("Unexpected class file*");
+ }
+ stackMapTableAttribute.set(stackMapTable);
+ return clazz.toBytecode();
+ }
+
+ public static byte[] dump() throws Exception {
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V1_8, ACC_FINAL | ACC_SUPER, MAIN_CLASS, null, "java/lang/Object", null);
+
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ Label labelNew = new Label();
+ Label labelInit = new Label();
+ Label labelAfterInit = new Label();
+
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitJumpInsn(GOTO, labelNew);
+
+ methodVisitor.visitLabel(labelInit);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 3,
+ new Object[] {"java/io/PrintStream", labelNew, labelNew});
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, MAIN_CLASS, "<init>", "()V", false);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 2,
+ new Object[] {"java/io/PrintStream", MAIN_CLASS});
+ methodVisitor.visitJumpInsn(GOTO, labelAfterInit);
+
+ methodVisitor.visitLabel(labelNew);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 1,
+ new Object[] {"java/io/PrintStream"});
+ methodVisitor.visitTypeInsn(NEW, MAIN_CLASS);
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitJumpInsn(GOTO, labelInit);
+
+ methodVisitor.visitLabel(labelAfterInit);
+ methodVisitor.visitFrame(
+ Opcodes.F_FULL,
+ 1,
+ new Object[] {"[Ljava/lang/String;"},
+ 2,
+ new Object[] {"java/io/PrintStream", MAIN_CLASS});
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/Object;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(3, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(ACC_PUBLIC, "toString", "()Ljava/lang/String;", null, null);
+ methodVisitor.visitCode();
+ methodVisitor.visitLdcInsn("Hello, world!");
+ methodVisitor.visitInsn(ARETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}