Add rewriting of PermittedSubclasses

Bug: b/227160052
Change-Id: I3ee4a058b728020ffe72ace2890b926656644585
diff --git a/src/main/java/com/android/tools/r8/graph/DexClass.java b/src/main/java/com/android/tools/r8/graph/DexClass.java
index cc8e00f..a87febd 100644
--- a/src/main/java/com/android/tools/r8/graph/DexClass.java
+++ b/src/main/java/com/android/tools/r8/graph/DexClass.java
@@ -1043,6 +1043,10 @@
     permittedSubclasses.clear();
   }
 
+  public void removePermittedSubclassAttribute(Predicate<PermittedSubclassAttribute> predicate) {
+    permittedSubclasses.removeIf(predicate);
+  }
+
   public boolean isLocalClass() {
     InnerClassAttribute innerClass = getInnerClassAttributeForThisClass();
     // The corresponding enclosing-method attribute might be not available, e.g., CF version 50.
@@ -1139,6 +1143,10 @@
     return permittedSubclasses;
   }
 
+  public void setPermittedSubclassAttributes(List<PermittedSubclassAttribute> permittedSubclasses) {
+    this.permittedSubclasses = permittedSubclasses;
+  }
+
   public List<RecordComponentInfo> getRecordComponents() {
     return recordComponents;
   }
diff --git a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
index a84af1a..035ab26 100644
--- a/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
+++ b/src/main/java/com/android/tools/r8/graph/fixup/TreeFixerBase.java
@@ -28,6 +28,7 @@
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.IdentityHashMap;
+import java.util.LinkedHashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.function.Consumer;
@@ -297,15 +298,23 @@
       return permittedSubclassAttributes;
     }
     boolean changed = false;
-    List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
-        new ArrayList<>(permittedSubclassAttributes.size());
+    LinkedHashSet<DexType> newPermittedSubclassTypes =
+        new LinkedHashSet<>(permittedSubclassAttributes.size());
     for (PermittedSubclassAttribute permittedSubclassAttribute : permittedSubclassAttributes) {
-      DexType permittedSubclassType = permittedSubclassAttribute.getPermittedSubclass();
-      DexType newPermittedSubclassType = fixupType(permittedSubclassType);
-      newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
-      changed |= newPermittedSubclassType != permittedSubclassType;
+      DexType permittedSubClassType = permittedSubclassAttribute.getPermittedSubclass();
+      DexType newPermittedSubClassType = fixupType(permittedSubClassType);
+      newPermittedSubclassTypes.add(newPermittedSubClassType);
+      changed |= newPermittedSubClassType != permittedSubClassType;
     }
-    return changed ? newPermittedSubclassAttributes : permittedSubclassAttributes;
+    if (!changed) {
+      return permittedSubclassAttributes;
+    }
+    List<PermittedSubclassAttribute> newPermittedSubclassAttributes =
+        new ArrayList<>(newPermittedSubclassTypes.size());
+    for (DexType newPermittedSubclassType : newPermittedSubclassTypes) {
+      newPermittedSubclassAttributes.add(new PermittedSubclassAttribute(newPermittedSubclassType));
+    }
+    return newPermittedSubclassAttributes;
   }
 
   protected List<RecordComponentInfo> fixupRecordComponents(
diff --git a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
index 804b505..ee56cc7 100644
--- a/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
+++ b/src/main/java/com/android/tools/r8/horizontalclassmerging/TreeFixer.java
@@ -157,6 +157,8 @@
     clazz.setInnerClasses(fixupInnerClassAttributes(clazz.getInnerClasses()));
     clazz.setNestHostAttribute(fixupNestHost(clazz.getNestHostClassAttribute()));
     clazz.setNestMemberAttributes(fixupNestMemberAttributes(clazz.getNestMembersClassAttributes()));
+    clazz.setPermittedSubclassAttributes(
+        fixupPermittedSubclassAttribute(clazz.getPermittedSubclassAttributes()));
   }
 
   private void fixupProgramClassSuperTypes(DexProgramClass clazz) {
diff --git a/src/main/java/com/android/tools/r8/shaking/TreePruner.java b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
index 3688415..ec15263 100644
--- a/src/main/java/com/android/tools/r8/shaking/TreePruner.java
+++ b/src/main/java/com/android/tools/r8/shaking/TreePruner.java
@@ -21,6 +21,7 @@
 import com.android.tools.r8.graph.EnclosingMethodAttribute;
 import com.android.tools.r8.graph.InnerClassAttribute;
 import com.android.tools.r8.graph.NestMemberClassAttribute;
+import com.android.tools.r8.graph.PermittedSubclassAttribute;
 import com.android.tools.r8.graph.ProgramMethod;
 import com.android.tools.r8.graph.PrunedItems;
 import com.android.tools.r8.graph.RecordComponentInfo;
@@ -226,6 +227,7 @@
             PredicateUtils.not(isReachableInstanceField(reachableInstanceFields)));
       }
     }
+    clazz.removePermittedSubclassAttribute(this::isAttributeReferencingPrunedType);
     unusedItemsPrinter.visited();
     assert verifyNoDeadFields(clazz);
   }
@@ -319,6 +321,10 @@
     return context == null || isTypeMissing(context) || !isTypeLive(context);
   }
 
+  private boolean isAttributeReferencingPrunedType(PermittedSubclassAttribute attr) {
+    return !isTypeLive(attr.getPermittedSubclass());
+  }
+
   private <D extends DexEncodedMember<D, R>, R extends DexMember<D, R>> int firstUnreachableIndex(
       List<D> items, Predicate<D> live) {
     for (int i = 0; i < items.size(); i++) {
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
index 07d4ef7..e00e194 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesEnumJdk17CompiledTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.sealed;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static junit.framework.Assert.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
@@ -18,7 +19,7 @@
 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.Matchers;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -70,14 +71,15 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz("enum_sealed.Enum");
-    assertThat(clazz, Matchers.isPresentAndRenamed());
-    if (!parameters.isCfRuntime()) {
-      return;
-    }
+    ClassSubject clazz = inspector.clazz(EnumSealed.Enum.typeName());
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(EnumSealed.EnumB.typeName());
+    assertThat(sub1, isPresentAndRenamed());
     assertEquals(
-        keepPermittedSubclassesAttribute ? 1 : 0,
-        clazz.getFinalPermittedSubclassAttributes().size());
+        parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+            ? ImmutableList.of(sub1.asTypeSubject())
+            : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
   }
 
   @Test
@@ -90,6 +92,7 @@
             keepPermittedSubclassesAttribute,
             TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
         .addKeepMainRule(EnumSealed.Main.typeName())
+        .addKeepClassRulesWithAllowObfuscation(EnumSealed.Enum.typeName())
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), EnumSealed.Main.typeName())
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 87baa3f..240b7a9 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
@@ -4,6 +4,8 @@
 
 package com.android.tools.r8.desugar.sealed;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndNotRenamed;
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static junit.framework.Assert.assertEquals;
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.hamcrest.MatcherAssert.assertThat;
@@ -20,7 +22,7 @@
 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.Matchers;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.hamcrest.Matcher;
 import org.junit.Test;
@@ -37,6 +39,7 @@
 
   @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!");
@@ -80,14 +83,19 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(C.class);
-    assertThat(clazz, Matchers.isPresentAndRenamed());
-    if (!parameters.isCfRuntime()) {
-      return;
-    }
+    ClassSubject clazz = inspector.clazz(Super.class);
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(Sub1.class);
+    ClassSubject sub2 = inspector.clazz(Sub2.class);
+    ClassSubject sub3 = inspector.clazz(Sub3.class);
+    assertThat(sub1, isPresentAndNotRenamed());
+    assertThat(sub2, isPresentAndNotRenamed());
+    assertThat(sub3, isPresentAndNotRenamed());
     assertEquals(
-        keepPermittedSubclassesAttribute ? 2 : 0,
-        clazz.getFinalPermittedSubclassAttributes().size());
+        parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+            ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+            : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
   }
 
   @Test
@@ -100,10 +108,8 @@
         .applyIf(
             keepPermittedSubclassesAttribute,
             TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
-        // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
-        .addKeepPermittedSubclasses(C.class)
-        // Keep subclasses as the PermittedSubclasses attribute is not rewritten.
-        .addKeepRules("-keep class * extends " + C.class.getTypeName())
+        .addKeepPermittedSubclasses(Super.class)
+        .addKeepRules("-keep class * extends " + Super.class.getTypeName())
         .addKeepMainRule(TestClass.class)
         .compile()
         .inspect(this::inspect)
@@ -121,7 +127,9 @@
   }
 
   public byte[] getTransformedClasses() throws Exception {
-    return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+    return transformer(Super.class)
+        .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+        .transform();
   }
 
   static class TestClass {
@@ -134,11 +142,11 @@
     }
   }
 
-  abstract static class C /* permits Sub1, Sub2 */ {}
+  abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends C {}
+  static class Sub1 extends Super {}
 
-  static class Sub2 extends C {}
+  static class Sub2 extends Super {}
 
-  static class Sub3 extends C {}
+  static class Sub3 extends Super {}
 }
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
index 2b7d981..c51bb03 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesJdk17CompiledTest.java
@@ -4,6 +4,7 @@
 
 package com.android.tools.r8.desugar.sealed;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static junit.framework.Assert.assertEquals;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
@@ -18,7 +19,7 @@
 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.Matchers;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -70,14 +71,17 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz("sealed.Compiler");
-    assertThat(clazz, Matchers.isPresentAndRenamed());
-    if (!parameters.isCfRuntime()) {
-      return;
-    }
+    ClassSubject clazz = inspector.clazz(Sealed.Compiler.typeName());
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(Sealed.R8Compiler.typeName());
+    ClassSubject sub2 = inspector.clazz(Sealed.D8Compiler.typeName());
+    assertThat(sub1, isPresentAndRenamed());
+    assertThat(sub2, isPresentAndRenamed());
     assertEquals(
-        keepPermittedSubclassesAttribute ? 2 : 0,
-        clazz.getFinalPermittedSubclassAttributes().size());
+        parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+            ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+            : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
   }
 
   @Test
@@ -89,8 +93,8 @@
         .applyIf(
             keepPermittedSubclassesAttribute,
             TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
-        // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
         .addKeepPermittedSubclasses(Sealed.Compiler.typeName())
+        .addKeepRules("-keep,allowobfuscation class * extends " + Sealed.Compiler.typeName())
         .addKeepMainRule(Sealed.Main.typeName())
         .compile()
         .inspect(this::inspect)
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
new file mode 100644
index 0000000..bcd774a
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesMergeTest.java
@@ -0,0 +1,99 @@
+// 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.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+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.google.common.collect.ImmutableList;
+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 SealedClassesMergeTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  static final String EXPECTED = StringUtils.lines("Success!");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+    builder
+        .addProgramClasses(TestClass.class, Sub1.class, Sub2.class)
+        .addProgramClassFileData(getTransformedClasses());
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Super.class);
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(Sub1.class);
+    assertThat(sub1, isPresentAndRenamed());
+    assertEquals(
+        parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .apply(this::addTestClasses)
+        .setMinApi(parameters)
+        .addKeepAttributePermittedSubclasses()
+        .addKeepClassRulesWithAllowObfuscation(Super.class)
+        .addKeepMainRule(TestClass.class)
+        .addHorizontallyMergedClassesInspector(
+            inspector -> {
+              inspector
+                  .assertIsCompleteMergeGroup(Sub2.class, Sub1.class)
+                  .assertNoOtherClassesMerged();
+            })
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  public byte[] getTransformedClasses() throws Exception {
+    return transformer(Super.class)
+        .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new Sub1();
+      new Sub2();
+      System.out.println("Success!");
+    }
+  }
+
+  abstract static class Super /* permits Sub1, Sub2 */ {}
+
+  static class Sub1 extends Super {}
+
+  static class Sub2 extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
new file mode 100644
index 0000000..1893e3b
--- /dev/null
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesShrinkingTest.java
@@ -0,0 +1,92 @@
+// 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.desugar.sealed;
+
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
+import static junit.framework.Assert.assertEquals;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import com.android.tools.r8.TestBase;
+import com.android.tools.r8.TestBuilder;
+import com.android.tools.r8.TestParameters;
+import com.android.tools.r8.TestParametersCollection;
+import com.android.tools.r8.TestRuntime.CfVm;
+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.google.common.collect.ImmutableList;
+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 SealedClassesShrinkingTest extends TestBase {
+
+  @Parameter(0)
+  public TestParameters parameters;
+
+  static final String EXPECTED = StringUtils.lines("Success!");
+
+  @Parameters(name = "{0}")
+  public static TestParametersCollection data() {
+    return getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build();
+  }
+
+  private void addTestClasses(TestBuilder<?, ?> builder) throws Exception {
+    builder
+        .addProgramClasses(TestClass.class, UsedSub.class, UnusedSub.class)
+        .addProgramClassFileData(getTransformedClasses());
+  }
+
+  private void inspect(CodeInspector inspector) {
+    ClassSubject clazz = inspector.clazz(Super.class);
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(UsedSub.class);
+    assertThat(sub1, isPresentAndRenamed());
+    assertEquals(
+        parameters.isCfRuntime() ? ImmutableList.of(sub1.asTypeSubject()) : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
+  }
+
+  @Test
+  public void testR8() throws Exception {
+    parameters.assumeR8TestParameters();
+    testForR8(parameters.getBackend())
+        .apply(this::addTestClasses)
+        .setMinApi(parameters)
+        .addKeepAttributePermittedSubclasses()
+        .addKeepClassRulesWithAllowObfuscation(Super.class, UsedSub.class)
+        .addKeepMainRule(TestClass.class)
+        .compile()
+        .inspect(this::inspect)
+        .run(parameters.getRuntime(), TestClass.class)
+        .applyIf(
+            !parameters.isCfRuntime() || parameters.asCfRuntime().isNewerThanOrEqual(CfVm.JDK17),
+            r -> r.assertSuccessWithOutput(EXPECTED),
+            r -> r.assertFailureWithErrorThatThrows(UnsupportedClassVersionError.class));
+  }
+
+  public byte[] getTransformedClasses() throws Exception {
+    return transformer(Super.class)
+        .setPermittedSubclasses(Super.class, UsedSub.class, UnusedSub.class)
+        .transform();
+  }
+
+  static class TestClass {
+
+    public static void main(String[] args) {
+      new UsedSub();
+      System.out.println("Success!");
+    }
+  }
+
+  abstract static class Super /* permits UsedSub, UnusedSub */ {}
+
+  static class UsedSub extends Super {}
+
+  static class UnusedSub extends Super {}
+}
diff --git a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
index 3aa1a38..997e4fb 100644
--- a/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
+++ b/src/test/java/com/android/tools/r8/desugar/sealed/SealedClassesTest.java
@@ -4,7 +4,9 @@
 
 package com.android.tools.r8.desugar.sealed;
 
+import static com.android.tools.r8.utils.codeinspector.Matchers.isPresentAndRenamed;
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertTrue;
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assume.assumeTrue;
 
@@ -18,7 +20,7 @@
 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.Matchers;
+import com.google.common.collect.ImmutableList;
 import java.util.List;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -35,12 +37,16 @@
   @Parameter(1)
   public boolean keepPermittedSubclassesAttribute;
 
+  @Parameter(2)
+  public boolean repackage;
+
   static final String EXPECTED = StringUtils.lines("Success!");
 
-  @Parameters(name = "{0}, keepPermittedSubclasses = {1}")
+  @Parameters(name = "{0}, keepPermittedSubclasses = {1}, repackage = {2}")
   public static List<Object[]> data() {
     return buildParameters(
         getTestParameters().withAllRuntimes().withAllApiLevelsAlsoForCf().build(),
+        BooleanUtils.values(),
         BooleanUtils.values());
   }
 
@@ -76,14 +82,22 @@
   }
 
   private void inspect(CodeInspector inspector) {
-    ClassSubject clazz = inspector.clazz(C.class);
-    assertThat(clazz, Matchers.isPresentAndRenamed());
-    if (!parameters.isCfRuntime()) {
-      return;
+    ClassSubject clazz = inspector.clazz(Super.class);
+    assertThat(clazz, isPresentAndRenamed());
+    ClassSubject sub1 = inspector.clazz(Sub1.class);
+    ClassSubject sub2 = inspector.clazz(Sub2.class);
+    assertThat(sub1, isPresentAndRenamed());
+    assertThat(sub2, isPresentAndRenamed());
+    if (repackage) {
+      assertEquals(-1, sub1.getFinalName().indexOf('.'));
+    } else {
+      assertTrue(sub1.getFinalName().startsWith(getClass().getPackage().getName()));
     }
     assertEquals(
-        keepPermittedSubclassesAttribute ? 2 : 0,
-        clazz.getFinalPermittedSubclassAttributes().size());
+        parameters.isCfRuntime() && keepPermittedSubclassesAttribute
+            ? ImmutableList.of(sub1.asTypeSubject(), sub2.asTypeSubject())
+            : ImmutableList.of(),
+        clazz.getFinalPermittedSubclassAttributes());
   }
 
   @Test
@@ -96,8 +110,9 @@
             keepPermittedSubclassesAttribute,
             TestShrinkerBuilder::addKeepAttributePermittedSubclasses)
         // Keep the sealed class to ensure the PermittedSubclasses attribute stays live.
-        .addKeepPermittedSubclasses(C.class)
+        .addKeepPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
         .addKeepMainRule(TestClass.class)
+        .applyIf(repackage, b -> b.addKeepRules("-repackageclasses"))
         .compile()
         .inspect(this::inspect)
         .run(parameters.getRuntime(), TestClass.class)
@@ -108,7 +123,9 @@
   }
 
   public byte[] getTransformedClasses() throws Exception {
-    return transformer(C.class).setPermittedSubclasses(C.class, Sub1.class, Sub2.class).transform();
+    return transformer(Super.class)
+        .setPermittedSubclasses(Super.class, Sub1.class, Sub2.class)
+        .transform();
   }
 
   static class TestClass {
@@ -120,9 +137,9 @@
     }
   }
 
-  abstract static class C /* permits Sub1, Sub2 */ {}
+  public abstract static class Super /* permits Sub1, Sub2 */ {}
 
-  static class Sub1 extends C {}
+  public static class Sub1 extends Super {}
 
-  static class Sub2 extends C {}
+  public static class Sub2 extends Super {}
 }