Additional tests for api modeling, merging and class inlining
Bug: 138781768
Bug: 188388130
Change-Id: I4ffdac3127e515a4eb8a024f2b2497f89b55ef6e
diff --git a/src/main/java/com/android/tools/r8/utils/InternalOptions.java b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
index e08fe12..3a11cbd 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -51,8 +51,10 @@
import com.android.tools.r8.naming.MapVersion;
import com.android.tools.r8.origin.Origin;
import com.android.tools.r8.position.Position;
+import com.android.tools.r8.references.FieldReference;
import com.android.tools.r8.references.MethodReference;
import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.references.TypeReference;
import com.android.tools.r8.repackaging.Repackaging.DefaultRepackagingConfiguration;
import com.android.tools.r8.repackaging.Repackaging.RepackagingConfiguration;
import com.android.tools.r8.shaking.AppInfoWithLiveness;
@@ -1299,8 +1301,10 @@
public static class ApiModelTestingOptions {
- // A mapping from methods to the api-level introducing them.
+ // A mapping from references to the api-level introducing them.
public Map<MethodReference, AndroidApiLevel> methodApiMapping = new HashMap<>();
+ public Map<FieldReference, AndroidApiLevel> fieldApiMapping = new HashMap<>();
+ public Map<TypeReference, AndroidApiLevel> typeApiMapping = new HashMap<>();
public boolean enableApiCallerIdentification = false;
}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
new file mode 100644
index 0000000..8045b48
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiFieldsTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+
+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.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelClassMergingWithDifferentApiFieldsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Api::foo", "B::bar"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelClassMergingWithDifferentApiFieldsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, ApiSetter.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ // TODO(b/138781768): Should not be merged
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesMerged(A.class, B.class))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1))
+ .compile()
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Api {
+
+ public void foo() {
+ System.out.println("Api::foo");
+ }
+ }
+
+ static class ApiSetter {
+ static void set() {
+ A.api = new Api();
+ }
+ }
+
+ static class A {
+
+ private static Api api;
+
+ public static void callApi() throws Exception {
+ // The reflective call here is to ensure that the setting of A's api level is not based on
+ // a method reference to `Api` and only because of the type reference in the field `api`.
+ Class<?> aClass =
+ Class.forName(
+ "com.android.tools.r8.apimodeling.ApiModelClassMergingWithDifferentApiFieldsTest_Api"
+ .replace("_", "$"));
+ Method foo = aClass.getDeclaredMethod("foo");
+ foo.invoke(api);
+ }
+ }
+
+ public static class B {
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B::bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ ApiSetter.set();
+ A.callApi();
+ B.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
new file mode 100644
index 0000000..b56d6ae
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelClassMergingWithDifferentApiMethodsTest.java
@@ -0,0 +1,86 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+
+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.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelClassMergingWithDifferentApiMethodsTest extends TestBase {
+
+ private final TestParameters parameters;
+ private final String[] EXPECTED = new String[] {"Api::apiLevel22", "B::bar"};
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelClassMergingWithDifferentApiMethodsTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ // TODO(b/138781768): Should not be merged
+ .addHorizontallyMergedClassesInspector(
+ inspector -> inspector.assertClassesMerged(A.class, B.class))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .compile()
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ public static class Api {
+
+ public static void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ public static class A {
+
+ @NeverInline
+ public static void callFoo() {
+ Api.apiLevel22();
+ }
+ }
+
+ public static class B {
+
+ @NeverInline
+ public static void bar() {
+ System.out.println("B::bar");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ A.callFoo();
+ B.bar();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
new file mode 100644
index 0000000..2e24790
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelInlineMethodWithApiTypeTest.java
@@ -0,0 +1,98 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelInlineMethodWithApiTypeTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelInlineMethodWithApiTypeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiCallerApiLevel1 = ApiCaller.class.getDeclaredMethod("apiLevel22");
+ Method apiCallerCallerApiLevel1 = ApiCallerCaller.class.getDeclaredMethod("apiLevel22");
+ Method otherCallerApiLevel1 = OtherCaller.class.getDeclaredMethod("apiLevel1");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, OtherCaller.class, Main.class)
+ .addLibraryClasses(ApiType.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForType(ApiType.class, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .addRunClasspathClasses(ApiType.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(ApiType.class.getName())
+ .inspect(verifyThat(parameters, apiCallerApiLevel1).inlinedInto(apiCallerCallerApiLevel1))
+ // TODO(b/138781768): Should not be inlined
+ .inspect(
+ verifyThat(parameters, apiCallerCallerApiLevel1).inlinedInto(otherCallerApiLevel1));
+ }
+
+ public static class ApiType {}
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+ public static ApiType apiLevel22() throws Exception {
+ // The reflective call here is to ensure that the setting of A's api level is not based on
+ // a method reference to `Api` and only because of the type reference in the field `api`.
+ Class<?> reflectiveCall =
+ Class.forName(
+ "com.android.tools.r8.apimodeling.ApiModelInlineMethodWithApiTypeTest_ApiType"
+ .replace("_", "$"));
+ return (ApiType) reflectiveCall.getDeclaredConstructor().newInstance();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ public static void apiLevel22() throws Exception {
+ // This is referencing the proto of ApiCaller.foo and thus have a reference to ApiType. It is
+ // therefore OK to inline ApiCaller.foo() into ApiCallerCaller.bar().
+ System.out.println(ApiCaller.apiLevel22().getClass().getName());
+ }
+ }
+
+ public static class OtherCaller {
+
+ public static void apiLevel1() throws Exception {
+ // ApiCallerCaller.apiLevel22 should never be inlined here.
+ ApiCallerCaller.apiLevel22();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) throws Exception {
+ OtherCaller.apiLevel1();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
new file mode 100644
index 0000000..3bd5b8a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningFieldTest.java
@@ -0,0 +1,100 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+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.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Field;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelNoClassInliningFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoClassInliningFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("foo");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, ApiBuilder.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ inspector -> {
+ // TODO(b/138781768): Should not be class inlined.
+ assertThat(inspector.clazz(ApiCaller.class), not(isPresent()));
+ })
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public String foo = "Api::apiLevel22";
+ }
+
+ public static class ApiBuilder {
+
+ public static Api build() {
+ return new Api();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ private Api api;
+
+ public ApiCaller(Api api) {
+ this.api = api;
+ System.out.println(api.foo);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void callCallApi() {
+ new ApiCaller(ApiBuilder.build());
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.callCallApi();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
new file mode 100644
index 0000000..a7804e0
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoClassInliningMethodTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForMethod;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelNoClassInliningMethodTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoClassInliningMethodTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Method apiMethod = Api.class.getDeclaredMethod("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("callApi");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("callCallApi");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForMethod(apiMethod, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ verifyThat(parameters, apiCaller)
+ .inlinedIntoFromApiLevel(apiCallerCaller, AndroidApiLevel.L_MR1))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Api::apiLevel22");
+ }
+
+ public static class Api {
+
+ public static void apiLevel22() {
+ System.out.println("Api::apiLevel22");
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public void callApi() {
+ Api.apiLevel22();
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void callCallApi() {
+ new ApiCaller().callApi();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.callCallApi();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
new file mode 100644
index 0000000..f210e6f
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelInstanceFieldTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelNoInliningOfHigherApiLevelInstanceFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelInstanceFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("getInstanceField");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ // TODO(b/138781768): Should not be inlined
+ .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ // The api class does not have an api level to ensure it is not the instance initializer that is
+ // keeping us from inlining.
+ public static class Api {
+
+ public String apiLevel22 = "Hello World!";
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void getInstanceField() {
+ System.out.println(new Api().apiLevel22);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void noApiCall() {
+ ApiCaller.getInstanceField();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
new file mode 100644
index 0000000..0087275
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelNoInliningOfHigherApiLevelStaticFieldTest.java
@@ -0,0 +1,90 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForField;
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.verifyThat;
+
+import com.android.tools.r8.NeverInline;
+import com.android.tools.r8.NoHorizontalClassMerging;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelNoInliningOfHigherApiLevelStaticFieldTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelNoInliningOfHigherApiLevelStaticFieldTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ Field apiField = Api.class.getDeclaredField("apiLevel22");
+ Method apiCaller = ApiCaller.class.getDeclaredMethod("getStaticField");
+ Method apiCallerCaller = ApiCallerCaller.class.getDeclaredMethod("noApiCall");
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ApiCaller.class, ApiCallerCaller.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNoHorizontalClassMergingAnnotations()
+ .apply(setMockApiLevelForField(apiField, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ // TODO(b/138781768): Should not be inlined
+ .inspect(verifyThat(parameters, apiCaller).inlinedInto(apiCallerCaller))
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ // The api class does not have an api level to ensure it is not the class reference is keeping us
+ // from inlining.
+ public static class Api {
+
+ public static String apiLevel22 = "Hello World!";
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCaller {
+
+ public static void getStaticField() {
+ System.out.println(Api.apiLevel22);
+ }
+ }
+
+ @NoHorizontalClassMerging
+ public static class ApiCallerCaller {
+
+ @NeverInline
+ public static void noApiCall() {
+ ApiCaller.getStaticField();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ ApiCallerCaller.noApiCall();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
new file mode 100644
index 0000000..a04774b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelVerticalMergingOfSuperClassTest.java
@@ -0,0 +1,84 @@
+// Copyright (c) 2021, 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.apimodeling;
+
+import static com.android.tools.r8.apimodeling.ApiModelingTestHelper.setMockApiLevelForType;
+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.NeverClassInline;
+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.utils.AndroidApiLevel;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.junit.runners.Parameterized.Parameters;
+
+@RunWith(Parameterized.class)
+public class ApiModelVerticalMergingOfSuperClassTest extends TestBase {
+
+ private final TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ public ApiModelVerticalMergingOfSuperClassTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(A.class, B.class, Main.class)
+ .addLibraryClasses(Api.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
+ .enableNeverClassInliningAnnotations()
+ .apply(setMockApiLevelForType(Api.class, AndroidApiLevel.L_MR1))
+ .apply(ApiModelingTestHelper::enableApiCallerIdentification)
+ .compile()
+ .inspect(
+ inspector -> {
+ // TODO(b/138781768): We should not merge A into B.
+ assertThat(inspector.clazz(A.class), not(isPresent()));
+ })
+ .addRunClasspathClasses(Api.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines("Hello World!");
+ }
+
+ public static class Api {}
+
+ public static class A extends Api {
+
+ @NeverInline
+ public void bar() {
+ System.out.println("Hello World!");
+ }
+ }
+
+ @NeverClassInline
+ public static class B extends A {
+
+ public void foo() {
+ bar();
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ new B().foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
index 5b1a2f7..b20e395 100644
--- a/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodeling/ApiModelingTestHelper.java
@@ -17,6 +17,7 @@
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import com.android.tools.r8.utils.codeinspector.CodeMatchers;
import com.android.tools.r8.utils.codeinspector.MethodSubject;
+import java.lang.reflect.Field;
import java.lang.reflect.Method;
public abstract class ApiModelingTestHelper {
@@ -34,6 +35,32 @@
};
}
+ static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>>
+ ThrowableConsumer<T> setMockApiLevelForField(Field field, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options
+ .apiModelingOptions()
+ .fieldApiMapping
+ .put(Reference.fieldFromField(field), apiLevel);
+ });
+ };
+ }
+
+ static <T extends TestCompilerBuilder<?, ?, ?, ?, ?>> ThrowableConsumer<T> setMockApiLevelForType(
+ Class<?> clazz, AndroidApiLevel apiLevel) {
+ return compilerBuilder -> {
+ compilerBuilder.addOptionsModification(
+ options -> {
+ options
+ .apiModelingOptions()
+ .typeApiMapping
+ .put(Reference.classFromClass(clazz), apiLevel);
+ });
+ };
+ }
+
static void enableApiCallerIdentification(TestCompilerBuilder<?, ?, ?, ?, ?> compilerBuilder) {
compilerBuilder.addOptionsModification(
options -> {