Add test for creating uninitialized instances of android.graphics.SurfaceTexture

R=christofferqa@google.com

Bug: b/441137561
Change-Id: I8a682b8691a8bb084d5d62f9067c122609e394ab
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningInSynchronizedMethodTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningInSynchronizedMethodTest.java
new file mode 100644
index 0000000..3d28108
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningInSynchronizedMethodTest.java
@@ -0,0 +1,154 @@
+// Copyright (c) 2025, 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.apimodel;
+
+import static org.junit.Assert.assertEquals;
+
+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.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+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;
+
+@RunWith(Parameterized.class)
+public class ApiModelExcludeClinitOutliningInSynchronizedMethodTest extends TestBase {
+
+  private static final String SURFACE_TEXTURE_TYPE_NAME = "android.graphics.SurfaceTexture";
+  private static final String SURFACE_TEXTURE_DESCRIPTOR =
+      DescriptorUtils.javaTypeToDescriptor(SURFACE_TEXTURE_TYPE_NAME);
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // Run tests with API level 21 to get <clinit> outlining (disabled below API 21).
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.L).build();
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("SurfaceTexture <clinit>", "DONE");
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters)
+        .addProgramClassFileData(getTestClass())
+        .compile()
+        .inspect(
+            inspector ->
+                assertEquals(
+                    2,
+                    inspector
+                        .clazz(TestClass.class)
+                        .uniqueMethodWithOriginalName("constructorArgumentInSynchronizedMethod")
+                        .streamInstructions()
+                        .filter(InstructionSubject::isInvokeStatic)
+                        .map(InstructionSubject::getMethod)
+                        .filter(
+                            dexMethod ->
+                                SyntheticItemsTestUtils.isExternalApiOutlineClass(
+                                    dexMethod.getHolderType().asClassReference()))
+                        .count()))
+        .addRunClasspathClassFileData(getSurfaceTexture())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters)
+        .addProgramClassFileData(getTestClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject constructorArgumentInSynchronizedMethod =
+                  inspector
+                      .clazz(TestClass.class)
+                      .uniqueMethodWithOriginalName("constructorArgumentInSynchronizedMethod");
+              assertEquals(
+                  1,
+                  constructorArgumentInSynchronizedMethod
+                      .streamInstructions()
+                      .filter(InstructionSubject::isInvokeStatic)
+                      .map(InstructionSubject::getMethod)
+                      .filter(
+                          dexMethod ->
+                              dexMethod
+                                  .getHolderType()
+                                  .getTypeName()
+                                  .equals(
+                                      inspector.getObfuscatedTypeName(
+                                          SyntheticItemsTestUtils.syntheticApiOutlineClass(
+                                                  TestClass.class, 0)
+                                              .getTypeName())))
+                      .count());
+              // As android.graphics.SurfaceTexture was introduced in API level 11 the <clinit>
+              // outline is inlined.
+              assertEquals(
+                  1,
+                  constructorArgumentInSynchronizedMethod
+                      .streamInstructions()
+                      .filter(InstructionSubject::isNewInstance)
+                      .filter(
+                          i ->
+                              i.asDexInstruction()
+                                  .getInstruction()
+                                  .asNewInstance()
+                                  .getType()
+                                  .getTypeName()
+                                  .equals("android.graphics.SurfaceTexture"))
+                      .count());
+            })
+        .addRunClasspathClassFileData(getSurfaceTexture())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private static byte[] getTestClass() throws IOException {
+    return transformer(TestClass.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(SurfaceTextureStub.class), SURFACE_TEXTURE_DESCRIPTOR)
+        .transform();
+  }
+
+  private static byte[] getSurfaceTexture() throws IOException {
+    return transformer(SurfaceTextureStub.class)
+        .setClassDescriptor(SURFACE_TEXTURE_DESCRIPTOR)
+        .transform();
+  }
+
+  // Transformed to android.graphics.SurfaceTexture
+  public static class SurfaceTextureStub {
+    static {
+      System.out.println("SurfaceTexture <clinit>");
+    }
+
+    public SurfaceTextureStub(boolean arg) {}
+  }
+
+  static class TestClass {
+    @NeverInline
+    private static synchronized void constructorArgumentInSynchronizedMethod() {
+      new SurfaceTextureStub(true);
+    }
+
+    public static void main(String[] args) {
+      constructorArgumentInSynchronizedMethod();
+      System.out.println("DONE");
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningWhereArgumentsCouldCauseSideEffectsTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningWhereArgumentsCouldCauseSideEffectsTest.java
new file mode 100644
index 0000000..1a76b6d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelExcludeClinitOutliningWhereArgumentsCouldCauseSideEffectsTest.java
@@ -0,0 +1,161 @@
+// Copyright (c) 2025, 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.apimodel;
+
+import static org.junit.Assert.assertEquals;
+
+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.synthesis.SyntheticItemsTestUtils;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import com.android.tools.r8.utils.StringUtils;
+import com.android.tools.r8.utils.codeinspector.InstructionSubject;
+import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.io.IOException;
+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;
+
+@RunWith(Parameterized.class)
+public class ApiModelExcludeClinitOutliningWhereArgumentsCouldCauseSideEffectsTest
+    extends TestBase {
+
+  private static final String SURFACE_TEXTURE_TYPE_NAME = "android.graphics.SurfaceTexture";
+  private static final String SURFACE_TEXTURE_DESCRIPTOR =
+      DescriptorUtils.javaTypeToDescriptor(SURFACE_TEXTURE_TYPE_NAME);
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    // Run tests with API level 21 to get <clinit> outlining (disabled below API 21).
+    return getTestParameters().withDexRuntimes().withApiLevel(AndroidApiLevel.L).build();
+  }
+
+  private static final String EXPECTED_OUTPUT =
+      StringUtils.lines("SurfaceTexture <clinit>", "getArgument", "DONE");
+
+  @Test
+  public void testD8() throws Exception {
+    testForD8(parameters)
+        .addProgramClassFileData(getTestClass())
+        .compile()
+        .inspect(
+            inspector ->
+                assertEquals(
+                    2,
+                    inspector
+                        .clazz(TestClass.class)
+                        .uniqueMethodWithOriginalName("constructorArgumentCouldCauseSideEffects")
+                        .streamInstructions()
+                        .filter(InstructionSubject::isInvokeStatic)
+                        .map(InstructionSubject::getMethod)
+                        .filter(
+                            dexMethod ->
+                                SyntheticItemsTestUtils.isExternalApiOutlineClass(
+                                    dexMethod.getHolderType().asClassReference()))
+                        .count()))
+        .addRunClasspathClassFileData(getSurfaceTexture())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    testForR8(parameters)
+        .addProgramClassFileData(getTestClass())
+        .addKeepMainRule(TestClass.class)
+        .enableInliningAnnotations()
+        .compile()
+        .inspect(
+            inspector -> {
+              MethodSubject constructorArgumentCouldCauseSideEffects =
+                  inspector
+                      .clazz(TestClass.class)
+                      .uniqueMethodWithOriginalName("constructorArgumentCouldCauseSideEffects");
+              assertEquals(
+                  1,
+                  constructorArgumentCouldCauseSideEffects
+                      .streamInstructions()
+                      .filter(InstructionSubject::isInvokeStatic)
+                      .map(InstructionSubject::getMethod)
+                      .filter(
+                          dexMethod ->
+                              dexMethod
+                                  .getHolderType()
+                                  .getTypeName()
+                                  .equals(
+                                      inspector.getObfuscatedTypeName(
+                                          SyntheticItemsTestUtils.syntheticApiOutlineClass(
+                                                  TestClass.class, 0)
+                                              .getTypeName())))
+                      .count());
+              // As android.graphics.SurfaceTexture was introduced in API level 11 the <clinit>
+              // outline is inlined.
+              assertEquals(
+                  1,
+                  constructorArgumentCouldCauseSideEffects
+                      .streamInstructions()
+                      .filter(InstructionSubject::isNewInstance)
+                      .filter(
+                          i ->
+                              i.asDexInstruction()
+                                  .getInstruction()
+                                  .asNewInstance()
+                                  .getType()
+                                  .getTypeName()
+                                  .equals("android.graphics.SurfaceTexture"))
+                      .count());
+            })
+        .addRunClasspathClassFileData(getSurfaceTexture())
+        .run(parameters.getRuntime(), TestClass.class)
+        .assertSuccessWithOutput(EXPECTED_OUTPUT);
+  }
+
+  private static byte[] getTestClass() throws IOException {
+    return transformer(TestClass.class)
+        .replaceClassDescriptorInMethodInstructions(
+            descriptor(SurfaceTextureStub.class), SURFACE_TEXTURE_DESCRIPTOR)
+        .transform();
+  }
+
+  private static byte[] getSurfaceTexture() throws IOException {
+    return transformer(SurfaceTextureStub.class)
+        .setClassDescriptor(SURFACE_TEXTURE_DESCRIPTOR)
+        .transform();
+  }
+
+  // Transformed to android.graphics.SurfaceTexture
+  public static class SurfaceTextureStub {
+    static {
+      System.out.println("SurfaceTexture <clinit>");
+    }
+
+    public SurfaceTextureStub(boolean arg) {}
+  }
+
+  static class TestClass {
+    @NeverInline
+    private static boolean getArgument() {
+      System.out.println("getArgument");
+      return System.currentTimeMillis() > 0;
+    }
+
+    @NeverInline
+    private static void constructorArgumentCouldCauseSideEffects() {
+      new SurfaceTextureStub(getArgument());
+    }
+
+    public static void main(String[] args) {
+      constructorArgumentCouldCauseSideEffects();
+      System.out.println("DONE");
+    }
+  }
+}
diff --git a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
index 753027f..7ce29d2 100644
--- a/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
+++ b/src/test/testbase/java/com/android/tools/r8/synthesis/SyntheticItemsTestUtils.java
@@ -338,6 +338,10 @@
     return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.OUTLINE);
   }
 
+  public static boolean isExternalApiOutlineClass(ClassReference reference) {
+    return SyntheticNaming.isSynthetic(reference, Phase.EXTERNAL, naming.API_MODEL_OUTLINE);
+  }
+
   public static boolean isInitializerTypeArgument(ClassReference reference) {
     return SyntheticNaming.isSynthetic(reference, null, naming.INIT_TYPE_ARGUMENT);
   }