mirror of
https://github.com/Guardsquare/proguard.git
synced 2026-03-13 09:50:34 +08:00
Run InterfaceUsageMarker before NestUsageMarker to prevent missing marking of permitted subclasses attribute
Closes #501 1. [ClassUsageMarker assumes that the NestUsageMarker will mark the permitted subclasses](869ce156b1/base/src/main/java/proguard/shrink/ClassUsageMarker.java (L1144)). 2. [ NestUsageMarker only marks class constants in the permittedSubclasses attribute if the referenced class is used ](869ce156b1/base/src/main/java/proguard/shrink/NestUsageMarker.java (L100)). 3. [ If the referenced class is another interface, this is marked by the InterfaceUsageMarker ](869ce156b1/base/src/main/java/proguard/shrink/InterfaceUsageMarker.java (L36)). 4. [However, the interfaceUsageMarker only runs after the NestUsageMarker, hence nothing gets marked if a sealed interface permits another sealed interface](869ce156b1/base/src/main/java/proguard/shrink/UsageMarker.java (L119-130))
This commit is contained in:
@@ -106,6 +106,10 @@ public class UsageMarker
|
||||
classUsageMarker))
|
||||
));
|
||||
|
||||
// Mark interfaces that have to be kept. This must be before the NestUsageMarker call right after,
|
||||
// see https://github.com/Guardsquare/proguard/issues/501.
|
||||
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
|
||||
|
||||
// Mark the elements of Kotlin metadata that need to be kept.
|
||||
if (configuration.keepKotlinMetadata)
|
||||
{
|
||||
@@ -114,7 +118,6 @@ public class UsageMarker
|
||||
new ReferencedKotlinMetadataVisitor(
|
||||
classUsageMarker));
|
||||
}
|
||||
|
||||
// Mark the inner class and annotation information that has to be kept.
|
||||
programClassPool.classesAccept(
|
||||
new UsedClassFilter(simpleUsageMarker,
|
||||
@@ -126,8 +129,6 @@ public class UsageMarker
|
||||
new LocalVariableTypeUsageMarker(classUsageMarker)
|
||||
))));
|
||||
|
||||
// Mark interfaces that have to be kept.
|
||||
programClassPool.classesAccept(new InterfaceUsageMarker(classUsageMarker));
|
||||
|
||||
if (configuration.keepKotlinMetadata)
|
||||
{
|
||||
|
||||
150
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal file
150
base/src/test/kotlin/proguard/shrink/UsageMarkerTest.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
package proguard.shrink
|
||||
|
||||
import io.kotest.core.spec.style.BehaviorSpec
|
||||
import io.kotest.matchers.shouldBe
|
||||
import proguard.Configuration
|
||||
import proguard.classfile.ClassPool
|
||||
import proguard.classfile.Clazz
|
||||
import proguard.classfile.attribute.Attribute
|
||||
import proguard.classfile.attribute.PermittedSubclassesAttribute
|
||||
import proguard.classfile.attribute.visitor.AllAttributeVisitor
|
||||
import proguard.classfile.attribute.visitor.AttributeVisitor
|
||||
import proguard.classfile.constant.ClassConstant
|
||||
import proguard.classfile.constant.Constant
|
||||
import proguard.classfile.constant.visitor.ConstantVisitor
|
||||
import proguard.classfile.visitor.AllMemberVisitor
|
||||
import proguard.classfile.visitor.MultiClassVisitor
|
||||
import proguard.resources.file.ResourceFilePool
|
||||
import proguard.testutils.ClassPoolBuilder
|
||||
import proguard.testutils.JavaSource
|
||||
import proguard.testutils.RequiresJavaVersion
|
||||
import proguard.util.ProcessingFlagSetter
|
||||
import proguard.util.ProcessingFlags.DONT_SHRINK
|
||||
|
||||
@RequiresJavaVersion(15)
|
||||
class Java15UsageMarkerTest : BehaviorSpec({
|
||||
// Regression test for https://github.com/Guardsquare/proguard/issues/501
|
||||
Given("A class pool containing a sealed interface extending another sealed interface, and final classes implementing both") {
|
||||
val (programClassPool, _) = ClassPoolBuilder.fromSource(
|
||||
JavaSource("sample/Animal.java","""
|
||||
package sample;
|
||||
public sealed interface Animal permits Fish, Mammal {
|
||||
static Animal ofType(String type) {
|
||||
if (Cat.TYPE.matches(type)) {
|
||||
return new Cat();
|
||||
} else if (Dog.TYPE.matches(type)) {
|
||||
return new Dog();
|
||||
} else if (Fish.TYPE.matches(type)) {
|
||||
return new Fish();
|
||||
}
|
||||
throw new IllegalArgumentException("Wrong animal type: " + type);
|
||||
}
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/AnimalType.java","""
|
||||
package sample;
|
||||
|
||||
public enum AnimalType {
|
||||
|
||||
CAT("CAT"),
|
||||
DOG("DOG"),
|
||||
FISH("FISH");
|
||||
|
||||
|
||||
private final String typeString;
|
||||
|
||||
AnimalType(String typeString){
|
||||
this.typeString = typeString;
|
||||
}
|
||||
|
||||
|
||||
public boolean matches(String typeString) {
|
||||
return this.typeString.equalsIgnoreCase(typeString);
|
||||
}
|
||||
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Mammal.java", """
|
||||
package sample;
|
||||
public sealed interface Mammal extends Animal permits Cat, Dog {
|
||||
}
|
||||
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Cat.java","""
|
||||
package sample;
|
||||
public final class Cat implements Mammal {
|
||||
public static final AnimalType TYPE = AnimalType.CAT;
|
||||
}
|
||||
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Dog.java","""
|
||||
package sample;
|
||||
public final class Dog implements Mammal {
|
||||
public static final AnimalType TYPE = AnimalType.DOG;
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Fish.java","""
|
||||
package sample;
|
||||
public final class Fish implements Animal {
|
||||
public static final AnimalType TYPE = AnimalType.FISH;
|
||||
}
|
||||
""".trimIndent()),
|
||||
JavaSource("sample/Main.java","""
|
||||
package sample;
|
||||
public class Main {
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Trying to create animal");
|
||||
Animal animal = Animal.ofType("fish");
|
||||
System.out.println("Successfully created animal");
|
||||
}
|
||||
}
|
||||
|
||||
""".trimIndent()
|
||||
), javacArguments = listOf("--enable-preview", "--release", "15"),
|
||||
)
|
||||
val animal = programClassPool.getClass("sample/Animal")
|
||||
|
||||
val main = programClassPool.getClass("sample/Main")
|
||||
main.accept(
|
||||
MultiClassVisitor(ProcessingFlagSetter(DONT_SHRINK), AllMemberVisitor(
|
||||
ProcessingFlagSetter(DONT_SHRINK)
|
||||
)))
|
||||
When("marking") {
|
||||
val simpleUsageMarker = SimpleUsageMarker()
|
||||
UsageMarker(Configuration()).mark(programClassPool,ClassPool(), ResourceFilePool(),simpleUsageMarker)
|
||||
Then("The Animal class permitted subclasses constants should all be marked as used") {
|
||||
var visited = false
|
||||
animal.accept(AllAttributeVisitor(object : AttributeVisitor, ConstantVisitor {
|
||||
override fun visitAnyAttribute(
|
||||
clazz: Clazz,
|
||||
attribute: Attribute
|
||||
) {
|
||||
}
|
||||
|
||||
override fun visitPermittedSubclassesAttribute(
|
||||
clazz: Clazz,
|
||||
permittedSubclassesAttribute: PermittedSubclassesAttribute
|
||||
) {
|
||||
permittedSubclassesAttribute.permittedSubclassConstantsAccept(clazz,this)
|
||||
}
|
||||
|
||||
override fun visitAnyConstant(
|
||||
clazz: Clazz,
|
||||
constant: Constant
|
||||
) {
|
||||
}
|
||||
|
||||
override fun visitClassConstant(
|
||||
clazz: Clazz,
|
||||
classConstant: ClassConstant
|
||||
) {
|
||||
visited = true
|
||||
simpleUsageMarker.isUsed(classConstant) shouldBe true
|
||||
}
|
||||
}))
|
||||
visited shouldBe true
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user