[ApiModel] Only visit super hierarchy for library classes
Bug: b/246251214
Change-Id: I557a0cb9eea4f7340432b62fa230d325bc66c043
diff --git a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
index 6d8c09d..6754dde 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -6,6 +6,7 @@
import static com.android.tools.r8.utils.MapUtils.ignoreKey;
+import com.android.tools.r8.dex.code.CfOrDexInstruction;
import com.android.tools.r8.errors.MissingGlobalSyntheticsConsumerDiagnostic;
import com.android.tools.r8.errors.Unreachable;
import com.android.tools.r8.graph.AppInfo;
@@ -30,12 +31,18 @@
import com.android.tools.r8.utils.WorkList;
import com.google.common.collect.Sets;
import java.util.Arrays;
+import java.util.ListIterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
+/**
+ * The only instructions we do not outline is constant classes, instance-of/checkcast and exception
+ * guards. For program classes we also visit super types if these are library otherwise we will
+ * visit the program super type's super types when visiting that program class.
+ */
public class ApiReferenceStubber {
private class ReferencesToApiLevelUseRegistry extends UseRegistry<ProgramMethod> {
@@ -46,52 +53,52 @@
@Override
public void registerInitClass(DexType type) {
- checkReferenceToLibraryClass(type);
+ // Intentionally empty.
}
@Override
public void registerInvokeVirtual(DexMethod method) {
- checkReferenceToLibraryClass(method);
+ // Intentionally empty.
}
@Override
public void registerInvokeDirect(DexMethod method) {
- checkReferenceToLibraryClass(method);
+ // Intentionally empty.
}
@Override
public void registerInvokeStatic(DexMethod method) {
- checkReferenceToLibraryClass(method);
+ // Intentionally empty.
}
@Override
public void registerInvokeInterface(DexMethod method) {
- checkReferenceToLibraryClass(method);
+ // Intentionally empty.
}
@Override
public void registerInvokeSuper(DexMethod method) {
- checkReferenceToLibraryClass(method);
+ // Intentionally empty.
}
@Override
public void registerInstanceFieldRead(DexField field) {
- checkReferenceToLibraryClass(field);
+ // Intentionally empty.
}
@Override
public void registerInstanceFieldWrite(DexField field) {
- checkReferenceToLibraryClass(field);
+ // Intentionally empty.
}
@Override
public void registerStaticFieldRead(DexField field) {
- checkReferenceToLibraryClass(field);
+ // Intentionally empty.
}
@Override
public void registerStaticFieldWrite(DexField field) {
- checkReferenceToLibraryClass(field);
+ // Intentionally empty.
}
@Override
@@ -99,6 +106,29 @@
checkReferenceToLibraryClass(type);
}
+ @Override
+ public void registerInstanceOf(DexType type) {
+ checkReferenceToLibraryClass(type);
+ }
+
+ @Override
+ public void registerConstClass(
+ DexType type,
+ ListIterator<? extends CfOrDexInstruction> iterator,
+ boolean ignoreCompatRules) {
+ checkReferenceToLibraryClass(type);
+ }
+
+ @Override
+ public void registerCheckCast(DexType type, boolean ignoreCompatRules) {
+ checkReferenceToLibraryClass(type);
+ }
+
+ @Override
+ public void registerExceptionGuard(DexType guard) {
+ checkReferenceToLibraryClass(guard);
+ }
+
private void checkReferenceToLibraryClass(DexReference reference) {
DexType rewrittenType = appView.graphLens().lookupType(reference.getContextType());
findReferencedLibraryClasses(rewrittenType, getContext().getContextClass());
@@ -158,7 +188,9 @@
.isSyntheticOfKind(clazz.getType(), kinds -> kinds.API_MODEL_OUTLINE)) {
return;
}
- findReferencedLibraryClasses(clazz.type, clazz);
+ clazz
+ .allImmediateSupertypes()
+ .forEach(superType -> findReferencedLibraryClasses(superType, clazz));
clazz.forEachProgramMethodMatching(
DexEncodedMethod::hasCode,
method -> method.registerCodeReferences(new ReferencesToApiLevelUseRegistry(method)));
@@ -171,22 +203,20 @@
WorkList<DexType> workList = WorkList.newIdentityWorkList(type, seenTypes);
while (workList.hasNext()) {
DexClass clazz = appView.definitionFor(workList.next());
- if (clazz == null) {
+ if (clazz == null || !clazz.isLibraryClass()) {
continue;
}
- if (clazz.isLibraryClass()) {
- ComputedApiLevel androidApiLevel =
- apiLevelCompute.computeApiLevelForLibraryReference(
- clazz.type, ComputedApiLevel.unknown());
- if (androidApiLevel.isGreaterThan(appView.computedMinApiLevel())
- && !androidApiLevel.isUnknownApiLevel()) {
- libraryClassesToMock.add(clazz.asLibraryClass());
- referencingContexts
- .computeIfAbsent(clazz.asLibraryClass(), ignoreKey(Sets::newConcurrentHashSet))
- .add(context);
- }
+ ComputedApiLevel androidApiLevel =
+ apiLevelCompute.computeApiLevelForLibraryReference(
+ clazz.type, ComputedApiLevel.unknown());
+ if (androidApiLevel.isGreaterThan(appView.computedMinApiLevel())
+ && androidApiLevel.isKnownApiLevel()) {
+ workList.addIfNotSeen(clazz.allImmediateSupertypes());
+ libraryClassesToMock.add(clazz.asLibraryClass());
+ referencingContexts
+ .computeIfAbsent(clazz.asLibraryClass(), ignoreKey(Sets::newConcurrentHashSet))
+ .add(context);
}
- workList.addIfNotSeen(clazz.allImmediateSupertypes());
}
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
deleted file mode 100644
index 4f31982..0000000
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassInstanceInitTest.java
+++ /dev/null
@@ -1,140 +0,0 @@
-// 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.apimodel;
-
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
-import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
-import static org.hamcrest.CoreMatchers.containsString;
-import static org.hamcrest.CoreMatchers.not;
-import static org.junit.Assume.assumeTrue;
-
-import com.android.tools.r8.CompilationMode;
-import com.android.tools.r8.NeverInline;
-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.DexVm.Version;
-import com.android.tools.r8.utils.AndroidApiLevel;
-import com.android.tools.r8.utils.codeinspector.CodeInspector;
-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 ApiModelMockClassInstanceInitTest extends TestBase {
-
- private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
-
- @Parameter public TestParameters parameters;
-
- @Parameters(name = "{0}")
- public static TestParametersCollection data() {
- return getTestParameters().withAllRuntimesAndApiLevels().build();
- }
-
- private boolean isGreaterOrEqualToMockLevel() {
- return parameters.isDexRuntime() && parameters.getApiLevel().isGreaterThanOrEqualTo(mockLevel);
- }
-
- private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
- testBuilder
- .addProgramClasses(Main.class, TestClass.class)
- .addLibraryClasses(LibraryClass.class)
- .addDefaultRuntimeLibrary(parameters)
- .setMinApi(parameters.getApiLevel())
- .addAndroidBuildVersion()
- .apply(ApiModelingTestHelper::enableStubbingOfClasses)
- .apply(setMockApiLevelForClass(LibraryClass.class, mockLevel))
- .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockLevel));
- }
-
- @Test
- public void testD8Debug() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8()
- .setMode(CompilationMode.DEBUG)
- .apply(this::setupTestBuilder)
- .compile()
- .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
- .run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput)
- .inspect(this::inspect);
- }
-
- @Test
- public void testD8Release() throws Exception {
- assumeTrue(parameters.isDexRuntime());
- testForD8()
- .setMode(CompilationMode.RELEASE)
- .apply(this::setupTestBuilder)
- .compile()
- .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
- .run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput)
- .inspect(this::inspect);
- }
-
- @Test
- public void testR8() throws Exception {
- testForR8(parameters.getBackend())
- .apply(this::setupTestBuilder)
- .addKeepMainRule(Main.class)
- .enableInliningAnnotations()
- .compile()
- .applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
- .run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput)
- .inspect(this::inspect);
- }
-
- private void checkOutput(SingleTestRunResult<?> runResult) {
- if (isGreaterOrEqualToMockLevel()) {
- runResult.assertSuccessWithOutputLines("LibraryClass::foo");
- } else {
- runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
- }
- runResult.applyIf(
- !isGreaterOrEqualToMockLevel()
- && parameters.isDexRuntime()
- && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
- result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
- }
-
- private void inspect(CodeInspector inspector) {
- verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
- }
-
- // Only present from api level 23.
- public static class LibraryClass {
-
- public void foo() {
- System.out.println("LibraryClass::foo");
- }
- }
-
- public static class TestClass {
-
- @NeverInline
- public static void test() {
- try {
- new LibraryClass().foo();
- } catch (ExceptionInInitializerError | NoClassDefFoundError er) {
- System.out.println("NoClassDefFoundError");
- }
- }
- }
-
- public static class Main {
-
- public static void main(String[] args) {
- TestClass.test();
- }
- }
-}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
index b9476a7..b80d79c 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelMockClassLoadingTest.java
@@ -6,14 +6,19 @@
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
import static com.android.tools.r8.apimodel.ApiModelingTestHelper.verifyThat;
+import static org.hamcrest.CoreMatchers.containsString;
+import static org.hamcrest.CoreMatchers.not;
import static org.junit.Assume.assumeTrue;
import com.android.tools.r8.CompilationMode;
+import com.android.tools.r8.NeverInline;
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.DexVm;
+import com.android.tools.r8.ToolHelper.DexVm.Version;
import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.codeinspector.CodeInspector;
import org.junit.Test;
@@ -27,7 +32,7 @@
private final AndroidApiLevel mockLevel = AndroidApiLevel.M;
- @Parameter() public TestParameters parameters;
+ @Parameter public TestParameters parameters;
@Parameters(name = "{0}")
public static TestParametersCollection data() {
@@ -40,7 +45,7 @@
private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) {
testBuilder
- .addProgramClasses(Main.class)
+ .addProgramClasses(Main.class, TestClass.class)
.addLibraryClasses(LibraryClass.class)
.addDefaultRuntimeLibrary(parameters)
.setMinApi(parameters.getApiLevel())
@@ -55,10 +60,10 @@
.setMode(CompilationMode.DEBUG)
.apply(this::setupTestBuilder)
.compile()
- .inspect(this::inspect)
.applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput);
+ .apply(this::checkOutput)
+ .inspect(this::inspect);
}
@Test
@@ -68,10 +73,10 @@
.setMode(CompilationMode.RELEASE)
.apply(this::setupTestBuilder)
.compile()
- .inspect(this::inspect)
.applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput);
+ .apply(this::checkOutput)
+ .inspect(this::inspect);
}
@Test
@@ -79,35 +84,56 @@
testForR8(parameters.getBackend())
.apply(this::setupTestBuilder)
.addKeepMainRule(Main.class)
+ .enableInliningAnnotations()
.compile()
- .inspect(this::inspect)
.applyIf(isGreaterOrEqualToMockLevel(), b -> b.addBootClasspathClasses(LibraryClass.class))
.run(parameters.getRuntime(), Main.class)
- .apply(this::checkOutput);
+ .apply(this::checkOutput)
+ .inspect(this::inspect);
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult) {
+ if (isGreaterOrEqualToMockLevel()) {
+ runResult.assertSuccessWithOutputLines("Hello World!");
+ } else if (parameters.isDexRuntime()
+ && parameters.asDexRuntime().getVm().isEqualTo(DexVm.ART_4_4_4_HOST)) {
+ runResult.assertSuccessWithOutputLines("ClassNotFoundException");
+ } else {
+ runResult.assertSuccessWithOutputLines("NoClassDefFoundError");
+ }
+ runResult.applyIf(
+ !isGreaterOrEqualToMockLevel()
+ && parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
+ result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
}
private void inspect(CodeInspector inspector) {
verifyThat(inspector, parameters, LibraryClass.class).stubbedUntil(mockLevel);
}
- private void checkOutput(SingleTestRunResult<?> runResult) {
- runResult.assertSuccessWithOutputLinesIf(isGreaterOrEqualToMockLevel(), "Hello World");
- runResult.assertFailureWithErrorThatThrowsIf(
- !isGreaterOrEqualToMockLevel(), NoClassDefFoundError.class);
- }
+ // Only present from api level 23.
+ public static class LibraryClass {}
- // Only present form api level 23.
- public static class LibraryClass {
+ public static class TestClass {
- public void foo() {
- System.out.println("Hello World");
+ @NeverInline
+ public static void test() {
+ try {
+ Class.forName(LibraryClass.class.getName());
+ System.out.println("Hello World!");
+ } catch (ExceptionInInitializerError | NoClassDefFoundError er) {
+ System.out.println("NoClassDefFoundError");
+ } catch (ClassNotFoundException e) {
+ System.out.println("ClassNotFoundException");
+ }
}
}
public static class Main {
public static void main(String[] args) {
- new LibraryClass().foo();
+ TestClass.test();
}
}
}