Merge commit '9284765f' into dev-release
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 4864e0b..c6d8571 100644
--- a/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
+++ b/src/main/java/com/android/tools/r8/androidapi/ApiReferenceStubber.java
@@ -226,21 +226,7 @@
DexLibraryClass libraryClass,
ThrowExceptionCode throwExceptionCode) {
DexItemFactory factory = appView.dexItemFactory();
- if (libraryClass.getType() == factory.objectType
- || libraryClass.getType().toDescriptorString().startsWith("Ljava/")) {
- return;
- }
- // We cannot reliably create a stub that will have the same throwing
- // behavior for all VMs. We only create stubs for exceptions to allow them being present in
- // catch handlers. See b/b/258270051 for more information.
- if (!isThrowable(libraryClass)
- || appView.options().apiModelingOptions().stubNonThrowableClasses) {
- return;
- }
- if (appView
- .options()
- .machineDesugaredLibrarySpecification
- .isSupported(libraryClass.getType())) {
+ if (!appView.options().apiModelingOptions().stubbingEnabledFor(appView, libraryClass)) {
return;
}
Set<ProgramDefinition> contexts = referencingContexts.get(libraryClass);
@@ -277,16 +263,4 @@
},
ignored -> {});
}
-
- private boolean isThrowable(DexLibraryClass libraryClass) {
- DexClass current = libraryClass;
- while (current.getSuperType() != null) {
- DexType superType = current.getSuperType();
- if (superType == factory.throwableType) {
- return true;
- }
- current = appView.definitionFor(current.getSuperType());
- }
- return false;
- }
}
diff --git a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
index 5f0d225..4300df0 100644
--- a/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
+++ b/src/main/java/com/android/tools/r8/dex/ApplicationWriter.java
@@ -769,7 +769,8 @@
List<InnerClassAttribute> innerClasses = clazz.getInnerClasses();
if (enclosingMethod == null
&& innerClasses.isEmpty()
- && clazz.getClassSignature().hasNoSignature()) {
+ && clazz.getClassSignature().hasNoSignature()
+ && !clazz.isInANest()) {
return;
}
diff --git a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
index d838596..411e5ee 100644
--- a/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
+++ b/src/main/java/com/android/tools/r8/graph/DexItemFactory.java
@@ -142,6 +142,7 @@
public final DexString shortDescriptor = createString("S");
public final DexString voidDescriptor = createString("V");
public final DexString descriptorSeparator = createString("/");
+ public final DexString javaDescriptorPrefix = createString("Ljava/");
private final DexString booleanArrayDescriptor = createString("[Z");
private final DexString byteArrayDescriptor = createString("[B");
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
index 100d61a..810f2de 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinClassInfo.java
@@ -20,6 +20,8 @@
import com.android.tools.r8.utils.Pair;
import com.android.tools.r8.utils.Reporter;
import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Sets;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -157,7 +159,6 @@
keepByteCode,
extensionInformation,
originalMembersWithKotlinInfo);
- String companionObjectName = setCompanionObject(kmClass, hostClass, reporter);
KotlinTypeReference anonymousObjectOrigin = getAnonymousObjectOrigin(kmClass, factory);
boolean nameCanBeDeducedFromClassOrOrigin =
kmClass.name.equals(
@@ -176,7 +177,7 @@
getSuperTypes(kmClass.getSupertypes(), factory, reporter),
getSealedSubClasses(kmClass.getSealedSubclasses(), factory),
getNestedClasses(hostClass, kmClass.getNestedClasses(), factory),
- kmClass.getEnumEntries(),
+ setEnumEntries(kmClass, hostClass),
KotlinVersionRequirementInfo.create(kmClass.getVersionRequirements()),
anonymousObjectOrigin,
packageName,
@@ -187,7 +188,7 @@
KotlinTypeInfo.create(kmClass.getInlineClassUnderlyingType(), factory, reporter),
originalMembersWithKotlinInfo,
JvmExtensionsKt.getJvmFlags(kmClass),
- companionObjectName);
+ setCompanionObject(kmClass, hostClass, reporter));
}
private static KotlinTypeReference getAnonymousObjectOrigin(
@@ -248,6 +249,25 @@
return companionObjectName;
}
+ private static List<String> setEnumEntries(KmClass kmClass, DexClass hostClass) {
+ List<String> enumEntries = kmClass.getEnumEntries();
+ if (enumEntries.isEmpty()) {
+ return enumEntries;
+ }
+ Collection<String> enumEntryStrings =
+ enumEntries.size() < 16 ? enumEntries : Sets.newHashSet(enumEntries);
+ hostClass
+ .fields()
+ .forEach(
+ field -> {
+ String fieldName = field.getName().toString();
+ if (enumEntryStrings.contains(fieldName)) {
+ field.setKotlinMemberInfo(new KotlinEnumEntryInfo(fieldName));
+ }
+ });
+ return enumEntries;
+ }
+
@Override
public boolean isClass() {
return true;
@@ -290,14 +310,24 @@
}
// Find a companion object.
boolean foundCompanion = false;
+ int numberOfEnumEntries = 0;
for (DexEncodedField field : clazz.fields()) {
- if (field.getKotlinInfo().isCompanion()) {
+ KotlinFieldLevelInfo kotlinInfo = field.getKotlinInfo();
+ if (kotlinInfo.isCompanion()) {
rewritten |=
- field
- .getKotlinInfo()
+ kotlinInfo
.asCompanion()
.rewrite(kmClass, field.getReference(), appView.getNamingLens());
foundCompanion = true;
+ } else if (kotlinInfo.isEnumEntry()) {
+ KotlinEnumEntryInfo kotlinEnumEntryInfo = kotlinInfo.asEnumEntry();
+ rewritten |=
+ kotlinEnumEntryInfo.rewrite(kmClass, field.getReference(), appView.getNamingLens());
+ if (numberOfEnumEntries >= enumEntries.size()
+ || !enumEntries.get(numberOfEnumEntries).equals(kotlinEnumEntryInfo.getEnumEntry())) {
+ rewritten = true;
+ }
+ numberOfEnumEntries += 1;
}
}
// If we did not find a companion but it was there on input we have to emit a new metadata
@@ -305,6 +335,11 @@
if (!foundCompanion && companionObjectName != null) {
rewritten = true;
}
+ // If we could remove enum entries but were unable to rename them we still have to emit a new
+ // metadata object.
+ if (numberOfEnumEntries < enumEntries.size()) {
+ rewritten = true;
+ }
// Take all not backed constructors because we will never find them in definitions.
for (KotlinConstructorInfo constructorInfo : constructorsWithNoBacking) {
rewritten |= constructorInfo.rewrite(kmClass, null, appView);
@@ -373,8 +408,6 @@
appView,
null);
}
- // TODO(b/154347404): Understand enum entries.
- kmClass.getEnumEntries().addAll(enumEntries);
rewritten |= versionRequirements.rewrite(kmClass::visitVersionRequirement);
if (inlineClassUnderlyingPropertyName != null && inlineClassUnderlyingType != null) {
kmClass.setInlineClassUnderlyingPropertyName(inlineClassUnderlyingPropertyName);
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
new file mode 100644
index 0000000..25e4ca7
--- /dev/null
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinEnumEntryInfo.java
@@ -0,0 +1,47 @@
+// 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.kotlin;
+
+import com.android.tools.r8.graph.DexDefinitionSupplier;
+import com.android.tools.r8.graph.DexField;
+import com.android.tools.r8.graph.DexString;
+import com.android.tools.r8.naming.NamingLens;
+import kotlinx.metadata.KmClassVisitor;
+
+// Structure around a kotlin enum value that can be assigned to a field.
+public class KotlinEnumEntryInfo implements KotlinFieldLevelInfo {
+
+ private final String enumEntry;
+
+ public KotlinEnumEntryInfo(String enumEntry) {
+ this.enumEntry = enumEntry;
+ }
+
+ @Override
+ public boolean isEnumEntry() {
+ return true;
+ }
+
+ @Override
+ public KotlinEnumEntryInfo asEnumEntry() {
+ return this;
+ }
+
+ boolean rewrite(KmClassVisitor visitor, DexField field, NamingLens lens) {
+ DexString dexString = lens.lookupName(field);
+ String finalName = dexString.toString();
+ visitor.visitEnumEntry(finalName);
+ return !finalName.equals(enumEntry);
+ }
+
+ @Override
+ public void trace(DexDefinitionSupplier definitionSupplier) {
+ // Do nothing.
+ }
+
+ public String getEnumEntry() {
+ return enumEntry;
+ }
+}
diff --git a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
index 41f23c1..7c6a6c1 100644
--- a/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
+++ b/src/main/java/com/android/tools/r8/kotlin/KotlinMemberLevelInfo.java
@@ -43,4 +43,12 @@
default KotlinPropertyInfo asProperty() {
return null;
}
+
+ default boolean isEnumEntry() {
+ return false;
+ }
+
+ default KotlinEnumEntryInfo asEnumEntry() {
+ return null;
+ }
}
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 b726531..b4099ee 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -1832,6 +1832,40 @@
public void disableStubbingOfClasses() {
enableStubbingOfClasses = false;
}
+
+ public boolean stubbingEnabledFor(AppView<?> appView, DexLibraryClass libraryClass) {
+ if (libraryClass.getType() == appView.dexItemFactory().objectType
+ || libraryClass
+ .getType()
+ .getDescriptor()
+ .startsWith(appView.dexItemFactory().javaDescriptorPrefix)) {
+ return false;
+ }
+ // Check if desugared library will bridge the type.
+ if (appView
+ .options()
+ .machineDesugaredLibrarySpecification
+ .isSupported(libraryClass.getType())) {
+ return false;
+ }
+ // We cannot reliably create a stub that will have the same throwing behavior for all VMs. We
+ // only create stubs for exceptions to allow them being present in catch handlers. See
+ // b/258270051 for more information. Note that throwables in the java namespace are not
+ // stubbed (bailout above).
+ return isThrowable(appView, libraryClass) || stubNonThrowableClasses;
+ }
+
+ private boolean isThrowable(AppView<?> appView, DexLibraryClass libraryClass) {
+ DexClass current = libraryClass;
+ while (current.getSuperType() != null) {
+ DexType superType = current.getSuperType();
+ if (superType == appView.dexItemFactory().throwableType) {
+ return true;
+ }
+ current = appView.definitionFor(current.getSuperType());
+ }
+ return false;
+ }
}
public static class ProtoShrinkingOptions {
diff --git a/src/test/java/com/android/tools/r8/JvmTestBuilder.java b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
index 8b56639..49ce60e 100644
--- a/src/test/java/com/android/tools/r8/JvmTestBuilder.java
+++ b/src/test/java/com/android/tools/r8/JvmTestBuilder.java
@@ -6,6 +6,7 @@
import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.errors.Unimplemented;
import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
import com.android.tools.r8.utils.AndroidApp;
import com.android.tools.r8.utils.FileUtils;
import com.google.common.collect.ObjectArrays;
@@ -189,7 +190,11 @@
}
public JvmTestBuilder addAndroidBuildVersion() {
- addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=10000");
+ return addAndroidBuildVersion(AndroidApiLevel.ANDROID_PLATFORM);
+ }
+
+ public JvmTestBuilder addAndroidBuildVersion(AndroidApiLevel apiLevel) {
+ addVmArguments("-D" + AndroidBuildVersion.PROPERTY + "=" + apiLevel.getLevel());
return addProgramClasses(AndroidBuildVersion.class);
}
}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
new file mode 100644
index 0000000..f859e30
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithDifferentApiLevelTest.java
@@ -0,0 +1,199 @@
+// 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 com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+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.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+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.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+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 ApiModelIndirectTargetWithDifferentApiLevelTest extends TestBase {
+
+ private final AndroidApiLevel ifaceApiLevel = AndroidApiLevel.M;
+ private final AndroidApiLevel classApiLevel = AndroidApiLevel.M;
+ private final AndroidApiLevel classMethodApiLevel = AndroidApiLevel.O_MR1;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private boolean isGreaterOrEqualToIfaceMockLevel() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(ifaceApiLevel);
+ }
+
+ private boolean isGreaterOrEqualToClassMethodMockLevel() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(classMethodApiLevel);
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClasses(Main.class, ProgramJoiner.class)
+ .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+ .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, classApiLevel))
+ .apply(setMockApiLevelForClass(LibraryClass.class, classApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryClass.class.getDeclaredMethod("foo"), classMethodApiLevel))
+ .apply(setMockApiLevelForClass(LibraryInterface.class, ifaceApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryInterface.class.getDeclaredMethod("foo"), ifaceApiLevel));
+ }
+
+ private void setupRunEnvironment(TestCompileResult<?, ?> compileResult) {
+ compileResult
+ .applyIf(
+ isGreaterOrEqualToClassMethodMockLevel(),
+ b -> b.addRunClasspathClasses(LibraryClass.class, LibraryInterface.class))
+ .applyIf(
+ !isGreaterOrEqualToClassMethodMockLevel() && isGreaterOrEqualToIfaceMockLevel(),
+ b ->
+ b.addRunClasspathClasses(LibraryInterface.class)
+ .addRunClasspathClassFileData(
+ transformer(LibraryClass.class).removeMethodsWithName("foo").transform()));
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClasses(Main.class, ProgramJoiner.class)
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false));
+ }
+
+ @Test
+ public void testD8Debug() throws Exception {
+ testForD8(parameters.getBackend())
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .apply(this::setupRunEnvironment)
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false));
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ testForD8(parameters.getBackend())
+ .setMode(CompilationMode.RELEASE)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .apply(this::setupRunEnvironment)
+ .inspect(this::inspect)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(ProgramJoiner.class)
+ .compile()
+ .inspect(this::inspect)
+ .apply(this::setupRunEnvironment)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, true));
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult, boolean isR8) {
+ if (isGreaterOrEqualToClassMethodMockLevel()) {
+ runResult.assertSuccessWithOutputLines("LibraryClass::foo");
+ } else if (isGreaterOrEqualToIfaceMockLevel()) {
+ if (isR8) {
+ runResult.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ } else {
+ runResult.assertFailureWithErrorThatThrows(AbstractMethodError.class);
+ }
+ } else if (isR8 && parameters.isCfRuntime()) {
+ // TODO(b/254510678): R8 should not rebind to the library method.
+ runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ } else {
+ runResult.assertSuccessWithOutputLines("Hello World");
+ }
+ runResult.applyIf(
+ !isGreaterOrEqualToIfaceMockLevel()
+ && parameters.isDexRuntime()
+ && parameters.getDexRuntimeVersion().isNewerThanOrEqual(Version.V7_0_0),
+ result -> result.assertStderrMatches(not(containsString("This dex file is invalid"))));
+ }
+
+ private void inspect(CodeInspector inspector) throws Exception {
+ // TODO(b/254510678): We should outline the call to ProgramJoiner.foo.
+ verifyThat(
+ inspector,
+ parameters,
+ Reference.method(
+ Reference.classFromClass(ProgramJoiner.class),
+ "foo",
+ Collections.emptyList(),
+ null))
+ .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+ }
+
+ // Only present from api level 23.
+ public static class LibraryClass {
+
+ // Only present from api level 27;
+ public void foo() {
+ System.out.println("LibraryClass::foo");
+ }
+ }
+
+ // Present from 23
+ public interface LibraryInterface {
+
+ // Present from 23
+ void foo();
+ }
+
+ public static class ProgramJoiner extends LibraryClass implements LibraryInterface {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 23) {
+ new ProgramJoiner().foo();
+ } else {
+ System.out.println("Hello World");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
new file mode 100644
index 0000000..a0cc571
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelIndirectTargetWithSameApiLevelTest.java
@@ -0,0 +1,181 @@
+// 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 com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForClass;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForDefaultInstanceInitializer;
+import static com.android.tools.r8.apimodel.ApiModelingTestHelper.setMockApiLevelForMethod;
+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.SingleTestRunResult;
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestCompileResult;
+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.references.Reference;
+import com.android.tools.r8.testing.AndroidBuildVersion;
+import com.android.tools.r8.utils.AndroidApiLevel;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import java.util.Collections;
+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;
+
+// The difference between this test and the ApiModelIndirectTargetWithDifferentApiLevelTest is
+// what we should rebind to. If there is a method definition in the class hierarchy and it has
+// the same api-level as one in an interface we should still pick the class.
+@RunWith(Parameterized.class)
+public class ApiModelIndirectTargetWithSameApiLevelTest extends TestBase {
+
+ private final AndroidApiLevel mockApiLevel = AndroidApiLevel.M;
+
+ @Parameter public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+ }
+
+ private boolean isGreaterOrEqualToMockLevel() {
+ return parameters.getApiLevel().isGreaterThanOrEqualTo(mockApiLevel);
+ }
+
+ private void setupTestBuilder(TestCompilerBuilder<?, ?, ?, ?, ?> testBuilder) throws Exception {
+ testBuilder
+ .addProgramClasses(Main.class, ProgramJoiner.class)
+ .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .setMinApi(parameters.getApiLevel())
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .apply(ApiModelingTestHelper::enableStubbingOfClasses)
+ .apply(setMockApiLevelForDefaultInstanceInitializer(LibraryClass.class, mockApiLevel))
+ .apply(setMockApiLevelForClass(LibraryClass.class, mockApiLevel))
+ .apply(setMockApiLevelForMethod(LibraryClass.class.getDeclaredMethod("foo"), mockApiLevel))
+ .apply(setMockApiLevelForClass(LibraryInterface.class, mockApiLevel))
+ .apply(
+ setMockApiLevelForMethod(
+ LibraryInterface.class.getDeclaredMethod("foo"), mockApiLevel));
+ }
+
+ private void setupRunEnvironment(TestCompileResult<?, ?> compileResult) {
+ compileResult.applyIf(
+ isGreaterOrEqualToMockLevel(),
+ b -> b.addRunClasspathClasses(LibraryClass.class, LibraryInterface.class));
+ }
+
+ @Test
+ public void testReference() throws Exception {
+ assumeTrue(parameters.isCfRuntime() && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClasses(Main.class, ProgramJoiner.class)
+ .addAndroidBuildVersion(parameters.getApiLevel())
+ .addLibraryClasses(LibraryClass.class, LibraryInterface.class)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false));
+ }
+
+ @Test
+ public void testD8Debug() throws Exception {
+ testForD8(parameters.getBackend())
+ .setMode(CompilationMode.DEBUG)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .apply(this::setupRunEnvironment)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testD8Release() throws Exception {
+ testForD8(parameters.getBackend())
+ .setMode(CompilationMode.RELEASE)
+ .apply(this::setupTestBuilder)
+ .compile()
+ .apply(this::setupRunEnvironment)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, false))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .apply(this::setupTestBuilder)
+ .addKeepMainRule(Main.class)
+ .addKeepClassAndMembersRules(ProgramJoiner.class)
+ .compile()
+ .inspect(this::inspect)
+ .apply(this::setupRunEnvironment)
+ .run(parameters.getRuntime(), Main.class)
+ .apply(result -> checkOutput(result, true));
+ }
+
+ private void checkOutput(SingleTestRunResult<?> runResult, boolean isR8) {
+ if (isGreaterOrEqualToMockLevel()) {
+ runResult.assertSuccessWithOutputLines("LibraryClass::foo");
+ } else if (isR8 && parameters.isCfRuntime()) {
+ // TODO(b/254510678): R8 should not rebind to the library method.
+ runResult.assertFailureWithErrorThatThrows(NoClassDefFoundError.class);
+ } else {
+ runResult.assertSuccessWithOutputLines("Hello World");
+ }
+ 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) throws Exception {
+ // TODO(b/254510678): We should outline the call to ProgramClass.foo.
+ verifyThat(
+ inspector,
+ parameters,
+ Reference.method(
+ Reference.classFromClass(ProgramJoiner.class),
+ "foo",
+ Collections.emptyList(),
+ null))
+ .isNotOutlinedFrom(Main.class.getDeclaredMethod("main", String[].class));
+ }
+
+ // Only present from api level 23.
+ public static class LibraryClass {
+
+ // Only present from api level 27;
+ public void foo() {
+ System.out.println("LibraryClass::foo");
+ }
+ }
+
+ // Present from 23
+ public interface LibraryInterface {
+
+ // Present from 23
+ void foo();
+ }
+
+ public static class ProgramJoiner extends LibraryClass implements LibraryInterface {}
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ if (AndroidBuildVersion.VERSION >= 23) {
+ new ProgramJoiner().foo();
+ } else {
+ System.out.println("Hello World");
+ }
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
index 016a147..20d345a 100644
--- a/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
+++ b/src/test/java/com/android/tools/r8/apimodel/ApiModelingTestHelper.java
@@ -213,6 +213,11 @@
}
static ApiModelingMethodVerificationHelper verifyThat(
+ CodeInspector inspector, TestParameters parameters, MethodReference method) {
+ return new ApiModelingMethodVerificationHelper(inspector, parameters, method);
+ }
+
+ static ApiModelingMethodVerificationHelper verifyThat(
CodeInspector inspector, TestParameters parameters, Constructor method) {
return new ApiModelingMethodVerificationHelper(
inspector, parameters, Reference.methodFromMethod(method));
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
new file mode 100644
index 0000000..9829235
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexShrinkingTest.java
@@ -0,0 +1,349 @@
+// 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.desugar.nestaccesscontrol;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.utils.AndroidApiLevel;
+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.android.tools.r8.utils.codeinspector.TypeSubject;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+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;
+import org.objectweb.asm.ClassWriter;
+import org.objectweb.asm.Label;
+import org.objectweb.asm.MethodVisitor;
+import org.objectweb.asm.Opcodes;
+
+@RunWith(Parameterized.class)
+public class NestAttributesInDexShrinkingTest extends NestAttributesInDexTestBase
+ implements Opcodes {
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static List<Object[]> data() {
+ return buildParameters(
+ getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build());
+ }
+
+ private static final String EXPECTED_OUTPUT = StringUtils.lines("Hello, world!");
+
+ @Test
+ public void testRuntime() throws Exception {
+ assumeTrue(
+ parameters.isCfRuntime()
+ && isRuntimeWithNestSupport(parameters.asCfRuntime())
+ && parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
+ testForJvm()
+ .addProgramClassFileData(
+ dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
+ .run(parameters.getRuntime(), "Host")
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ private void inspect(CodeInspector inspector, boolean expectNestAttributes) {
+ ClassSubject host = inspector.clazz("Host");
+ ClassSubject member1 = inspector.clazz("Host$Member1");
+ ClassSubject member2 = inspector.clazz("Host$Member2");
+ if (expectNestAttributes) {
+ assertEquals(
+ ImmutableList.of(member2.asTypeSubject(), member1.asTypeSubject()),
+ host.getFinalNestMembersAttribute());
+ } else {
+ assertEquals(0, host.getFinalNestMembersAttribute().size());
+ }
+ TypeSubject expectedNestHostAttribute = expectNestAttributes ? host.asTypeSubject() : null;
+ assertEquals(expectedNestHostAttribute, member1.getFinalNestHostAttribute());
+ assertEquals(expectedNestHostAttribute, member2.getFinalNestHostAttribute());
+ }
+
+ private void expectNestAttributes(CodeInspector inspector) {
+ inspect(inspector, true);
+ }
+
+ private void expectNoNestAttributes(CodeInspector inspector) {
+ inspect(inspector, false);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+ assertFalse(parameters.getApiLevel().getLevel() > 33);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
+ .addKeepMainRule("Host")
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .compile()
+ .inspect(inspector -> assertEquals(1, inspector.allClasses().size()))
+ .run(parameters.getRuntime(), "Host")
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ @Test
+ public void testR8KeepHostWithPrivateMembers() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+ assertFalse(parameters.getApiLevel().getLevel() > 33);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ dumpHost(ACC_PRIVATE), dumpMember1(ACC_PRIVATE), dumpMember2(ACC_PRIVATE))
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule("Host")
+ .addKeepClassAndMembersRules("Host", "Host$Member1", "Host$Member2")
+ .compile()
+ .inspect(this::expectNestAttributes)
+ .run(parameters.getRuntime(), "Host")
+ .applyIf(
+ parameters.isCfRuntime(),
+ r -> r.assertSuccessWithOutput(EXPECTED_OUTPUT),
+ r -> r.assertFailureWithErrorThatThrows(IllegalAccessError.class));
+ }
+
+ @Test
+ public void testR8KeepHostWithPublicMembers() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ // TODO(b/247047415): Update test when a DEX VM natively supporting nests is added.
+ assertFalse(parameters.getApiLevel().getLevel() > 33);
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(
+ dumpHost(ACC_PUBLIC), dumpMember1(ACC_PUBLIC), dumpMember2(ACC_PUBLIC))
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule("Host")
+ .addKeepClassAndMembersRules("Host", "Host$Member1", "Host$Member2")
+ .compile()
+ .inspect(this::expectNoNestAttributes)
+ .run(parameters.getRuntime(), "Host")
+ .assertSuccessWithOutput(EXPECTED_OUTPUT);
+ }
+
+ /*
+ Dump of:
+
+ public class Host {
+ public static void main(String[] args) {
+ new Host().h1();
+ System.out.println();
+ }
+
+ static class Member1 {
+ private void m(Host host) { // private or public
+ host.h2("Hello");
+ }
+ }
+
+ static class Member2 {
+ private void m(Host host) { // private or public
+ host.h2(", world!");
+ }
+ }
+
+ private void h1() { // private or public
+ new Member1().m(this);
+ new Member2().m(this);
+ }
+
+ private void h2(String message) { // private or public
+ System.out.print(message);
+ }
+ }
+
+ compiled with `-target 11`. Not a transformer here, as transforming the javac post nest
+ access methods is not feasible.
+ */
+
+ public static byte[] dumpHost(int methodAccess) throws Exception {
+ assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_PUBLIC | ACC_SUPER, "Host", null, "java/lang/Object", null);
+ classWriter.visitSource("Host.java", null);
+ classWriter.visitNestMember("Host$Member2");
+ classWriter.visitNestMember("Host$Member1");
+ classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+ classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+ {
+ methodVisitor = classWriter.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(1, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(
+ ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(3, label0);
+ methodVisitor.visitTypeInsn(NEW, "Host");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host", "<init>", "()V", false);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h1", "()V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(4, label1);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "()V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(5, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(methodAccess, "h1", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(18, label0);
+ methodVisitor.visitTypeInsn(NEW, "Host$Member1");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member1", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member1", "m", "(LHost;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(19, label1);
+ methodVisitor.visitTypeInsn(NEW, "Host$Member2");
+ methodVisitor.visitInsn(DUP);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "Host$Member2", "<init>", "()V", false);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host$Member2", "m", "(LHost;)V", false);
+ Label label2 = new Label();
+ methodVisitor.visitLabel(label2);
+ methodVisitor.visitLineNumber(20, label2);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor =
+ classWriter.visitMethod(methodAccess, "h2", "(Ljava/lang/String;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(23, label0);
+ methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitMethodInsn(
+ INVOKEVIRTUAL, "java/io/PrintStream", "print", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(24, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static byte[] dumpMember1(int methodAccess) throws Exception {
+ assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_SUPER, "Host$Member1", null, "java/lang/Object", null);
+ classWriter.visitSource("Host.java", null);
+ classWriter.visitNestHost("Host");
+ classWriter.visitInnerClass("Host$Member1", "Host", "Member1", ACC_STATIC);
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(7, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(9, label0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn("Hello");
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
+ Label label1 = new Label();
+ methodVisitor.visitLabel(label1);
+ methodVisitor.visitLineNumber(10, label1);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+
+ public static byte[] dumpMember2(int methodAccess) throws Exception {
+ assert methodAccess == ACC_PUBLIC || methodAccess == ACC_PRIVATE;
+
+ ClassWriter classWriter = new ClassWriter(0);
+ MethodVisitor methodVisitor;
+
+ classWriter.visit(V11, ACC_SUPER, "Host$Member2", null, "java/lang/Object", null);
+ classWriter.visitSource("Host.java", null);
+ classWriter.visitNestHost("Host");
+ classWriter.visitInnerClass("Host$Member2", "Host", "Member2", ACC_STATIC);
+ {
+ methodVisitor = classWriter.visitMethod(0, "<init>", "()V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(13, label0);
+ methodVisitor.visitVarInsn(ALOAD, 0);
+ methodVisitor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(1, 1);
+ methodVisitor.visitEnd();
+ }
+ {
+ methodVisitor = classWriter.visitMethod(methodAccess, "m", "(LHost;)V", null, null);
+ methodVisitor.visitCode();
+ Label label0 = new Label();
+ methodVisitor.visitLabel(label0);
+ methodVisitor.visitLineNumber(14, label0);
+ methodVisitor.visitVarInsn(ALOAD, 1);
+ methodVisitor.visitLdcInsn(", world!");
+ methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "Host", "h2", "(Ljava/lang/String;)V", false);
+ methodVisitor.visitInsn(RETURN);
+ methodVisitor.visitMaxs(2, 2);
+ methodVisitor.visitEnd();
+ }
+ classWriter.visitEnd();
+
+ return classWriter.toByteArray();
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
index b5ad377..25f211f 100644
--- a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTest.java
@@ -9,8 +9,8 @@
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
-import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestRunResult;
import com.android.tools.r8.TestRuntime.CfVm;
import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member1;
import com.android.tools.r8.desugar.nestaccesscontrol.NestAttributesInDexTest.Host.Member2;
@@ -36,7 +36,7 @@
import org.objectweb.asm.Opcodes;
@RunWith(Parameterized.class)
-public class NestAttributesInDexTest extends TestBase {
+public class NestAttributesInDexTest extends NestAttributesInDexTestBase {
@Parameter() public TestParameters parameters;
@@ -52,11 +52,35 @@
"true", "true", "true", "false", "false", "true", "true", "true", "false", "false",
"true", "true", "true");
+ // Right now R8 removes the nest attributes if they are not required for runtime execution, so
+ // most reflective calls will return false.
+ private static final String R8_EXPECTED_OUTPUT =
+ StringUtils.lines(
+ "false", "false", "false", "true", "false", "false", "true", "false", "false", "false",
+ "false", "false", "true", "false", "false", "false", "false", "false", "true", "false",
+ "false", "true", "true", "true");
+
+ private void checkResult(TestRunResult<?> result) {
+ if (isRuntimeWithNestSupport(parameters.getRuntime())) {
+ result.assertSuccessWithOutput(EXPECTED_OUTPUT);
+ } else {
+ result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+ }
+
+ private void checkResultR8(TestRunResult<?> result) {
+ if (isRuntimeWithNestSupport(parameters.getRuntime())) {
+ result.assertSuccessWithOutput(R8_EXPECTED_OUTPUT);
+ } else {
+ result.assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ }
+ }
+
@Test
public void testRuntime() throws Exception {
assumeTrue(
parameters.isCfRuntime()
- && parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK11)
+ && isRuntimeWithNestSupport(parameters.asCfRuntime())
&& parameters.getApiLevel().isEqualTo(AndroidApiLevel.B));
testForJvm()
.addProgramClassFileData(getTransformedClasses())
@@ -93,8 +117,7 @@
.compile()
.inspect(inspector -> inspect(inspector, true))
.run(parameters.getRuntime(), TestClass.class)
- // No Art versions have support for nest attributes yet.
- .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ .apply(this::checkResult);
}
@Test
@@ -109,7 +132,84 @@
.compile()
.inspect(inspector -> inspect(inspector, false))
.run(parameters.getRuntime(), TestClass.class)
- .assertFailureWithErrorThatThrows(NoSuchMethodError.class);
+ .apply(this::checkResult);
+ }
+
+ @Test
+ public void testR8NoKeep() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(OtherHost.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule(TestClass.class)
+ .compile()
+ // Don't expect any nest info. The classes Host, Member1, Member2 and OtherHost remains
+ // due to the use of class constants in the code, but they have no methods so no nest
+ // attributes are required for runtime execution.
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkResultR8);
+ }
+
+ @Test
+ public void testR8KeepHost() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(OtherHost.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(Host.class)
+ .compile()
+ // Don't expect any nest info. Class Host is kept and the classes Member1, Members and
+ // OtherHost remains due to the use of class constants in the code, but they have no methods
+ // so no nest attributes are required for runtime execution.
+ // TODO(b/130716228#comment5): How to keep nest attributes?
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkResultR8);
+ }
+
+ @Test
+ public void testR8KeepMembers() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(OtherHost.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(Member1.class, Member2.class)
+ .compile()
+ // Don't expect any nest info. Member1 and Member2 are kept and the classes Host and
+ // OtherHost remains due to the use of class constants in the code, but they have no
+ // methods so no nest attributes are required for runtime execution.
+ // TODO(b/130716228#comment5): How to keep nest attributes?
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkResultR8);
+ }
+
+ @Test
+ public void testR8KeepBoth() throws Exception {
+ assumeTrue(parameters.isDexRuntime() || isRuntimeWithNestSupport(parameters.asCfRuntime()));
+ testForR8(parameters.getBackend())
+ .addProgramClassFileData(getTransformedClasses())
+ .addProgramClasses(OtherHost.class)
+ .setMinApi(parameters.getApiLevel())
+ .addOptionsModification(options -> options.emitNestAnnotationsInDex = true)
+ .addKeepMainRule(TestClass.class)
+ .addKeepClassRules(Host.class, Member1.class, Member2.class)
+ .compile()
+ // Don't expect any nest info. All of Host, Member1 and Member2 are kept,
+ // but they have no methods so no nest attributes are required for runtime execution.
+ // TODO(b/130716228#comment5): How to keep nest attributes?
+ .inspect(inspector -> inspect(inspector, false))
+ .run(parameters.getRuntime(), TestClass.class)
+ .apply(this::checkResultR8);
}
public Collection<byte[]> getTransformedClasses() throws Exception {
diff --git a/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTestBase.java b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTestBase.java
new file mode 100644
index 0000000..9f67110
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/nestaccesscontrol/NestAttributesInDexTestBase.java
@@ -0,0 +1,33 @@
+// 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.desugar.nestaccesscontrol;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestRuntime;
+import com.android.tools.r8.TestRuntime.CfRuntime;
+import com.android.tools.r8.TestRuntime.CfVm;
+import com.android.tools.r8.TestRuntime.DexRuntime;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public abstract class NestAttributesInDexTestBase extends TestBase {
+
+ protected boolean isRuntimeWithNestSupport(TestRuntime runtime) {
+ if (runtime.isCf()) {
+ return isRuntimeWithNestSupport(runtime.asCf());
+ } else {
+ return isRuntimeWithNestSupport(runtime.asDex());
+ }
+ }
+
+ protected boolean isRuntimeWithNestSupport(CfRuntime runtime) {
+ return runtime.isNewerThanOrEqual(CfVm.JDK11);
+ }
+
+ protected boolean isRuntimeWithNestSupport(DexRuntime runtime) {
+ // No Art versions have support for nest attributes yet.
+ return false;
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
index 939eb30..b7c8c16 100644
--- a/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
+++ b/src/test/java/com/android/tools/r8/kotlin/metadata/MetadataRewriteEnumTest.java
@@ -3,16 +3,23 @@
// BSD-style license that can be found in the LICENSE file.
package com.android.tools.r8.kotlin.metadata;
+import static com.android.tools.r8.cf.methodhandles.fields.ClassFieldMethodHandleTest.Main.assertEquals;
import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertTrue;
import com.android.tools.r8.KotlinTestParameters;
import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.ToolHelper.ProcessResult;
import com.android.tools.r8.utils.codeinspector.ClassSubject;
+import com.android.tools.r8.utils.codeinspector.KmClassSubject;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.List;
+import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
@@ -58,7 +65,7 @@
}
@Test
- public void testR8() throws Exception {
+ public void testKotlincFailsRenamed() throws Exception {
R8TestCompileResult r8libResult =
testForR8(parameters.getBackend())
.addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
@@ -74,24 +81,60 @@
assertThat(direction, isPresentAndNotRenamed());
direction.allFields().forEach(field -> assertTrue(field.isRenamed()));
});
- Path libJar = r8libResult.writeToZip();
+ ProcessResult kotlinCompileResult =
+ kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
+ .addClasspathFiles(r8libResult.writeToZip())
+ .addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main"))
+ .setOutputPath(temp.newFolder().toPath())
+ .compileRaw();
+ assertEquals(1, kotlinCompileResult.exitCode);
+ assertThat(kotlinCompileResult.stderr, containsString("unresolved reference"));
+ assertThat(kotlinCompileResult.stderr, containsString("Direction.UP"));
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ R8TestCompileResult r8libResult =
+ testForR8(parameters.getBackend())
+ .addProgramFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
+ .addClasspathFiles(kotlinc.getKotlinStdlibJar())
+ .addKeepKotlinMetadata()
+ .addKeepEnumsRule()
+ .addKeepClassRules(DIRECTION_TYPE_NAME)
+ .addKeepClassAndMembersRulesWithAllowObfuscation(DIRECTION_TYPE_NAME)
+ .compile()
+ .inspect(
+ inspector -> {
+ ClassSubject direction = inspector.clazz(DIRECTION_TYPE_NAME);
+ assertThat(direction, isPresentAndNotRenamed());
+ direction.allFields().forEach(field -> assertTrue(field.isRenamed()));
+ KmClassSubject kmClass = direction.getKmClass();
+ List<String> expectedEnumNames = Arrays.asList("a", "b", "c", "d");
+ Assert.assertEquals(expectedEnumNames, kmClass.getEnumEntries());
+ });
Path output =
kotlinc(parameters.getRuntime().asCf(), kotlinc, targetVersion)
- .addClasspathFiles(libJar)
+ .addClasspathFiles(jarMap.getForConfiguration(kotlinc, targetVersion))
.addSourceFiles(getKotlinFileInTest(PKG_PREFIX + "/enum_app", "main"))
.compile();
Path path =
testForR8(parameters.getBackend())
- .addClasspathFiles(kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), libJar)
+ .addClasspathFiles(
+ kotlinc.getKotlinStdlibJar(),
+ kotlinc.getKotlinReflectJar(),
+ jarMap.getForConfiguration(kotlinc, targetVersion))
.addProgramFiles(output)
.addKeepAllClassesRule()
.addApplyMapping(r8libResult.getProguardMap())
.compile()
.writeToZip();
- // TODO(b/259389417): We should rename enum values in metadata.
testForRuntime(parameters)
- .addProgramFiles(libJar, kotlinc.getKotlinStdlibJar(), kotlinc.getKotlinReflectJar(), path)
+ .addProgramFiles(
+ r8libResult.writeToZip(),
+ kotlinc.getKotlinStdlibJar(),
+ kotlinc.getKotlinReflectJar(),
+ path)
.run(parameters.getRuntime(), PKG + ".enum_app.MainKt")
- .assertFailureWithErrorThatThrows(NoSuchFieldError.class);
+ .assertSuccessWithOutputLines(EXPECTED);
}
}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
new file mode 100644
index 0000000..8cc0379
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInCatchRangeTest.java
@@ -0,0 +1,69 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+// Regression test for issue found in b/259986613
+@RunWith(Parameterized.class)
+public class NewArrayInCatchRangeTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayInCatchRangeTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayInCatchRangeTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ int[] array = new int[1];
+ try {
+ array[0] = value;
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
new file mode 100644
index 0000000..ab11b3d
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/rewrite/arrays/NewArrayInTwoCatchRangesTest.java
@@ -0,0 +1,77 @@
+// 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.rewrite.arrays;
+
+import static org.junit.Assume.assumeTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.utils.StringUtils;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+@RunWith(Parameterized.class)
+public class NewArrayInTwoCatchRangesTest extends TestBase {
+
+ static final String EXPECTED = StringUtils.lines("1");
+
+ private final TestParameters parameters;
+
+ @Parameterized.Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimes().withAllApiLevels().build();
+ }
+
+ public NewArrayInTwoCatchRangesTest(TestParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ @Test
+ public void test() throws Exception {
+ testForRuntime(parameters)
+ .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ @Test
+ public void testReleaseD8() throws Exception {
+ assumeTrue(parameters.isDexRuntime());
+ testForD8(parameters.getBackend())
+ .release()
+ .setMinApi(parameters.getApiLevel())
+ .addInnerClasses(NewArrayInTwoCatchRangesTest.class)
+ .run(parameters.getRuntime(), TestClass.class)
+ .assertSuccessWithOutput(EXPECTED);
+ }
+
+ static class TestClass {
+
+ public static int foo() {
+ int value = 1;
+ try {
+ int[] array = new int[2];
+ try {
+ array[0] = value;
+ try {
+ array[1] = value;
+ } catch (RuntimeException e) {
+ return array[1];
+ }
+ } catch (RuntimeException e) {
+ return array[0];
+ }
+ return array[0];
+ } catch (RuntimeException e) {
+ return 42;
+ }
+ }
+
+ public static void main(String[] args) {
+ System.out.println(foo());
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java
new file mode 100644
index 0000000..247b780
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/shaking/librarymethodoverride/LibraryMethodOverrideOfClassMethodWithInterfaceTest.java
@@ -0,0 +1,101 @@
+// 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.shaking.librarymethodoverride;
+
+import static org.junit.Assert.assertTrue;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.graph.DexEncodedMethod;
+import com.android.tools.r8.graph.DexItemFactory;
+import com.android.tools.r8.graph.DexProgramClass;
+import com.android.tools.r8.shaking.AppInfoWithLiveness;
+import com.android.tools.r8.shaking.Enqueuer;
+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 LibraryMethodOverrideOfClassMethodWithInterfaceTest extends TestBase {
+
+ private final String EXPECTED = "ProgramClass::foo";
+
+ @Parameter() public TestParameters parameters;
+
+ @Parameters(name = "{0}")
+ public static TestParametersCollection data() {
+ return getTestParameters().withAllRuntimesAndApiLevels().build();
+ }
+
+ @Test
+ public void testRuntime() throws Exception {
+ testForRuntime(parameters)
+ .addProgramClasses(ProgramClass.class, ProgramInterface.class, Main.class)
+ .addLibraryClasses(LibraryClass.class)
+ .addRunClasspathFiles(buildOnDexRuntime(parameters, LibraryClass.class))
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ @Test
+ public void testR8() throws Exception {
+ testForR8(parameters.getBackend())
+ .addProgramClasses(ProgramClass.class, ProgramInterface.class, Main.class)
+ .addDefaultRuntimeLibrary(parameters)
+ .addLibraryClasses(LibraryClass.class)
+ .addOptionsModification(
+ options -> options.testing.enqueuerInspector = this::verifyLibraryOverrideInformation)
+ .setMinApi(parameters.getApiLevel())
+ .addKeepMainRule(Main.class)
+ .compile()
+ .addBootClasspathClasses(LibraryClass.class)
+ .run(parameters.getRuntime(), Main.class)
+ .assertSuccessWithOutputLines(EXPECTED);
+ }
+
+ private void verifyLibraryOverrideInformation(AppInfoWithLiveness appInfo, Enqueuer.Mode mode) {
+ DexItemFactory dexItemFactory = appInfo.dexItemFactory();
+ DexProgramClass clazz =
+ appInfo
+ .definitionFor(dexItemFactory.createType(descriptor(ProgramInterface.class)))
+ .asProgramClass();
+ DexEncodedMethod method =
+ clazz.lookupVirtualMethod(m -> m.getReference().name.toString().equals("foo"));
+ // TODO(b/259531498): We should not mark the interface method as overriding.
+ assertTrue(method.isLibraryMethodOverride().isTrue());
+ }
+
+ public abstract static class LibraryClass {
+
+ abstract void foo();
+ }
+
+ public interface ProgramInterface {
+
+ void foo();
+ }
+
+ public static class ProgramClass extends LibraryClass implements ProgramInterface {
+
+ @Override
+ public void foo() {
+ System.out.println("ProgramClass::foo");
+ }
+ }
+
+ public static class Main {
+
+ public static void main(String[] args) {
+ callFoo(new ProgramClass());
+ }
+
+ public static void callFoo(ProgramInterface i) {
+ i.foo();
+ }
+ }
+}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
index d87e74e..1da8375 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/AbsentKmClassSubject.java
@@ -141,6 +141,11 @@
}
@Override
+ public List<String> getEnumEntries() {
+ return null;
+ }
+
+ @Override
public String getCompanionObject() {
return null;
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
index cc9bfd0..34f7c4c 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/FoundKmClassSubject.java
@@ -127,6 +127,11 @@
}
@Override
+ public List<String> getEnumEntries() {
+ return kmClass.getEnumEntries();
+ }
+
+ @Override
public List<KmTypeParameter> getKmTypeParameters() {
return kmClass.getTypeParameters();
}
diff --git a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
index 9d776d3..84b493a 100644
--- a/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
+++ b/src/test/java/com/android/tools/r8/utils/codeinspector/KmClassSubject.java
@@ -26,5 +26,7 @@
public abstract List<ClassSubject> getSealedSubclasses();
+ public abstract List<String> getEnumEntries();
+
public abstract String getCompanionObject();
}