Check PermittedSubclassesAttribute for missing classes
Bug: b/227160052
Change-Id: Ic2b6c76d589b81a91cfd678c193a439ba0406680
diff --git a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
index 1ea86b5..4bbc625 100644
--- a/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
+++ b/src/main/java/com/android/tools/r8/shaking/Enqueuer.java
@@ -80,6 +80,7 @@
import com.android.tools.r8.graph.MethodResolutionResult.SingleResolutionResult;
import com.android.tools.r8.graph.NestMemberClassAttribute;
import com.android.tools.r8.graph.ObjectAllocationInfoCollectionImpl;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
import com.android.tools.r8.graph.ProgramDefinition;
import com.android.tools.r8.graph.ProgramDerivedContext;
import com.android.tools.r8.graph.ProgramField;
@@ -2098,6 +2099,23 @@
}
}
+ // Mark types in permitted-subclasses attributes referenced.
+ List<PermittedSubclassAttribute> permittedSubclassAttributes =
+ clazz.getPermittedSubclassAttributes();
+ if (!permittedSubclassAttributes.isEmpty()) {
+ BiConsumer<DexType, ProgramDerivedContext> missingClassConsumer =
+ options.reportMissingClassesInPermittedSubclassesAttributes
+ ? this::reportMissingClass
+ : this::ignoreMissingClass;
+ for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
+ recordTypeReference(
+ permittedSubclassAttribute.getPermittedSubclass(),
+ clazz,
+ this::recordNonProgramClass,
+ missingClassConsumer);
+ }
+ }
+
KeepReason reason = KeepReason.reachableFromLiveType(clazz.type);
for (DexType iface : clazz.getInterfaces()) {
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 c284670..83b37c3 100644
--- a/src/main/java/com/android/tools/r8/utils/InternalOptions.java
+++ b/src/main/java/com/android/tools/r8/utils/InternalOptions.java
@@ -876,6 +876,7 @@
public boolean ignoreMissingClasses = false;
public boolean reportMissingClassesInEnclosingMethodAttribute = false;
public boolean reportMissingClassesInInnerClassAttributes = false;
+ public boolean reportMissingClassesInPermittedSubclassesAttributes = false;
public boolean disableGenericSignatureValidation = false;
public boolean disableInnerClassSeparatorValidationWhenRepackaging = false;
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
index 03d9160..87baa3f 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesIllegalSubclassTest.java
@@ -37,7 +37,6 @@
@Parameter(1)
public boolean keepPermittedSubclassesAttribute;
-
static final Matcher<String> EXPECTED = containsString("cannot inherit from sealed class");
static final String EXPECTED_WITHOUT_PERMITTED_SUBCLASSES_ATTRIBUTE =
StringUtils.lines("Success!");
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java
new file mode 100644
index 0000000..af6954b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassReferencedFromPermittedSubclassesAttributeTest.java
@@ -0,0 +1,152 @@
+// Copyright (c) 2023, 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.missingclasses;
+
+import static com.android.tools.r8.utils.codeinspector.AssertUtils.assertFailsCompilationIf;
+import static junit.framework.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
+import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestBuilder;
+import com.android.tools.r8.TestDiagnosticMessages;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.diagnostic.DefinitionContext;
+import com.android.tools.r8.diagnostic.internal.DefinitionClassContextImpl;
+import com.android.tools.r8.references.ClassReference;
+import com.android.tools.r8.references.Reference;
+import com.android.tools.r8.utils.BooleanUtils;
+import com.android.tools.r8.utils.codeinspector.CodeInspector;
+import com.google.common.collect.ImmutableList;
+import java.util.List;
+import org.junit.Test;
+import org.junit.runners.Parameterized.Parameters;
+
+public class MissingClassReferencedFromPermittedSubclassesAttributeTest
+ extends MissingClassesTestBase {
+
+ private static final DefinitionContext referencedFrom =
+ DefinitionClassContextImpl.builder()
+ .setClassContext(Reference.classFromClass(Super.class))
+ .setOrigin(getOrigin(Super.class))
+ .build();
+
+ @Parameters(name = "{1}, report: {0}")
+ public static List<Object[]> refinedData() {
+ return buildParameters(
+ BooleanUtils.values(), getTestParameters().withAllRuntimesAndApiLevels().build());
+ }
+
+ private final boolean reportMissingClassesInPermittedSubclassesAttributes;
+
+ public MissingClassReferencedFromPermittedSubclassesAttributeTest(
+ boolean reportMissingClassesInPermittedSubclassesAttributes, TestParameters parameters) {
+ super(parameters);
+ this.reportMissingClassesInPermittedSubclassesAttributes =
+ reportMissingClassesInPermittedSubclassesAttributes;
+ }
+
+ private void inspect(CodeInspector inspector) {
+ // Missing classes stays in the PermittedSubclasses attribute.
+ assertEquals(
+ parameters.isCfRuntime()
+ ? ImmutableList.of(
+ inspector.clazz(Sub.class).asTypeSubject(),
+ inspector.getTypeSubject(MissingSub.class.getTypeName()))
+ : ImmutableList.of(),
+ inspector.clazz(Super.class).getFinalPermittedSubclassAttributes());
+ }
+
+ @Test()
+ public void testNoRules() throws Exception {
+ assertFailsCompilationIf(
+ reportMissingClassesInPermittedSubclassesAttributes,
+ () ->
+ compileWithExpectedDiagnostics(
+ Main.class,
+ reportMissingClassesInPermittedSubclassesAttributes
+ ? diagnostics -> inspectDiagnosticsWithNoRules(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ this::configure));
+ }
+
+ @Test
+ public void testDontWarnSuperClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addDontWarn(Super.class).andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testDontWarnMissingClass() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ TestDiagnosticMessages::assertNoMessages,
+ addDontWarn(MissingSub.class).andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ @Test
+ public void testIgnoreWarnings() throws Exception {
+ compileWithExpectedDiagnostics(
+ Main.class,
+ reportMissingClassesInPermittedSubclassesAttributes
+ ? diagnostics -> inspectDiagnosticsWithIgnoreWarnings(diagnostics, referencedFrom)
+ : TestDiagnosticMessages::assertNoMessages,
+ addIgnoreWarnings(reportMissingClassesInPermittedSubclassesAttributes)
+ .andThen(this::configure))
+ .inspect(this::inspect);
+ }
+
+ void configure(R8FullTestBuilder builder) {
+ try {
+ builder
+ .addKeepAttributePermittedSubclasses()
+ .addProgramClasses(Sub.class)
+ .addProgramClassFileData(getTransformedClasses())
+ .addKeepClassRulesWithAllowObfuscation(Super.class, Sub.class)
+ .addOptionsModification(
+ options -> {
+ // We do not report missing classes from permitted subclasses attributes by default.
+ assertFalse(options.reportMissingClassesInPermittedSubclassesAttributes);
+ options.reportMissingClassesInPermittedSubclassesAttributes =
+ reportMissingClassesInPermittedSubclassesAttributes;
+ })
+ .applyIf(
+ !reportMissingClassesInPermittedSubclassesAttributes,
+ // The -dontwarn Main and -dontwarn MissingClass tests will have unused -dontwarn
+ // rules.
+ R8TestBuilder::allowUnusedDontWarnPatterns);
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ ClassReference getMissingClassReference() {
+ return Reference.classFromClass(MissingSub.class);
+ }
+
+ public byte[] getTransformedClasses() throws Exception {
+ return transformer(Super.class)
+ .setPermittedSubclasses(Super.class, Sub.class, MissingSub.class)
+ .transform();
+ }
+
+ static class Main {
+
+ public static void main(String[] args) {
+ new Sub();
+ System.out.println("Success!");
+ }
+ }
+
+ abstract static class Super /* permits Sub, MissingSub */ {}
+
+ static class Sub extends Super {}
+
+ static class MissingSub extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
index 5cc5fe1..91c8722 100644
--- a/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
+++ b/src/test/java/com/android/tools/r8/missingclasses/MissingClassesTestBase.java
@@ -8,6 +8,7 @@
import com.android.tools.r8.CompilationFailedException;
import com.android.tools.r8.R8FullTestBuilder;
+import com.android.tools.r8.R8TestCompileResult;
import com.android.tools.r8.TestBase;
import com.android.tools.r8.TestCompilerBuilder.DiagnosticsConsumer;
import com.android.tools.r8.TestDiagnosticMessages;
@@ -75,27 +76,27 @@
compileWithExpectedDiagnostics(mainClass, diagnosticsConsumer, null);
}
- public void compileWithExpectedDiagnostics(
+ public R8TestCompileResult compileWithExpectedDiagnostics(
Class<?> mainClass,
DiagnosticsConsumer diagnosticsConsumer,
ThrowableConsumer<R8FullTestBuilder> configuration)
throws CompilationFailedException {
- internalCompileWithExpectedDiagnostics(
+ return internalCompileWithExpectedDiagnostics(
diagnosticsConsumer,
builder ->
builder.addProgramClasses(mainClass).addKeepMainRule(mainClass).apply(configuration));
}
- public void compileWithExpectedDiagnostics(
+ public R8TestCompileResult compileWithExpectedDiagnostics(
ThrowableConsumer<R8FullTestBuilder> configuration, DiagnosticsConsumer diagnosticsConsumer)
throws CompilationFailedException {
- internalCompileWithExpectedDiagnostics(diagnosticsConsumer, configuration);
+ return internalCompileWithExpectedDiagnostics(diagnosticsConsumer, configuration);
}
- private void internalCompileWithExpectedDiagnostics(
+ private R8TestCompileResult internalCompileWithExpectedDiagnostics(
DiagnosticsConsumer diagnosticsConsumer, ThrowableConsumer<R8FullTestBuilder> configuration)
throws CompilationFailedException {
- testForR8(parameters.getBackend())
+ return testForR8(parameters.getBackend())
.apply(configuration)
.setMinApi(parameters)
.compileWithExpectedDiagnostics(diagnosticsConsumer);