From a9d5833d24f58be05db64bb3c676c014971e8c17 Mon Sep 17 00:00:00 2001 From: "niccolo.piazzesi" Date: Thu, 2 Apr 2026 17:02:45 +0200 Subject: [PATCH] Properly fix KotlinShrinker and add tests --- .../java/proguard/shrink/KotlinShrinker.java | 479 ++++++++------- .../proguard/shrink/KotlinShrinkerTest.kt | 578 ++++++++++++++++++ docs/md/manual/releasenotes.md | 6 + 3 files changed, 831 insertions(+), 232 deletions(-) create mode 100644 base/src/test/kotlin/proguard/shrink/KotlinShrinkerTest.kt diff --git a/base/src/main/java/proguard/shrink/KotlinShrinker.java b/base/src/main/java/proguard/shrink/KotlinShrinker.java index cfaf3609..7d4497c1 100644 --- a/base/src/main/java/proguard/shrink/KotlinShrinker.java +++ b/base/src/main/java/proguard/shrink/KotlinShrinker.java @@ -20,8 +20,10 @@ */ package proguard.shrink; +import java.util.List; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; +import proguard.classfile.kotlin.KotlinAnnotation; import proguard.classfile.kotlin.KotlinClassKindMetadata; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.kotlin.KotlinConstructorMetadata; @@ -42,8 +44,10 @@ import proguard.classfile.kotlin.flags.KotlinPropertyAccessorMetadata; import proguard.classfile.kotlin.visitor.AllTypeVisitor; import proguard.classfile.kotlin.visitor.KotlinConstructorVisitor; +import proguard.classfile.kotlin.visitor.KotlinEnumEntryVisitor; import proguard.classfile.kotlin.visitor.KotlinFunctionVisitor; import proguard.classfile.kotlin.visitor.KotlinMetadataVisitor; +import proguard.classfile.kotlin.visitor.KotlinPropertyAccessorVisitor; import proguard.classfile.kotlin.visitor.KotlinPropertyVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeAliasVisitor; import proguard.classfile.kotlin.visitor.KotlinTypeParameterVisitor; @@ -55,37 +59,33 @@ import proguard.classfile.visitor.MultiMemberVisitor; import proguard.util.Processable; -import java.util.List; - public class KotlinShrinker -implements KotlinMetadataVisitor, - - // Implementation interfaces. - KotlinPropertyVisitor, - KotlinFunctionVisitor, - KotlinTypeAliasVisitor, - KotlinTypeVisitor, - KotlinConstructorVisitor, - KotlinTypeParameterVisitor, - KotlinValueParameterVisitor, - KotlinVersionRequirementVisitor -{ + implements KotlinMetadataVisitor, + + // Implementation interfaces. + KotlinPropertyVisitor, + KotlinFunctionVisitor, + KotlinTypeAliasVisitor, + KotlinTypeVisitor, + KotlinConstructorVisitor, + KotlinEnumEntryVisitor, + KotlinTypeParameterVisitor, + KotlinValueParameterVisitor, + KotlinVersionRequirementVisitor, + KotlinPropertyAccessorVisitor { private final SimpleUsageMarker usageMarker; - - KotlinShrinker(SimpleUsageMarker usageMarker) - { + public KotlinShrinker(SimpleUsageMarker usageMarker) { this.usageMarker = usageMarker; } - @Override public void visitAnyKotlinMetadata(Clazz clazz, KotlinMetadata kotlinMetadata) {} // Implementations for KotlinMetadataVisitor. @Override - public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) - { + public void visitKotlinDeclarationContainerMetadata( + Clazz clazz, KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata) { // Compact the metadata's lists of properties and functions. shrinkMetadataArray(kotlinDeclarationContainerMetadata.properties); shrinkMetadataArray(kotlinDeclarationContainerMetadata.functions); @@ -93,52 +93,55 @@ public void visitKotlinDeclarationContainerMetadata(Clazz clazz, KotlinDeclarati shrinkMetadataArray(kotlinDeclarationContainerMetadata.localDelegatedProperties); // Compact each remaining property and function. - kotlinDeclarationContainerMetadata.propertiesAccept( clazz, this); - kotlinDeclarationContainerMetadata.functionsAccept( clazz, this); - kotlinDeclarationContainerMetadata.typeAliasesAccept( clazz, this); + kotlinDeclarationContainerMetadata.propertiesAccept(clazz, this); + kotlinDeclarationContainerMetadata.functionsAccept(clazz, this); + kotlinDeclarationContainerMetadata.typeAliasesAccept(clazz, this); kotlinDeclarationContainerMetadata.delegatedPropertiesAccept(clazz, this); } @Override - public void visitKotlinClassMetadata(Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) - { + public void visitKotlinClassMetadata( + Clazz clazz, KotlinClassKindMetadata kotlinClassKindMetadata) { // Compact the metadata's own fields. - if (shouldShrinkMetadata(kotlinClassKindMetadata.companionObjectName, - kotlinClassKindMetadata.referencedCompanionClass)) - { - kotlinClassKindMetadata.companionObjectName = null; + if (shouldShrinkMetadata( + kotlinClassKindMetadata.companionObjectName, + kotlinClassKindMetadata.referencedCompanionClass)) { + kotlinClassKindMetadata.companionObjectName = null; kotlinClassKindMetadata.referencedCompanionClass = null; } shrinkMetadataArray(kotlinClassKindMetadata.constructors); - shrinkEnumEntries(kotlinClassKindMetadata.enumEntries); + kotlinClassKindMetadata.enumEntries.removeIf( + it -> shouldShrinkMetadata(it, it.referencedEnumEntry)); - shrinkArray(kotlinClassKindMetadata.nestedClassNames, - kotlinClassKindMetadata.referencedNestedClasses); + shrinkArray( + kotlinClassKindMetadata.nestedClassNames, kotlinClassKindMetadata.referencedNestedClasses); - shrinkArray(kotlinClassKindMetadata.sealedSubclassNames, - kotlinClassKindMetadata.referencedSealedSubClasses); + shrinkArray( + kotlinClassKindMetadata.sealedSubclassNames, + kotlinClassKindMetadata.referencedSealedSubClasses); visitKotlinDeclarationContainerMetadata(clazz, kotlinClassKindMetadata); - kotlinClassKindMetadata.superTypesAccept( clazz, this); - kotlinClassKindMetadata.typeParametersAccept( clazz, this); - kotlinClassKindMetadata.versionRequirementAccept( clazz, this); - kotlinClassKindMetadata.constructorsAccept( clazz, this); - kotlinClassKindMetadata.contextReceiverTypesAccept( clazz, this); + kotlinClassKindMetadata.enumEntriesAccept(clazz, this); + kotlinClassKindMetadata.superTypesAccept(clazz, this); + kotlinClassKindMetadata.typeParametersAccept(clazz, this); + kotlinClassKindMetadata.versionRequirementAccept(clazz, this); + kotlinClassKindMetadata.constructorsAccept(clazz, this); + kotlinClassKindMetadata.contextReceiverTypesAccept(clazz, this); kotlinClassKindMetadata.inlineClassUnderlyingPropertyTypeAccept(clazz, this); } @Override - public void visitKotlinFileFacadeMetadata(Clazz clazz, KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata) - { + public void visitKotlinFileFacadeMetadata( + Clazz clazz, KotlinFileFacadeKindMetadata kotlinFileFacadeKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinFileFacadeKindMetadata); } @Override - public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) - { + public void visitKotlinSyntheticClassMetadata( + Clazz clazz, KotlinSyntheticClassKindMetadata kotlinSyntheticClassKindMetadata) { // Compact the metadata's lists of functions. shrinkMetadataArray(kotlinSyntheticClassKindMetadata.functions); @@ -147,137 +150,143 @@ public void visitKotlinSyntheticClassMetadata(Clazz clazz, KotlinSyntheticClassK } @Override - public void visitKotlinMultiFileFacadeMetadata(Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) - { - shrinkArray(kotlinMultiFileFacadeKindMetadata.partClassNames, - kotlinMultiFileFacadeKindMetadata.referencedPartClasses); + public void visitKotlinMultiFileFacadeMetadata( + Clazz clazz, KotlinMultiFileFacadeKindMetadata kotlinMultiFileFacadeKindMetadata) { + shrinkArray( + kotlinMultiFileFacadeKindMetadata.partClassNames, + kotlinMultiFileFacadeKindMetadata.referencedPartClasses); } @Override - public void visitKotlinMultiFilePartMetadata(Clazz clazz, KotlinMultiFilePartKindMetadata kotlinMultiFilePartKindMetadata) - { + public void visitKotlinMultiFilePartMetadata( + Clazz clazz, KotlinMultiFilePartKindMetadata kotlinMultiFilePartKindMetadata) { visitKotlinDeclarationContainerMetadata(clazz, kotlinMultiFilePartKindMetadata); } - // Implementations for KotlinPropertyVisitor. @Override - public void visitAnyProperty(Clazz clazz, - KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, - KotlinPropertyMetadata kotlinPropertyMetadata) - { - kotlinPropertyMetadata.versionRequirementAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinPropertyMetadata.typeAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinPropertyMetadata.setterParameterAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinPropertyMetadata.receiverTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinPropertyMetadata.contextReceiverTypesAccept(clazz, kotlinDeclarationContainerMetadata, this); - kotlinPropertyMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); - - if (shouldShrinkMetadata(kotlinPropertyMetadata.backingFieldSignature, - kotlinPropertyMetadata.referencedBackingField)) - { - kotlinPropertyMetadata.backingFieldSignature = null; + public void visitAnyProperty( + Clazz clazz, + KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, + KotlinPropertyMetadata kotlinPropertyMetadata) { + kotlinPropertyMetadata.versionRequirementAccept( + clazz, kotlinDeclarationContainerMetadata, this); + kotlinPropertyMetadata.typeAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinPropertyMetadata.setterParameterAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinPropertyMetadata.receiverTypeAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinPropertyMetadata.contextReceiverTypesAccept( + clazz, kotlinDeclarationContainerMetadata, this); + kotlinPropertyMetadata.typeParametersAccept(clazz, kotlinDeclarationContainerMetadata, this); + + if (shouldShrinkMetadata( + kotlinPropertyMetadata.backingFieldSignature, + kotlinPropertyMetadata.referencedBackingField)) { + kotlinPropertyMetadata.backingFieldSignature = null; kotlinPropertyMetadata.referencedBackingField = null; } - if (shouldShrinkMetadata(kotlinPropertyMetadata.getterMetadata)) - { - kotlinPropertyMetadata.getterMetadata.signature = null; + kotlinPropertyMetadata.propertyAccessorsAccept(clazz, kotlinDeclarationContainerMetadata, this); + + if (shouldShrinkMetadata( + kotlinPropertyMetadata.getterMetadata.signature, + kotlinPropertyMetadata.getterMetadata.referencedMethod)) { + kotlinPropertyMetadata.getterMetadata.signature = null; kotlinPropertyMetadata.getterMetadata.referencedMethod = null; } - if (shouldShrinkMetadata(kotlinPropertyMetadata.setterMetadata)) - { + if (kotlinPropertyMetadata.setterMetadata != null + && shouldShrinkMetadata( + kotlinPropertyMetadata.setterMetadata.signature, + kotlinPropertyMetadata.setterMetadata.referencedMethod)) { kotlinPropertyMetadata.setterMetadata.signature = null; kotlinPropertyMetadata.setterMetadata.referencedMethod = null; - kotlinPropertyMetadata.flags.isVar = false; - kotlinPropertyMetadata.setterParameter = null; + kotlinPropertyMetadata.flags.isVar = false; + // TODO(deprecation): Remove the deprecated setterParameters initialisation. + kotlinPropertyMetadata.setterParameters.clear(); + kotlinPropertyMetadata.setterParameter = null; } - kotlinPropertyMetadata.versionRequirementAccept(clazz, - kotlinDeclarationContainerMetadata, - this); + kotlinPropertyMetadata.versionRequirementAccept( + clazz, kotlinDeclarationContainerMetadata, this); - if (kotlinPropertyMetadata.syntheticMethodForAnnotations != null && - !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations)) - { - kotlinPropertyMetadata.syntheticMethodForAnnotations = null; + if (kotlinPropertyMetadata.syntheticMethodForAnnotations != null + && !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations)) { + kotlinPropertyMetadata.syntheticMethodForAnnotations = null; kotlinPropertyMetadata.referencedSyntheticMethodForAnnotations = null; - kotlinPropertyMetadata.referencedSyntheticMethodClass = null; - kotlinPropertyMetadata.annotations.clear(); + kotlinPropertyMetadata.referencedSyntheticMethodClass = null; + kotlinPropertyMetadata.flags.hasAnnotations = false; } - if (kotlinPropertyMetadata.syntheticMethodForDelegate != null && - !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForDelegateMethod)) - { - kotlinPropertyMetadata.syntheticMethodForDelegate = null; - kotlinPropertyMetadata.referencedSyntheticMethodForDelegateClass = null; + if (kotlinPropertyMetadata.syntheticMethodForDelegate != null + && !usageMarker.isUsed(kotlinPropertyMetadata.referencedSyntheticMethodForDelegateMethod)) { + kotlinPropertyMetadata.syntheticMethodForDelegate = null; + kotlinPropertyMetadata.referencedSyntheticMethodForDelegateClass = null; kotlinPropertyMetadata.referencedSyntheticMethodForDelegateMethod = null; } // Fix inconsistencies that were introduced as // a result of shrinking - if (kotlinPropertyMetadata.referencedBackingField != null && - (kotlinPropertyMetadata.setterMetadata == null || - kotlinPropertyMetadata.setterMetadata.signature == null) && - (kotlinPropertyMetadata.getterMetadata == null || - kotlinPropertyMetadata.getterMetadata.signature == null) && - (kotlinPropertyMetadata.referencedBackingField.getAccessFlags() & AccessConstants.PRIVATE) != 0 && - !kotlinPropertyMetadata.flags.visibility.isPrivate) - { + if (kotlinPropertyMetadata.referencedBackingField != null + && kotlinPropertyMetadata.getterMetadata != null + && kotlinPropertyMetadata.getterMetadata.signature == null + && kotlinPropertyMetadata.setterMetadata != null + && kotlinPropertyMetadata.setterMetadata.signature == null + && (kotlinPropertyMetadata.referencedBackingField.getAccessFlags() + & AccessConstants.PRIVATE) + != 0 + && !kotlinPropertyMetadata.flags.visibility.isPrivate) { int visibility = - kotlinPropertyMetadata.flags.visibility.isProtected ? AccessConstants.PROTECTED : - AccessConstants.PUBLIC; - - kotlinPropertyMetadata.referencedBackingField.accept(kotlinPropertyMetadata.referencedBackingFieldClass, - new MultiMemberVisitor( - new MemberAccessFlagCleaner(AccessConstants.PRIVATE), - new MemberAccessSetter(visibility))); + kotlinPropertyMetadata.flags.visibility.isProtected + ? AccessConstants.PROTECTED + : AccessConstants.PUBLIC; + + kotlinPropertyMetadata.referencedBackingField.accept( + kotlinPropertyMetadata.referencedBackingFieldClass, + new MultiMemberVisitor( + new MemberAccessFlagCleaner(AccessConstants.PRIVATE), + new MemberAccessSetter(visibility))); } } // Implementations for KotlinFunctionVisitor. @Override - public void visitAnyFunction(Clazz clazz, - KotlinMetadata kotlinMetadata, - KotlinFunctionMetadata kotlinFunctionMetadata) - { - if (kotlinFunctionMetadata.referencedLambdaClassOrigin != null && - shouldShrinkMetadata(kotlinFunctionMetadata.lambdaClassOriginName, - kotlinFunctionMetadata.referencedLambdaClassOrigin)) - { - kotlinFunctionMetadata.lambdaClassOriginName = null; + public void visitAnyFunction( + Clazz clazz, KotlinMetadata kotlinMetadata, KotlinFunctionMetadata kotlinFunctionMetadata) { + if (kotlinFunctionMetadata.referencedLambdaClassOrigin != null + && shouldShrinkMetadata( + kotlinFunctionMetadata.lambdaClassOriginName, + kotlinFunctionMetadata.referencedLambdaClassOrigin)) { + kotlinFunctionMetadata.lambdaClassOriginName = null; kotlinFunctionMetadata.referencedLambdaClassOrigin = null; } - kotlinFunctionMetadata.receiverTypeAccept( clazz, kotlinMetadata, this); + kotlinFunctionMetadata.receiverTypeAccept(clazz, kotlinMetadata, this); kotlinFunctionMetadata.contextReceiverTypesAccept(clazz, kotlinMetadata, this); - kotlinFunctionMetadata.typeParametersAccept( clazz, kotlinMetadata, this); - kotlinFunctionMetadata.valueParametersAccept( clazz, kotlinMetadata, this); - kotlinFunctionMetadata.returnTypeAccept( clazz, kotlinMetadata, this); - kotlinFunctionMetadata.contractsAccept( clazz, kotlinMetadata, new AllTypeVisitor(this)); - - if (kotlinFunctionMetadata.referencedDefaultMethod != null && - !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) - { - kotlinFunctionMetadata.referencedDefaultMethod = null; + kotlinFunctionMetadata.typeParametersAccept(clazz, kotlinMetadata, this); + kotlinFunctionMetadata.valueParametersAccept(clazz, kotlinMetadata, this); + kotlinFunctionMetadata.returnTypeAccept(clazz, kotlinMetadata, this); + kotlinFunctionMetadata.contractsAccept(clazz, kotlinMetadata, new AllTypeVisitor(this)); + + if (kotlinFunctionMetadata.referencedDefaultMethod != null + && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) { + kotlinFunctionMetadata.referencedDefaultMethod = null; kotlinFunctionMetadata.referencedDefaultMethodClass = null; } - if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null && - !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultImplementationMethod)) - { - kotlinFunctionMetadata.referencedDefaultImplementationMethod = null; + if (kotlinFunctionMetadata.referencedDefaultImplementationMethod != null + && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultImplementationMethod)) { + kotlinFunctionMetadata.referencedDefaultImplementationMethod = null; kotlinFunctionMetadata.referencedDefaultImplementationMethodClass = null; } + shrinkAnnotations(kotlinFunctionMetadata.annotations); + // Fix inconsistencies that were introduced as // a result of shrinking. - if (!kotlinFunctionMetadata.flags.modality.isAbstract && - kotlinMetadata.k == KotlinConstants.METADATA_KIND_CLASS && - ((KotlinClassKindMetadata)kotlinMetadata).flags.isInterface && - kotlinFunctionMetadata.referencedDefaultImplementationMethod == null) - { + if (!kotlinFunctionMetadata.flags.modality.isAbstract + && kotlinMetadata.k == KotlinConstants.METADATA_KIND_CLASS + && ((KotlinClassKindMetadata) kotlinMetadata).flags.isInterface + && kotlinFunctionMetadata.referencedDefaultImplementationMethod == null) { kotlinFunctionMetadata.flags.modality.isAbstract = true; } } @@ -285,171 +294,177 @@ public void visitAnyFunction(Clazz clazz, // Implementations for KotlinConstructorVisitor. @Override - public void visitConstructor(Clazz clazz, - KotlinClassKindMetadata kotlinClassKindMetadata, - KotlinConstructorMetadata kotlinConstructorMetadata) - { - kotlinConstructorMetadata.valueParametersAccept( clazz, kotlinClassKindMetadata, this); + public void visitConstructor( + Clazz clazz, + KotlinClassKindMetadata kotlinClassKindMetadata, + KotlinConstructorMetadata kotlinConstructorMetadata) { + kotlinConstructorMetadata.valueParametersAccept(clazz, kotlinClassKindMetadata, this); kotlinConstructorMetadata.versionRequirementAccept(clazz, kotlinClassKindMetadata, this); } // Implementations for KotlinTypeAliasVisitor. @Override - public void visitTypeAlias(Clazz clazz, - KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, - KotlinTypeAliasMetadata kotlinTypeAliasMetadata) - { - kotlinTypeAliasMetadata.typeParametersAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinTypeAliasMetadata.underlyingTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinTypeAliasMetadata.expandedTypeAccept( clazz, kotlinDeclarationContainerMetadata, this); - kotlinTypeAliasMetadata.versionRequirementAccept(clazz, kotlinDeclarationContainerMetadata, this); - - shrinkMetadataArray(kotlinTypeAliasMetadata.annotations); + public void visitTypeAlias( + Clazz clazz, + KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, + KotlinTypeAliasMetadata kotlinTypeAliasMetadata) { + kotlinTypeAliasMetadata.typeParametersAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinTypeAliasMetadata.underlyingTypeAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinTypeAliasMetadata.expandedTypeAccept(clazz, kotlinDeclarationContainerMetadata, this); + kotlinTypeAliasMetadata.versionRequirementAccept( + clazz, kotlinDeclarationContainerMetadata, this); + + shrinkAnnotations(kotlinTypeAliasMetadata.annotations); } // Implementations for KotlinTypeVisitor. @Override - public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) - { + public void visitAnyType(Clazz clazz, KotlinTypeMetadata kotlinTypeMetadata) { kotlinTypeMetadata.typeArgumentsAccept(clazz, this); - kotlinTypeMetadata.upperBoundsAccept( clazz, this); - kotlinTypeMetadata.abbreviationAccept( clazz, this); + kotlinTypeMetadata.upperBoundsAccept(clazz, this); + kotlinTypeMetadata.abbreviationAccept(clazz, this); - shrinkMetadataArray(kotlinTypeMetadata.annotations); + shrinkAnnotations(kotlinTypeMetadata.annotations); } // Implementations for KotlinTypeParameterVisitor. @Override - public void visitAnyTypeParameter(Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata) - { + public void visitAnyTypeParameter( + Clazz clazz, KotlinTypeParameterMetadata kotlinTypeParameterMetadata) { kotlinTypeParameterMetadata.upperBoundsAccept(clazz, this); - shrinkMetadataArray(kotlinTypeParameterMetadata.annotations); + shrinkAnnotations(kotlinTypeParameterMetadata.annotations); } // Implementations for KotlinValueParameterVisitor. @Override - public void visitAnyValueParameter(Clazz clazz, - KotlinValueParameterMetadata kotlinValueParameterMetadata) {} + public void visitAnyValueParameter( + Clazz clazz, KotlinValueParameterMetadata kotlinValueParameterMetadata) { + shrinkAnnotations(kotlinValueParameterMetadata.annotations); + } @Override - public void visitFunctionValParameter(Clazz clazz, - KotlinMetadata kotlinMetadata, - KotlinFunctionMetadata kotlinFunctionMetadata, - KotlinValueParameterMetadata kotlinValueParameterMetadata) - { - - kotlinValueParameterMetadata.typeAccept(clazz, - kotlinMetadata, - kotlinFunctionMetadata, - this); - - if (kotlinValueParameterMetadata.flags.hasDefaultValue && - !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) - { + public void visitFunctionValParameter( + Clazz clazz, + KotlinMetadata kotlinMetadata, + KotlinFunctionMetadata kotlinFunctionMetadata, + KotlinValueParameterMetadata kotlinValueParameterMetadata) { + + kotlinValueParameterMetadata.typeAccept(clazz, kotlinMetadata, kotlinFunctionMetadata, this); + + if (kotlinValueParameterMetadata.flags.hasDefaultValue + && !usageMarker.isUsed(kotlinFunctionMetadata.referencedDefaultMethod)) { kotlinValueParameterMetadata.flags.hasDefaultValue = false; } + + visitAnyValueParameter(clazz, kotlinValueParameterMetadata); + } + + @Override + public void visitConstructorValParameter( + Clazz clazz, + KotlinClassKindMetadata kotlinClassKindMetadata, + KotlinConstructorMetadata kotlinConstructorMetadata, + KotlinValueParameterMetadata kotlinValueParameterMetadata) { + kotlinValueParameterMetadata.typeAccept( + clazz, kotlinClassKindMetadata, kotlinConstructorMetadata, this); + + visitAnyValueParameter(clazz, kotlinValueParameterMetadata); } @Override - public void visitConstructorValParameter(Clazz clazz, - KotlinClassKindMetadata kotlinClassKindMetadata, - KotlinConstructorMetadata kotlinConstructorMetadata, - KotlinValueParameterMetadata kotlinValueParameterMetadata) - { - kotlinValueParameterMetadata.typeAccept(clazz, - kotlinClassKindMetadata, - kotlinConstructorMetadata, - this); + public void visitPropertyValParameter( + Clazz clazz, + KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, + KotlinPropertyMetadata kotlinPropertyMetadata, + KotlinValueParameterMetadata kotlinValueParameterMetadata) { + kotlinValueParameterMetadata.typeAccept( + clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata, this); + + visitAnyValueParameter(clazz, kotlinValueParameterMetadata); } @Override - public void visitPropertyValParameter(Clazz clazz, - KotlinDeclarationContainerMetadata kotlinDeclarationContainerMetadata, - KotlinPropertyMetadata kotlinPropertyMetadata, - KotlinValueParameterMetadata kotlinValueParameterMetadata) - { - kotlinValueParameterMetadata.typeAccept(clazz, - kotlinDeclarationContainerMetadata, - kotlinPropertyMetadata, - this); + public void visitAnyEnumEntry( + Clazz clazz, + KotlinClassKindMetadata kotlinClassKindMetadata, + KotlinEnumEntryMetadata kotlinEnumEntryMetadata) { + shrinkAnnotations(kotlinEnumEntryMetadata.annotations); } @Override - public void visitAnyVersionRequirement(Clazz clazz, - KotlinVersionRequirementMetadata kotlinVersionRequirementMetadata) - { + public void visitAnyPropertyAccessor( + Clazz clazz, + KotlinMetadata kotlinMetadata, + KotlinPropertyMetadata kotlinPropertyMetadata, + KotlinPropertyAccessorMetadata kotlinPropertyAccessorMetadata) { + shrinkAnnotations(kotlinPropertyAccessorMetadata.annotations); } + @Override + public void visitAnyVersionRequirement( + Clazz clazz, KotlinVersionRequirementMetadata kotlinVersionRequirementMetadata) {} + // Small helper methods. /** - * Returns whether the given metadata element has its corresponding jvm - * implementation element shrunk, and thus should be shrunk itself. + * Returns whether the given metadata element has its corresponding jvm implementation element + * shrunk, and thus should be shrunk itself. */ - private boolean shouldShrinkMetadata(Object metadataElement, Processable jvmElement) - { + private boolean shouldShrinkMetadata(Object metadataElement, Processable jvmElement) { // If this method throws a NPE, there is a kotlin metadata // element for which we could not find the corresponding // jvm element to cache yet (see ClassReferenceFixer.visitKotlinMetadata). - return metadataElement != null && - !usageMarker.isUsed(jvmElement); - } - - private boolean shouldShrinkMetadata(KotlinPropertyAccessorMetadata kotlinPropertyAccessorMetadata) { - return kotlinPropertyAccessorMetadata != null && !usageMarker.isUsed(kotlinPropertyAccessorMetadata.referencedMethod); + return metadataElement != null && !usageMarker.isUsed(jvmElement); } - - /** - * Shrinks elements and their corresponding referenced element, based on - * markings on the referenced element. + * Shrinks elements and their corresponding referenced element, based on markings on the + * referenced element. * - * List is modified - must be a modifiable list! + *

List is modified - must be a modifiable list! */ - private void shrinkArray(List elements, - List referencedJavaElements) - { + private void shrinkArray(List elements, List referencedJavaElements) { shrinkArray(usageMarker, elements, referencedJavaElements); } - private void shrinkEnumEntries(List enumEntries) { - enumEntries.removeIf(usageMarker::isUsed); - } - /** - * Shrinks elements and their corresponding referenced element, based on - * markings on the referenced element. - * List is modified - must be a modifiable list! + * Shrinks elements and their corresponding referenced element, based on markings on the + * referenced element. + * + *

List is modified - must be a modifiable list! */ - static void shrinkArray(SimpleUsageMarker usageMarker, - List elements, - List referencedJavaElements) - { - for (int k = elements.size() - 1; k >= 0; k--) - { - if (!usageMarker.isUsed(referencedJavaElements.get(k))) - { - elements .remove(k); + static void shrinkArray( + SimpleUsageMarker usageMarker, + List elements, + List referencedJavaElements) { + for (int k = elements.size() - 1; k >= 0; k--) { + if (!usageMarker.isUsed(referencedJavaElements.get(k))) { + elements.remove(k); referencedJavaElements.remove(k); } } } + /** Shrinks Kotlin annotations and their arguments. */ + private void shrinkAnnotations(List annotations) { + for (int k = annotations.size() - 1; k >= 0; k--) { + KotlinAnnotation annotation = annotations.get(k); + if (usageMarker.isUsed(annotation)) { + shrinkMetadataArray(annotation.arguments); + } else { + annotations.remove(k); + } + } + } - /** - * Shrinks elements based on their markings. - */ - private void shrinkMetadataArray(List elements) - { - for (int k = elements.size() - 1; k >= 0; k--) - { - if (!usageMarker.isUsed(elements.get(k))) - { + /** Shrinks elements based on their markings. */ + private void shrinkMetadataArray(List elements) { + for (int k = elements.size() - 1; k >= 0; k--) { + if (!usageMarker.isUsed(elements.get(k))) { elements.remove(k); } } diff --git a/base/src/test/kotlin/proguard/shrink/KotlinShrinkerTest.kt b/base/src/test/kotlin/proguard/shrink/KotlinShrinkerTest.kt new file mode 100644 index 00000000..35ce55d1 --- /dev/null +++ b/base/src/test/kotlin/proguard/shrink/KotlinShrinkerTest.kt @@ -0,0 +1,578 @@ +package proguard.shrink + +import io.kotest.core.spec.style.BehaviorSpec +import io.kotest.matchers.collections.shouldBeEmpty +import io.kotest.matchers.nulls.shouldBeNull +import io.kotest.matchers.shouldBe +import io.mockk.spyk +import io.mockk.verify +import proguard.classfile.ProgramClass +import proguard.classfile.kotlin.KotlinAnnotatable +import proguard.classfile.kotlin.KotlinAnnotation +import proguard.classfile.kotlin.KotlinAnnotationArgument.StringValue +import proguard.classfile.kotlin.KotlinEnumEntryMetadata +import proguard.classfile.kotlin.KotlinFunctionMetadata +import proguard.classfile.kotlin.KotlinTypeMetadata +import proguard.classfile.kotlin.KotlinValueParameterMetadata +import proguard.classfile.kotlin.visitor.AllEnumEntryVisitor +import proguard.classfile.kotlin.visitor.AllFunctionVisitor +import proguard.classfile.kotlin.visitor.AllKotlinAnnotationArgumentVisitor +import proguard.classfile.kotlin.visitor.AllKotlinAnnotationVisitor +import proguard.classfile.kotlin.visitor.AllPropertyVisitor +import proguard.classfile.kotlin.visitor.AllTypeVisitor +import proguard.classfile.kotlin.visitor.KotlinAnnotationArgumentVisitor +import proguard.classfile.kotlin.visitor.KotlinAnnotationVisitor +import proguard.classfile.kotlin.visitor.KotlinClassVisitor +import proguard.classfile.kotlin.visitor.MultiKotlinAnnotationVisitor +import proguard.classfile.kotlin.visitor.MultiKotlinMetadataVisitor +import proguard.classfile.kotlin.visitor.ReferencedKotlinMetadataVisitor +import proguard.testutils.ClassPoolBuilder +import proguard.testutils.KotlinSource + +class KotlinShrinkerTest : BehaviorSpec({ + Given("a Kotlin class with a mutable property") { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "TestClass.kt", + """ + class TestClass { + var myProperty: Int + get() = 5 + set(value) { + println(value) + } + } + """.trimIndent(), + ), + ) + + val clazz = programClassPool.getClass("TestClass") + + When("the property is marked as used and shrinker is applied") { + val usageMarker = SimpleUsageMarker() + usageMarker.markAsUsed(programClassPool.getClass("TestClass")) + clazz.kotlinMetadataAccept( + AllPropertyVisitor { clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata -> + usageMarker.markAsUsed(kotlinPropertyMetadata) + }, + ) + clazz.kotlinMetadataAccept(KotlinShrinker(usageMarker)) + + Then("the property's setter parameters should be empty") { + var visited = false + clazz.kotlinMetadataAccept( + AllPropertyVisitor { clazz, kotlinDeclarationContainerMetadata, kotlinPropertyMetadata -> + visited = true + kotlinPropertyMetadata.setterParameter.shouldBeNull() + kotlinPropertyMetadata.setterParameters.shouldBeEmpty() + }, + ) + visited shouldBe true + } + } + } + + Given("A property with annotated type") { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + @Target(AnnotationTarget.TYPE) + annotation class MyTypeAnnotation(val annotationArgument : String) + + val x: @MyTypeAnnotation(annotationArgument = "annotated") String = "foo" + """.trimIndent(), + ), + kotlincArguments = listOf("-Xannotations-in-metadata"), + ) + + val fileFacadeClass = programClassPool.getClass("TestKt") + val usageMarker = SimpleUsageMarker() + + // Mark the property metadata as used so it does not get removed. + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllPropertyVisitor { _, _, kotlinPropertyMetadata -> + usageMarker.markAsUsed( + kotlinPropertyMetadata, + ) + }, + ), + ) + + When("the type annotation is used but its argument is not") { + fileFacadeClass.kotlinMetadataAccept( + AllKotlinAnnotationVisitor { _, _, annotation -> usageMarker.markAsUsed(annotation) }, + ) + And("the KotlinShrinker is run") { + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(usageMarker), + ), + ) + + val annotationArgVisitor = spyk() + val annotationVisitor = spyk() + + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllTypeVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ), + ) + + Then("the type annotation is not removed") { + verify(exactly = 1) { + annotationVisitor.visitAnyAnnotation( + fileFacadeClass, + ofType(), + ofType(), + ) + } + } + + Then("the type annotation argument is removed") { + verify(exactly = 0) { + annotationArgVisitor.visitStringArgument( + fileFacadeClass, + ofType(), + ofType(), + withArg { it.name shouldBe "annotationArgument" }, + StringValue("annotated"), + ) + } + } + } + } + + When("the type annotation is not used") { + And("the KotlinShrinker runs") { + val annotationVisitor = spyk() + val annotationArgVisitor = spyk() + + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(SimpleUsageMarker()), + ), + ) + + Then("the type annotation is shrunk entirely") { + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllTypeVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ), + ) + + verify(exactly = 0) { + annotationVisitor.visitAnyAnnotation( + fileFacadeClass, + ofType(), + ofType(), + ) + } + } + } + } + } + + Given("An annotated function") { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + @Target(AnnotationTarget.FUNCTION) + annotation class MyFunctionAnnotation(val annotationArgument: String) + + @MyFunctionAnnotation( + annotationArgument = "annotated") fun foo(): String { return "Foo" } + """.trimIndent(), + ), + kotlincArguments = listOf("-Xannotations-in-metadata"), + ) + + val fileFacadeClass = programClassPool.getClass("TestKt") + val usageMarker = SimpleUsageMarker() + + // Mark the function metadata as used so it does not get removed. + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllFunctionVisitor( + { _, _, kotlinFunctionMetadata -> usageMarker.markAsUsed(kotlinFunctionMetadata) }, + ), + ), + ) + + When("the function annotation is used but its argument is not") { + fileFacadeClass.kotlinMetadataAccept( + AllKotlinAnnotationVisitor { _, _, annotation -> usageMarker.markAsUsed(annotation) }, + ) + And("the KotlinShrinker is run") { + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(usageMarker), + ), + ) + + val annotationArgVisitor = spyk() + val annotationVisitor = spyk() + + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ) + + Then("the function annotation is not removed") { + verify(exactly = 1) { + annotationVisitor.visitAnyAnnotation( + fileFacadeClass, + ofType(), + ofType(), + ) + } + } + + Then("the function annotation argument is removed") { + verify(exactly = 0) { + annotationArgVisitor.visitStringArgument( + fileFacadeClass, + ofType(), + ofType(), + withArg { it.name shouldBe "annotationArgument" }, + StringValue("annotated"), + ) + } + } + } + } + + When("the function annotation is not used") { + And("the KotlinShrinker runs") { + val annotationVisitor = spyk() + val annotationArgVisitor = spyk() + + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(SimpleUsageMarker()), + ), + ) + + Then("the function annotation is shrunk entirely") { + fileFacadeClass.accept( + ReferencedKotlinMetadataVisitor( + AllTypeVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ), + ) + + verify(exactly = 0) { + annotationVisitor.visitAnyAnnotation( + fileFacadeClass, + ofType(), + ofType(), + ) + } + } + } + } + } + + Given("Annotated value parameters") { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + @Target(AnnotationTarget.VALUE_PARAMETER) + annotation class MyValueParameterAnnotation(val annotationArgument: String) + + fun foo( + @MyValueParameterAnnotation(annotationArgument = "function") + valueParam : String = "default" ) : String { return valueParam } + + class Bar( + @MyValueParameterAnnotation(annotationArgument = "constructor") + valueParam : String = "default") + + var myProperty = "initialized" + set(@MyValueParameterAnnotation(annotationArgument = "property") valueParam) + { field = valueParam + "concat" } + """.trimIndent(), + ), + kotlincArguments = listOf("-Xannotations-in-metadata"), + ) + + val usageMarker = SimpleUsageMarker() + + // Mark the metadata as used so it does not get removed. + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + MultiKotlinMetadataVisitor( + KotlinClassVisitor { _, kotlinClassKindMetadata -> + // Don't shrink the class kind metadata + usageMarker.markAsUsed(kotlinClassKindMetadata) + // Don't shrink the constructor metadata + kotlinClassKindMetadata.constructors.forEach { constructorMetadata -> + usageMarker.markAsUsed(constructorMetadata) + // Don't shrink the value parameter metadata + constructorMetadata.valueParameters.forEach { + usageMarker.markAsUsed(it) + } + } + }, + AllFunctionVisitor( + { _, _, kotlinFunctionMetadata -> + usageMarker.markAsUsed(kotlinFunctionMetadata) + kotlinFunctionMetadata.valueParameters.forEach { metadata -> usageMarker.markAsUsed(metadata) } + }, + ), + AllPropertyVisitor { _, _, kotlinPropertyMetadata -> + if (kotlinPropertyMetadata.setterMetadata != null) { + // Don't shrink the property + usageMarker.markAsUsed(kotlinPropertyMetadata) + // Don't shrink the setter + usageMarker.markAsUsed(kotlinPropertyMetadata.setterMetadata) + usageMarker.markAsUsed(kotlinPropertyMetadata.setterParameter) + usageMarker.markAsUsed(kotlinPropertyMetadata.setterMetadata!!.referencedMethod) + } + }, + ), + ), + ) + + When("the annotation metadata is used but its argument is not") { + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + AllKotlinAnnotationVisitor { _, _, annotation -> + usageMarker.markAsUsed(annotation) + }, + ), + ) + + And("the KotlinShrinker is run") { + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(usageMarker), + ), + ) + + val annotationArgVisitor = spyk() + val annotationVisitor = spyk() + + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ) + + Then("the annotations are not removed") { + verify(exactly = 3) { + annotationVisitor.visitAnyAnnotation( + ofType(), + ofType(), + ofType(), + ) + } + } + + Then("the function annotation argument is removed") { + verify(exactly = 0) { + annotationArgVisitor.visitStringArgument( + ofType(), + ofType(), + ofType(), + withArg { it.name shouldBe "annotationArgument" }, + ofType(), + ) + } + } + } + } + + When("the function annotation is not used") { + And("the KotlinShrinker runs") { + val annotationVisitor = spyk() + val annotationArgVisitor = spyk() + + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(SimpleUsageMarker()), + ), + ) + + Then("the function annotation is shrunk entirely") { + programClassPool.classesAccept( + ReferencedKotlinMetadataVisitor( + AllTypeVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ), + ) + + verify(exactly = 0) { + annotationVisitor.visitAnyAnnotation( + ofType(), + ofType(), + ofType(), + ) + } + } + } + } + } + + Given("An annotated enum value") { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + @Target(AnnotationTarget.FIELD) + annotation class MyEnumEntryAnnotation(val string: String) + + enum class MyEnumClass { + @MyEnumEntryAnnotation(string = "foo") + MY_ENUM_ENTRY + } + """.trimIndent(), + ), + kotlincArguments = listOf("-Xannotations-in-metadata"), + ) + + val myEnumClass = programClassPool.getClass("MyEnumClass") + val usageMarker = SimpleUsageMarker() + + // Mark the function metadata as used so it does not get removed. + myEnumClass.accept( + ReferencedKotlinMetadataVisitor( + MultiKotlinMetadataVisitor( + KotlinClassVisitor { _, classKindMetadata, + -> + usageMarker.markAsUsed(classKindMetadata) + }, + AllEnumEntryVisitor { _, _, kotlinEnumEntryMetadata -> + usageMarker.markAsUsed(kotlinEnumEntryMetadata.referencedEnumEntry) + usageMarker.markAsUsed(kotlinEnumEntryMetadata) + }, + ), + ), + ) + + When("the annotation metadata is used but its argument is not") { + myEnumClass.kotlinMetadataAccept( + AllKotlinAnnotationVisitor { _, _, annotation -> usageMarker.markAsUsed(annotation) }, + ) + And("the KotlinShrinker is run") { + myEnumClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(usageMarker), + ), + ) + + val annotationArgVisitor = spyk() + val annotationVisitor = spyk() + + myEnumClass.accept( + ReferencedKotlinMetadataVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ) + + Then("the annotation metadata is not removed") { + verify(exactly = 1) { + annotationVisitor.visitAnyAnnotation( + myEnumClass, + ofType(), + ofType(), + ) + } + } + + Then("the function annotation argument is removed") { + verify(exactly = 0) { + annotationArgVisitor.visitStringArgument( + myEnumClass, + ofType(), + ofType(), + withArg { it.name shouldBe "annotationArgument" }, + StringValue("annotated"), + ) + } + } + } + } + + When("the annotation metadata is not used") { + And("the KotlinShrinker runs") { + val annotationVisitor = spyk() + val annotationArgVisitor = spyk() + + myEnumClass.accept( + ReferencedKotlinMetadataVisitor( + KotlinShrinker(SimpleUsageMarker()), + ), + ) + + Then("the annotation metadata is shrunk entirely") { + myEnumClass.accept( + ReferencedKotlinMetadataVisitor( + AllTypeVisitor( + AllKotlinAnnotationVisitor( + MultiKotlinAnnotationVisitor( + annotationVisitor, + AllKotlinAnnotationArgumentVisitor(annotationArgVisitor), + ), + ), + ), + ), + ) + + verify(exactly = 0) { + annotationVisitor.visitAnyAnnotation( + myEnumClass, + ofType(), + ofType(), + ) + } + } + } + } + } +}) diff --git a/docs/md/manual/releasenotes.md b/docs/md/manual/releasenotes.md index 2c5df577..2138abff 100644 --- a/docs/md/manual/releasenotes.md +++ b/docs/md/manual/releasenotes.md @@ -1,3 +1,9 @@ +## Version 7.9.1 + +### Bugfixes + +- Fix regression in Kotlin metadata shrinking (#527). + ## Version 7.9 ### Kotlin support