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();
+  }
+}