[ApiModel] Add test for stubbing existing android exception

Change-Id: If1230a025bceae4ef3166f30cfb5cf5b641c89bb
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java
new file mode 100644
index 0000000..5e0516a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockExceptionAndroidApiTest.java
@@ -0,0 +1,195 @@
+// Copyright (c) 2022, 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.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.OutputMode;
+import com.android.tools.r8.R8TestCompileResult;
+import com.android.tools.r8.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompilerBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.ToolHelper;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.synthesis.globals.GlobalSyntheticsTestingConsumer;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.DescriptorUtils;
+import java.nio.file.Path;
+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 ApiModelMockExceptionAndroidApiTest extends TestBase {
+
+  private final AndroidApiLevel apiLevelForIllFormedLocaleException = AndroidApiLevel.P;
+  private final String illFormedLocaleException = "android.icu.util.IllformedLocaleException";
+
+  @Parameter public TestParameters parameters;
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withDexRuntimesAndAllApiLevels().build();
+  }
+
+  private boolean isGreaterOrEqualToExceptionLevel() {
+    return parameters.getApiLevel().isGreaterThanOrEqualTo(apiLevelForIllFormedLocaleException);
+  }
+
+  private void setupTestCompileBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder)
+      throws Exception {
+    testBuilder
+        .addProgramClassFileData(
+            transformer(Main.class)
+                .transformTryCatchBlock(
+                    "main",
+                    (start, end, handler, type, visitor) -> {
+                      if (type.equals(binaryName(LibraryExceptionPlaceHolder.class))) {
+                        visitor.visitTryCatchBlock(
+                            start,
+                            end,
+                            handler,
+                            DescriptorUtils.getBinaryNameFromJavaType(illFormedLocaleException));
+                      } else {
+                        visitor.visitTryCatchBlock(start, end, handler, type);
+                      }
+                    })
+                .replaceClassDescriptorInMethodInstructions(
+                    descriptor(LibraryExceptionPlaceHolder.class),
+                    DescriptorUtils.javaTypeToDescriptor(illFormedLocaleException))
+                .transform())
+        .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST))
+        .setMinApi(parameters.getApiLevel())
+        .apply(ApiModelingTestHelper::enableStubbingOfClasses);
+  }
+
+  private void setupTestRuntimeBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
+    testBuilder.setMinApi(parameters.getApiLevel()).addAndroidBuildVersion();
+  }
+
+  private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+    setupTestCompileBuilder(testBuilder);
+    setupTestRuntimeBuilder(testBuilder);
+  }
+
+  @Test
+  public void testD8Debug() throws Exception {
+    testForD8()
+        .setMode(CompilationMode.DEBUG)
+        .apply(this::setupTestBuilder)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8Release() throws Exception {
+    testForD8()
+        .setMode(CompilationMode.RELEASE)
+        .apply(this::setupTestBuilder)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testD8MergeIndexed() throws Exception {
+    testD8Merge(OutputMode.DexIndexed);
+  }
+
+  @Test
+  public void testD8MergeFilePerClass() throws Exception {
+    testD8Merge(OutputMode.DexFilePerClass);
+  }
+
+  @Test
+  public void testD8MergeFilePerClassFile() throws Exception {
+    testD8Merge(OutputMode.DexFilePerClassFile);
+  }
+
+  private void testD8Merge(OutputMode outputMode) throws Exception {
+    GlobalSyntheticsTestingConsumer globals = new GlobalSyntheticsTestingConsumer();
+    Path incrementalOut =
+        testForD8()
+            .debug()
+            .setOutputMode(outputMode)
+            .setIntermediate(true)
+            .apply(b -> b.getBuilder().setGlobalSyntheticsConsumer(globals))
+            .apply(this::setupTestCompileBuilder)
+            .compile()
+            .writeToZip();
+
+    if (isGreaterOrEqualToExceptionLevel()) {
+      assertFalse(globals.hasGlobals());
+    } else if (outputMode == OutputMode.DexIndexed) {
+      assertTrue(globals.hasGlobals());
+      assertTrue(globals.isSingleGlobal());
+    } else {
+      assertTrue(globals.hasGlobals());
+      // The Main class reference the mock and should have globals.
+      assertNotNull(globals.getProvider(Reference.classFromClass(Main.class)));
+    }
+
+    testForD8()
+        .debug()
+        .addProgramFiles(incrementalOut)
+        .apply(b -> b.getBuilder().addGlobalSyntheticsResourceProviders(globals.getProviders()))
+        .apply(this::setupTestRuntimeBuilder)
+        .run(parameters.getRuntime(), Main.class)
+        .apply(this::checkOutput);
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    R8TestCompileResult compile =
+        testForR8(parameters.getBackend())
+            .apply(this::setupTestBuilder)
+            .addKeepMainRule(Main.class)
+            .enableInliningAnnotations()
+            .compile();
+    compile.run(parameters.getRuntime(), Main.class).apply(this::checkOutput);
+  }
+
+  private void checkOutput(SingleTestRunResult<?> runResult) {
+    if (isGreaterOrEqualToExceptionLevel()) {
+      runResult.assertSuccessWithOutputLines("Caught LibraryException");
+    } else {
+      runResult.assertSuccessWithOutputLines("Caught Exception");
+    }
+  }
+
+  // Only present from api level M.
+  public static class LibraryExceptionPlaceHolder /* android.icu.util.IllformedLocaleException */
+      extends Exception {}
+
+  public static class Main {
+
+    @NeverInline
+    public static void test(int apiVersion) throws Exception {
+      if (apiVersion >= 28) {
+        throw new /* android.icu.util.IllformedLocaleException() */ LibraryExceptionPlaceHolder();
+      } else {
+        throw new Exception();
+      }
+    }
+
+    public static void main(String[] args) {
+      try {
+        test(AndroidBuildVersion.VERSION);
+      } catch (LibraryExceptionPlaceHolder /* android.icu.util.IllformedLocaleException */ e) {
+        System.out.println("Caught LibraryException");
+      } catch (Exception e) {
+        System.out.println("Caught Exception");
+      }
+    }
+  }
+}
diff --git a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
index 322c1d4..9dc6120 100644
--- a/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
+++ b/src/test/java/com/android/tools/r8/transformers/ClassFileTransformer.java
@@ -1343,11 +1343,16 @@
 
   public ClassFileTransformer transformTryCatchBlock(
       String methodName, TryCatchBlockTransform transform) {
+    return transformTryCatchBlock(MethodPredicate.onName(methodName), transform);
+  }
+
+  public ClassFileTransformer transformTryCatchBlock(
+      MethodPredicate predicate, TryCatchBlockTransform transform) {
     return addMethodTransformer(
         new MethodTransformer() {
           @Override
           public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
-            if (getContext().method.getMethodName().equals(methodName)) {
+            if (MethodPredicate.testContext(predicate, getContext())) {
               transform.visitTryCatchBlock(
                   start,
                   end,