| // Copyright (c) 2019, 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.retrace; |
| |
| import static com.android.tools.r8.naming.retrace.StackTrace.isSame; |
| import static com.android.tools.r8.naming.retrace.StackTrace.isSameExceptForFileNameAndLineNumber; |
| import static com.android.tools.r8.utils.codeinspector.Matchers.isPresent; |
| import static org.hamcrest.CoreMatchers.not; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| |
| import com.android.tools.r8.AlwaysInline; |
| 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.TestRuntime.CfVm; |
| import com.android.tools.r8.naming.retrace.StackTrace; |
| import com.android.tools.r8.shaking.ProguardKeepAttributes; |
| import com.android.tools.r8.utils.AndroidApiLevel; |
| import com.android.tools.r8.utils.codeinspector.ClassSubject; |
| import com.android.tools.r8.utils.codeinspector.CodeInspector; |
| import java.util.Objects; |
| import org.junit.Before; |
| import org.junit.Test; |
| import org.junit.runner.RunWith; |
| import org.junit.runners.Parameterized; |
| |
| @RunWith(Parameterized.class) |
| public class InlineWithoutNullCheckTest extends TestBase { |
| |
| private final TestParameters parameters; |
| |
| @Parameterized.Parameters(name = "{0}") |
| public static TestParametersCollection data() { |
| return getTestParameters().withAllRuntimes().withAllApiLevels().build(); |
| } |
| |
| public InlineWithoutNullCheckTest(TestParameters parameters) { |
| this.parameters = parameters; |
| } |
| |
| public StackTrace expectedStackTraceForInlineMethod; |
| public StackTrace expectedStackTraceForInlineField; |
| public StackTrace expectedStackTraceForInlineStaticField; |
| |
| @Before |
| public void setup() throws Exception { |
| // Get the expected stack traces by running on the runtime to test. |
| expectedStackTraceForInlineMethod = |
| testForRuntime(parameters.getRuntime(), parameters.getApiLevel()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .run(parameters.getRuntime(), TestClassForInlineMethod.class) |
| .writeProcessResult(System.out) |
| .assertFailure() |
| .getStackTrace(); |
| expectedStackTraceForInlineField = |
| testForRuntime(parameters.getRuntime(), parameters.getApiLevel()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .run(parameters.getRuntime(), TestClassForInlineField.class) |
| .writeProcessResult(System.out) |
| .assertFailure() |
| .getStackTrace(); |
| expectedStackTraceForInlineStaticField = |
| testForRuntime(parameters.getRuntime(), parameters.getApiLevel()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .run(parameters.getRuntime(), TestClassForInlineStaticField.class) |
| .writeProcessResult(System.out) |
| .assertFailure() |
| .getStackTrace(); |
| |
| // Check the expected stack traces from running on the runtime to test. |
| assertThat( |
| expectedStackTraceForInlineMethod, |
| isSameExceptForFileNameAndLineNumber( |
| StackTrace.builder() |
| .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessInstanceMethod") |
| .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main") |
| .build())); |
| assertThat( |
| expectedStackTraceForInlineField, |
| isSameExceptForFileNameAndLineNumber( |
| StackTrace.builder() |
| .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessInstanceField") |
| .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main") |
| .build())); |
| assertThat( |
| expectedStackTraceForInlineStaticField, |
| isSameExceptForFileNameAndLineNumber( |
| StackTrace.builder() |
| .addWithoutFileNameAndLineNumber(A.class, "inlineMethodWhichAccessStaticField") |
| .addWithoutFileNameAndLineNumber(TestClassForInlineStaticField.class, "main") |
| .build())); |
| } |
| |
| private void checkSomething(CodeInspector inspector) { |
| ClassSubject classSubject = inspector.clazz(Result.class); |
| assertThat(classSubject, isPresent()); |
| assertThat( |
| classSubject.uniqueMethodWithName("methodWhichAccessInstanceMethod"), not(isPresent())); |
| assertThat( |
| classSubject.uniqueMethodWithName("methodWhichAccessInstanceField"), not(isPresent())); |
| assertThat(classSubject.uniqueMethodWithName("methodWhichAccessStaticField"), not(isPresent())); |
| } |
| |
| @Test |
| public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectMethod() throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .addKeepMainRule(TestClassForInlineMethod.class) |
| .enableAlwaysInliningAnnotations() |
| .enableInliningAnnotations() |
| .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) |
| .setMinApi(parameters.getApiLevel()) |
| .compile() |
| .inspect(this::checkSomething) |
| .run(parameters.getRuntime(), TestClassForInlineMethod.class) |
| .assertFailure() |
| // TODO(b/143607166): The stack trace has one more frame on the top than expected. |
| .inspectStackTrace( |
| stackTrace -> assertThat(expectedStackTraceForInlineMethod, not(isSame(stackTrace)))) |
| .inspectStackTrace( |
| stackTrace -> |
| assertThat( |
| stackTrace, |
| isSameExceptForFileNameAndLineNumber( |
| createStackTraceBuilder() |
| .addWithoutFileNameAndLineNumber( |
| A.class, "inlineMethodWhichAccessInstanceMethod") |
| .addWithoutFileNameAndLineNumber(TestClassForInlineMethod.class, "main") |
| .build()))); |
| } |
| |
| @Test |
| public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectField() throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .addKeepMainRule(TestClassForInlineField.class) |
| .enableAlwaysInliningAnnotations() |
| .enableInliningAnnotations() |
| .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) |
| .setMinApi(parameters.getApiLevel()) |
| .compile() |
| .inspect(this::checkSomething) |
| .run(parameters.getRuntime(), TestClassForInlineField.class) |
| .assertFailure() |
| // TODO(b/143607166): The stack trace has one more frame on the top than expected. |
| .inspectStackTrace( |
| stackTrace -> assertThat(expectedStackTraceForInlineField, not(isSame(stackTrace)))) |
| .inspectStackTrace( |
| stackTrace -> |
| assertThat( |
| stackTrace, |
| isSameExceptForFileNameAndLineNumber( |
| createStackTraceBuilder() |
| .addWithoutFileNameAndLineNumber( |
| A.class, "inlineMethodWhichAccessInstanceField") |
| .addWithoutFileNameAndLineNumber(TestClassForInlineField.class, "main") |
| .build()))); |
| } |
| |
| @Test |
| public void testInlineMethodWhichChecksNullReceiverBeforeAnySideEffectStaticField() |
| throws Exception { |
| testForR8(parameters.getBackend()) |
| .addInnerClasses(InlineWithoutNullCheckTest.class) |
| .addKeepMainRule(TestClassForInlineStaticField.class) |
| .enableAlwaysInliningAnnotations() |
| .enableInliningAnnotations() |
| .addKeepAttributes(ProguardKeepAttributes.SOURCE_FILE) |
| .setMinApi(parameters.getApiLevel()) |
| .compile() |
| .inspect(this::checkSomething) |
| .run(parameters.getRuntime(), TestClassForInlineStaticField.class) |
| .assertFailure() |
| // TODO(b/143607166): The stack trace has one more frame on the top than expected. |
| .inspectStackTrace( |
| stackTrace -> |
| assertThat(expectedStackTraceForInlineStaticField, not(isSame(stackTrace)))) |
| .inspectStackTrace( |
| stackTrace -> |
| assertThat( |
| stackTrace, |
| isSameExceptForFileNameAndLineNumber( |
| createStackTraceBuilder() |
| .addWithoutFileNameAndLineNumber( |
| A.class, "inlineMethodWhichAccessStaticField") |
| .addWithoutFileNameAndLineNumber( |
| TestClassForInlineStaticField.class, "main") |
| .build()))); |
| } |
| |
| private StackTrace.Builder createStackTraceBuilder() { |
| StackTrace.Builder builder = StackTrace.builder(); |
| if (canUseRequireNonNull()) { |
| if (parameters.isCfRuntime() |
| && parameters.getRuntime().asCf().isNewerThanOrEqual(CfVm.JDK9)) { |
| builder.addWithoutFileNameAndLineNumber("java.base/java.util.Objects", "requireNonNull"); |
| } else { |
| builder.addWithoutFileNameAndLineNumber(Objects.class, "requireNonNull"); |
| } |
| } |
| return builder; |
| } |
| |
| private boolean canUseRequireNonNull() { |
| return parameters.isDexRuntime() |
| && parameters.getApiLevel().isGreaterThanOrEqualTo(AndroidApiLevel.K); |
| } |
| |
| static class TestClassForInlineMethod { |
| |
| public static void main(String[] args) { |
| A a = new A(System.currentTimeMillis() > 0 ? null : new Result()); |
| System.out.print(a.inlineMethodWhichAccessInstanceMethod()); |
| } |
| } |
| |
| static class TestClassForInlineField { |
| |
| public static void main(String[] args) { |
| A a = new A(System.currentTimeMillis() > 0 ? null : new Result()); |
| System.out.print(a.inlineMethodWhichAccessInstanceField()); |
| } |
| } |
| |
| static class TestClassForInlineStaticField { |
| |
| public static void main(String[] args) { |
| A a = new A(System.currentTimeMillis() > 0 ? null : new Result()); |
| System.out.print(a.inlineMethodWhichAccessStaticField()); |
| } |
| } |
| |
| static class A { |
| Result result; |
| |
| A(Result result) { |
| this.result = result; |
| } |
| |
| @NeverInline |
| Result get() { |
| return result; |
| } |
| |
| @NeverInline |
| int inlineMethodWhichAccessInstanceMethod() { |
| return get().methodWhichAccessInstanceMethod(); |
| } |
| |
| @NeverInline |
| int inlineMethodWhichAccessInstanceField() { |
| return get().methodWhichAccessInstanceField(); |
| } |
| |
| @NeverInline |
| int inlineMethodWhichAccessStaticField() { |
| return get().methodWhichAccessStaticField(); |
| } |
| } |
| |
| static class Result { |
| int x = 1; |
| static int y = 1; |
| |
| @AlwaysInline |
| int methodWhichAccessInstanceMethod() { |
| return privateMethod(); |
| } |
| |
| @AlwaysInline |
| int methodWhichAccessInstanceField() { |
| return x; |
| } |
| |
| @AlwaysInline |
| int methodWhichAccessStaticField() { |
| return Result.y; |
| } |
| |
| @NeverInline |
| private int privateMethod() { |
| return 0; |
| } |
| } |
| } |