diff --git a/base/build.gradle b/base/build.gradle index ba1585c9..e6763dce 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -34,7 +34,7 @@ dependencies { testImplementation 'io.kotest:kotest-property-jvm:5.5.4' // for kotest property test testImplementation 'io.mockk:mockk:1.13.2' // for mocking - testImplementation(testFixtures("com.guardsquare:proguard-core:9.0.8")) { + testImplementation(testFixtures("com.guardsquare:proguard-core:${proguardCoreVersion}")) { exclude group: 'com.guardsquare', module: 'proguard-core' } } diff --git a/base/src/main/java/proguard/mark/Marker.java b/base/src/main/java/proguard/mark/Marker.java index 39d52645..ffbb75bf 100644 --- a/base/src/main/java/proguard/mark/Marker.java +++ b/base/src/main/java/proguard/mark/Marker.java @@ -234,8 +234,7 @@ public class Marker implements Pass { // Program classes are always available and safe to generalize/specialize from/to. ClassVisitor isClassAvailableMarker = - new AllMemberVisitor( - new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE)); + new ProcessingFlagSetter(ProcessingFlags.IS_CLASS_AVAILABLE); programClassPool.classesAccept(isClassAvailableMarker); diff --git a/base/src/main/java/proguard/optimize/MemberDescriptorSpecializer.java b/base/src/main/java/proguard/optimize/MemberDescriptorSpecializer.java index 042f0f79..d1ed20e6 100644 --- a/base/src/main/java/proguard/optimize/MemberDescriptorSpecializer.java +++ b/base/src/main/java/proguard/optimize/MemberDescriptorSpecializer.java @@ -141,7 +141,7 @@ implements MemberVisitor if (valueClass != null && valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(fieldType)) && - (programField.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) + (valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { logger.debug("MemberDescriptorSpecializer [{}.{} {}] -> {}", programClass.getName(), @@ -210,7 +210,7 @@ implements MemberVisitor if (valueClass != null && valueClass.extendsOrImplements(ClassUtil.internalClassNameFromClassType(parameterType)) && - (programMethod.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) + (valueClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { logger.debug("MemberDescriptorSpecializer [{}.{}{}]: parameter #{}: {} -> {}", programClass.getName(), diff --git a/base/src/main/java/proguard/optimize/MemberReferenceGeneralizer.java b/base/src/main/java/proguard/optimize/MemberReferenceGeneralizer.java index 472e60d4..ea570c8e 100644 --- a/base/src/main/java/proguard/optimize/MemberReferenceGeneralizer.java +++ b/base/src/main/java/proguard/optimize/MemberReferenceGeneralizer.java @@ -22,12 +22,22 @@ package proguard.optimize; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.classfile.*; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.Field; +import proguard.classfile.Member; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.constant.*; +import proguard.classfile.constant.AnyMethodrefConstant; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.*; -import proguard.classfile.instruction.*; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassVisitor; import proguard.util.ProcessingFlags; @@ -200,7 +210,7 @@ implements InstructionVisitor, // DGD-486: Only generalize members which are always available. Partial replacement of a class that is not // available on all platforms may result in a VerifyError at runtime. if (referencedMember != null && - (referencedMember.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) + (clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { clazz.constantPoolEntryAccept(refConstant.u2classIndex, this); @@ -287,13 +297,14 @@ implements InstructionVisitor, // Otherwise, look in the super class itself. // Only consider public classes and methods, to avoid any // access problems. + // Only consider classes that are marked as available. if (generalizedClass == null && - (superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0) + (superClass.getAccessFlags() & AccessConstants.PUBLIC) != 0 && + (superClass.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { Method method = superClass.findMethod(memberName, memberType); if (method != null && - (method.getAccessFlags() & AccessConstants.PUBLIC) != 0 && - (method.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) + (method.getAccessFlags() & AccessConstants.PUBLIC) != 0) { // Remember the generalized class and class member. generalizedClass = superClass; @@ -308,7 +319,7 @@ implements InstructionVisitor, Field field = clazz.findField(memberName, memberType); if (field != null && - (field.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) + (clazz.getProcessingFlags() & ProcessingFlags.IS_CLASS_AVAILABLE) != 0) { // Remember the generalized class and class member. generalizedClass = clazz; diff --git a/base/src/test/kotlin/proguard/optimize/MemberDescriptorSpecializerTest.kt b/base/src/test/kotlin/proguard/optimize/MemberDescriptorSpecializerTest.kt index 0e1cbdbf..bc433d81 100644 --- a/base/src/test/kotlin/proguard/optimize/MemberDescriptorSpecializerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/MemberDescriptorSpecializerTest.kt @@ -3,6 +3,7 @@ package proguard.optimize import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe import proguard.classfile.AccessConstants +import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.Member import proguard.classfile.attribute.visitor.AllAttributeVisitor @@ -26,7 +27,67 @@ import proguard.util.ProcessingFlagSetter import proguard.util.ProcessingFlags.IS_CLASS_AVAILABLE class MemberDescriptorSpecializerTest : FreeSpec({ - "Given a method with a more general parameter type than its use" - { + + fun specializeMemberDescriptors( + programClassPool: ClassPool, + libraryClassPool: ClassPool, + ) { + // Mark all program classes as available. + programClassPool.classesAccept(ProcessingFlagSetter(IS_CLASS_AVAILABLE)) + + // Setup the OptimizationInfo on the classes + val keepMarker = KeepMarker() + libraryClassPool.classesAccept(keepMarker) + libraryClassPool.classesAccept(AllMemberVisitor(keepMarker)) + + programClassPool.classesAccept(ProgramClassOptimizationInfoSetter()) + programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter())) + + // Create the optimization as in Optimizer + val fillingOutValuesClassVisitor = ClassVisitorFactory { + val valueFactory: ValueFactory = ParticularValueFactory() + val storingInvocationUnit: InvocationUnit = StoringInvocationUnit( + valueFactory, + true, + true, + true + ) + ClassAccessFilter( + 0, AccessConstants.SYNTHETIC, + AllMethodVisitor( + AllAttributeVisitor( + DebugAttributeVisitor( + "Filling out fields, method parameters, and return values", + PartialEvaluator( + valueFactory, storingInvocationUnit, + true + ) + ) + ) + ) + ) + } + + programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor()) + + // Specialize class member descriptors, based on partial evaluation. + programClassPool.classesAccept( + AllMemberVisitor( + OptimizationInfoMemberFilter( + MemberDescriptorSpecializer( + true, + true, + true, + null, + null, + null + ) + ) + ) + ) + } + + "Given a method with a more general program class pool parameter type than its use" - { val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( JavaSource( "Test.java", @@ -48,60 +109,7 @@ class MemberDescriptorSpecializerTest : FreeSpec({ ) "When specializing the member descriptors" - { - - // Mark all members as available. - programClassPool.classesAccept(AllMemberVisitor(ProcessingFlagSetter(IS_CLASS_AVAILABLE))) - - // Setup the OptimizationInfo on the classes - val keepMarker = KeepMarker() - libraryClassPool.classesAccept(keepMarker) - libraryClassPool.classesAccept(AllMemberVisitor(keepMarker)) - - programClassPool.classesAccept(ProgramClassOptimizationInfoSetter()) - programClassPool.classesAccept(AllMemberVisitor(ProgramMemberOptimizationInfoSetter())) - - // Create the optimization as in Optimizer - val fillingOutValuesClassVisitor = ClassVisitorFactory { - val valueFactory: ValueFactory = ParticularValueFactory() - val storingInvocationUnit: InvocationUnit = StoringInvocationUnit( - valueFactory, - true, - true, - true - ) - ClassAccessFilter( - 0, AccessConstants.SYNTHETIC, - AllMethodVisitor( - AllAttributeVisitor( - DebugAttributeVisitor( - "Filling out fields, method parameters, and return values", - PartialEvaluator( - valueFactory, storingInvocationUnit, - true - ) - ) - ) - ) - ) - } - - programClassPool.classesAccept(fillingOutValuesClassVisitor.createClassVisitor()) - - // Specialize class member descriptors, based on partial evaluation. - programClassPool.classesAccept( - AllMemberVisitor( - OptimizationInfoMemberFilter( - MemberDescriptorSpecializer( - true, - true, - true, - null, - null, - null - ) - ) - ) - ) + specializeMemberDescriptors(programClassPool, libraryClassPool) "Then the member descriptor should be correctly specialised" { lateinit var memberDescriptor: String @@ -122,4 +130,87 @@ class MemberDescriptorSpecializerTest : FreeSpec({ } } } + + "Given a field with a more general program class pool parameter type than its use" - { + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + JavaSource( + "Test.java", + """ + public class Test { + static Bar myField = null; + + public static void main(String[] args) { + myField = new Foo(); + } + } + + class Bar { } + + class Foo extends Bar { } + """.trimIndent() + ) + ) + + "When specializing the member descriptors" - { + specializeMemberDescriptors(programClassPool, libraryClassPool) + + "Then the member descriptor should be correctly specialised" { + lateinit var memberDescriptor: String + programClassPool.classAccept( + "Test", + AllMemberVisitor( + MemberNameFilter( + "myField*", + object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { + memberDescriptor = member.getDescriptor(clazz) + } + } + ) + ) + ) + memberDescriptor shouldBe "LFoo;" + } + } + } + + "Given a field with a more general library class pool parameter type than its use" - { + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + JavaSource( + "Test.java", + """ + public class Test { + static java.lang.Object myField = null; + + public static void main(String[] args) { + myField = new java.lang.StringBuffer(); + } + } + """.trimIndent() + ) + ) + + "When specializing the member descriptors" - { + specializeMemberDescriptors(programClassPool, libraryClassPool) + + "Then the member descriptor should be correctly specialised" { + lateinit var memberDescriptor: String + programClassPool.classAccept( + "Test", + AllMemberVisitor( + MemberNameFilter( + "myField*", + object : MemberVisitor { + override fun visitAnyMember(clazz: Clazz, member: Member) { + memberDescriptor = member.getDescriptor(clazz) + } + } + ) + ) + ) + // Library classes are not marked as available by default. Therefore, they are not specialized. + memberDescriptor shouldBe "Ljava/lang/Object;" + } + } + } }) diff --git a/docs/md/manual/releasenotes.md b/docs/md/manual/releasenotes.md index eb7a8dd6..48dfdc34 100644 --- a/docs/md/manual/releasenotes.md +++ b/docs/md/manual/releasenotes.md @@ -3,6 +3,7 @@ ### Bugfixes - Fix potential access issues when backporting. +- Fix potential NoClassDefFoundError when using type specialization optimization. (#373) ## Version 7.4.1