| // Copyright (c) 2020, 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.ir.optimize.inliner; |
| |
| import static org.junit.Assert.assertFalse; |
| import static org.junit.Assert.assertTrue; |
| |
| import com.android.tools.r8.NeverInline; |
| import com.android.tools.r8.R8TestRunResult; |
| import com.android.tools.r8.TestBase; |
| import com.android.tools.r8.TestParameters; |
| import com.android.tools.r8.ToolHelper; |
| import com.android.tools.r8.ToolHelper.DexVm.Version; |
| 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.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Streams; |
| import java.io.IOException; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.TreeSet; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| import org.junit.runners.Parameterized.Parameters; |
| |
| /** This test extends that of Regress131349148 for other API-introduced exceptions. */ |
| @RunWith(Parameterized.class) |
| public class InlineCatchHandlerWithLibraryTypeTest extends TestBase { |
| |
| private static final String TEMPLATE_CODE_EXCEPTION_BINARY_NAME = "java/lang/RuntimeException"; |
| |
| // A subset of exception types introduced in API levels between 16 to 24. |
| private static final Map<String, Integer> EXCEPTIONS = |
| ImmutableMap.<String, Integer>builder() |
| // VM 4.0.4 (api 15) is the first VM we have so no need to go prior to that. |
| .put("android.media.MediaCryptoException", 16) |
| .put("android.view.WindowManager$InvalidDisplayException", 17) |
| .put("android.media.DeniedByServerException", 18) |
| .put("android.media.ResourceBusyException", 19) |
| .put("java.lang.ReflectiveOperationException", 19) |
| .put("javax.crypto.AEADBadTagException", 19) |
| .put("android.system.ErrnoException", 21) |
| .put("android.media.MediaDrmResetException", 23) |
| .put("java.io.UncheckedIOException", 24) |
| .put("java.util.concurrent.CompletionException", 24) |
| // Verify error was fixed in 21 so up to 24 should catch post-fix issues. |
| .build(); |
| |
| private static final String EXPECTED = StringUtils.lines("Done..."); |
| |
| private final TestParameters parameters; |
| private final String exception; |
| |
| @Parameters(name = "{0}, {1}") |
| public static List<Object[]> params() { |
| return buildParameters( |
| getTestParameters().withAllRuntimesAndApiLevels().build(), |
| new TreeSet<>(EXCEPTIONS.keySet())); |
| } |
| |
| public InlineCatchHandlerWithLibraryTypeTest(TestParameters parameters, String exception) { |
| this.parameters = parameters; |
| this.exception = exception; |
| } |
| |
| private String getExceptionBinaryName() { |
| return DescriptorUtils.getBinaryNameFromJavaType(exception); |
| } |
| |
| private byte[] getClassWithCatchHandler() throws IOException { |
| return transformer(ClassWithCatchHandler.class) |
| .transformTryCatchBlock( |
| "methodWithCatch", |
| (start, end, handler, type, continuation) -> { |
| String newType = |
| type.equals(TEMPLATE_CODE_EXCEPTION_BINARY_NAME) |
| ? getExceptionBinaryName() |
| : type; |
| continuation.apply(start, end, handler, newType); |
| }) |
| .transform(); |
| } |
| |
| private boolean compilationTargetIsMissingExceptionType() { |
| // A CF target could target any API in the end. |
| return parameters.isCfRuntime() |
| || parameters.getApiLevel().getLevel() < EXCEPTIONS.get(exception); |
| } |
| |
| private boolean compileTargetHasVerificationBug() { |
| // A CF target could target any API in the end. |
| return parameters.isCfRuntime() || parameters.getApiLevel().isLessThan(AndroidApiLevel.L); |
| } |
| |
| @Test |
| public void test() throws Exception { |
| testForR8(parameters.getBackend()) |
| .enableInliningAnnotations() |
| .addProgramClasses(TestClass.class) |
| .addProgramClassFileData(getClassWithCatchHandler()) |
| .addKeepMainRule(TestClass.class) |
| .setMinApi(parameters.getApiLevel()) |
| // Use the latest library so that all of the exceptions are defined. |
| .addLibraryFiles(ToolHelper.getAndroidJar(AndroidApiLevel.LATEST)) |
| .compile() |
| .inspect(this::checkInlined) |
| .run(parameters.getRuntime(), TestClass.class) |
| .apply(this::checkResult); |
| } |
| |
| private void checkResult(R8TestRunResult runResult) { |
| // The bootclasspath for our build of 4.4.4 does not contain various bits. Allow verify error. |
| if (!compilationTargetIsMissingExceptionType() |
| && parameters.getRuntime().asDex().getVm().getVersion().equals(Version.V4_4_4) |
| && (exception.startsWith("android.media") || exception.startsWith("android.view"))) { |
| runResult.assertFailureWithErrorThatThrows(VerifyError.class); |
| return; |
| } |
| // Correct compilation should ensure that all programs run without error. |
| runResult.assertSuccessWithOutput(EXPECTED); |
| } |
| |
| private void checkInlined(CodeInspector inspector) { |
| ClassSubject classSubject = inspector.clazz(TestClass.class); |
| boolean mainHasInlinedCatchHandler = |
| Streams.stream(classSubject.mainMethod().iterateTryCatches()) |
| .anyMatch(tryCatch -> tryCatch.isCatching(exception)); |
| if (compileTargetHasVerificationBug() && compilationTargetIsMissingExceptionType()) { |
| assertFalse(mainHasInlinedCatchHandler); |
| } else { |
| assertTrue(mainHasInlinedCatchHandler); |
| } |
| } |
| |
| static class TestClass { |
| |
| public static void main(String[] args) { |
| if (args.length == 200) { |
| // Never called |
| ClassWithCatchHandler.methodWithCatch(); |
| } |
| System.out.println("Done..."); |
| } |
| } |
| |
| static class ClassWithCatchHandler { |
| |
| @NeverInline |
| public static void maybeThrow() { |
| if (System.nanoTime() > 0) { |
| throw new RuntimeException(); |
| } |
| } |
| |
| public static void methodWithCatch() { |
| try { |
| maybeThrow(); |
| } catch (RuntimeException e) { |
| // We must use the exception, otherwise there is no move-exception that triggers the |
| // verification error. |
| System.out.println(e.getClass().getName()); |
| } |
| } |
| } |
| } |