Fix MemberDescriptorSpecializer checking wrong processing flags

This diff also ensures the `IS_CLASS_AVAILABLE` processing flag is set on classes instead of its members.
This requires us to change usages of this flag in some other places as well.
This commit is contained in:
Robin Lefever
2023-11-29 13:05:53 +01:00
parent a02100cb93
commit 12c9c3f23e
6 changed files with 171 additions and 69 deletions

View File

@@ -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'
}
}

View File

@@ -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);

View File

@@ -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(),

View File

@@ -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;

View File

@@ -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;"
}
}
}
})

View File

@@ -3,6 +3,7 @@
### Bugfixes
- Fix potential access issues when backporting.
- Fix potential NoClassDefFoundError when using type specialization optimization. (#373)
## Version 7.4.1