From 7234690ae3778dc7b6ef466977acbc48da4a5c57 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 28 Dec 2021 18:03:06 +0100 Subject: [PATCH 001/195] Skeleton for new KotlinLambdaMerger to be used between shrinking and optimising --- base/src/main/java/proguard/ProGuard.java | 16 ++++++++++ .../optimize/kotlin/KotlinLambdaMerger.java | 30 +++++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index eb33d8517..e2184166f 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -39,6 +39,7 @@ import proguard.optimize.LineNumberTrimmer; import proguard.optimize.Optimizer; import proguard.optimize.gson.GsonOptimizer; +import proguard.optimize.kotlin.KotlinLambdaMerger; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.pass.PassRunner; import proguard.preverify.*; @@ -175,6 +176,11 @@ public void execute() throws Exception shrink(false); } + // if (configuration.mergeKotlinLambdaClasses) + //{ + mergeKotlinLambdaClasses(); + //} + // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : @@ -415,6 +421,16 @@ private void shrink(boolean afterOptimizer) throws Exception } } + /** + * Reduce the size needed to represent Kotlin lambda's. + * The classes that are generated for lambda's with a same structure and from the same package are merged into a group. + */ + private void mergeKotlinLambdaClasses() throws IOException { + programClassPool = new KotlinLambdaMerger(configuration).execute(programClassPool, + libraryClassPool, + resourceFilePool); + } + /** * Optimizes usages of the Gson library. diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java new file mode 100644 index 000000000..8164d4016 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -0,0 +1,30 @@ +package proguard.optimize.kotlin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.Configuration; +import proguard.classfile.ClassPool; +import proguard.classfile.Clazz; +import proguard.classfile.visitor.ClassPoolFiller; +import proguard.classfile.visitor.ImplementedClassFilter; +import proguard.resources.file.ResourceFilePool; + +import java.io.IOException; + +public class KotlinLambdaMerger { + + private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); + private final Configuration configuration; + + public KotlinLambdaMerger(Configuration configuration) + { + this.configuration = configuration; + } + + public ClassPool execute(ClassPool programClassPool, + ClassPool libraryClassPool, + ResourceFilePool resourceFilePool) throws IOException + { + return programClassPool; + } +} From b45bb362ab13ff0094bfdfae706a8973be95c588 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 28 Dec 2021 18:58:47 +0100 Subject: [PATCH 002/195] KotlinLambdaMerger: find lambda & function0 classes + find subclasses --- .../optimize/kotlin/KotlinLambdaMerger.java | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 8164d4016..16d23d07a 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -13,6 +13,9 @@ public class KotlinLambdaMerger { + private final String KOTLIN_LAMBDA_CLASS = "kotlin/jvm/internal/Lambda"; + private final String KOTLIN_FUNCTION0_INTERFACE = "kotlin/jvm/functions/Function0"; + private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; @@ -25,6 +28,45 @@ public ClassPool execute(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool) throws IOException { + // A class pool where the applicable lambda's will be stored + ClassPool lambdaClassPool = new ClassPool(); + + // get the Lambda class and the Function0 interface + Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); + Clazz kotlinFunction0Interface = getKotlinFunction0Interface(programClassPool, libraryClassPool); + if (kotlinLambdaClass == null) { + logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", KOTLIN_LAMBDA_CLASS); + } + if (kotlinFunction0Interface == null) { + logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", KOTLIN_FUNCTION0_INTERFACE); + } + + if (kotlinLambdaClass != null && kotlinFunction0Interface != null) { + // find all lambda classes of arity 0 + programClassPool.classesAccept(new ImplementedClassFilter(kotlinFunction0Interface, false, + new ImplementedClassFilter(kotlinLambdaClass, false, + new ClassPoolFiller(lambdaClassPool), null), null) + ); + // filter for lambda's without constructor arguments -> put them in a classpool + // TODO: filter on classes that have a constructor WITHOUT arguments! + } return programClassPool; } + + private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) + { + Clazz kotlinLambdaClass = programClassPool.getClass(KOTLIN_LAMBDA_CLASS); + if (kotlinLambdaClass == null) { + kotlinLambdaClass = libraryClassPool.getClass(KOTLIN_LAMBDA_CLASS); + } + return kotlinLambdaClass; + } + + private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool libraryClassPool) + { + Clazz kotlinFunction0Interface = programClassPool.getClass(KOTLIN_FUNCTION0_INTERFACE); + if (kotlinFunction0Interface == null) { + kotlinFunction0Interface = libraryClassPool.getClass(KOTLIN_FUNCTION0_INTERFACE); + } + } } From e1e33ef3d719b6afcadf7cfe6e8e26c8a109ac4b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 28 Dec 2021 19:01:34 +0100 Subject: [PATCH 003/195] Return Function0 interface class --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 16d23d07a..07a70788c 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -68,5 +68,6 @@ private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool if (kotlinFunction0Interface == null) { kotlinFunction0Interface = libraryClassPool.getClass(KOTLIN_FUNCTION0_INTERFACE); } + return kotlinFunction0Interface; } } From 1f9423113f740cf193103d8217c51447be1a2a1d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 28 Dec 2021 19:26:45 +0100 Subject: [PATCH 004/195] Filter on lambda classes with 0-argument constructor --- .../optimize/kotlin/KotlinLambdaMerger.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 07a70788c..63ea4d527 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -5,6 +5,7 @@ import proguard.Configuration; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; +import proguard.classfile.visitor.ClassMethodFilter; import proguard.classfile.visitor.ClassPoolFiller; import proguard.classfile.visitor.ImplementedClassFilter; import proguard.resources.file.ResourceFilePool; @@ -42,13 +43,17 @@ public ClassPool execute(ClassPool programClassPool, } if (kotlinLambdaClass != null && kotlinFunction0Interface != null) { - // find all lambda classes of arity 0 + // find all lambda classes of arity 0 and with an empty closure + // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V + // (i.e. no arguments) if the closure is empty programClassPool.classesAccept(new ImplementedClassFilter(kotlinFunction0Interface, false, new ImplementedClassFilter(kotlinLambdaClass, false, - new ClassPoolFiller(lambdaClassPool), null), null) + new ClassMethodFilter("", "()V", + new ClassPoolFiller(lambdaClassPool), null), null), null) ); - // filter for lambda's without constructor arguments -> put them in a classpool - // TODO: filter on classes that have a constructor WITHOUT arguments! + + + } return programClassPool; } From 4f7c6184443da026605965f2ed3c2858fff952ff Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 18:59:37 +0100 Subject: [PATCH 005/195] PackageGrouper: a ClassVisitor that creates a class pool per package --- .../optimize/kotlin/PackageGrouper.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java diff --git a/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java b/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java new file mode 100644 index 000000000..02fe06712 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java @@ -0,0 +1,59 @@ +package proguard.optimize.kotlin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.ClassPool; +import proguard.classfile.Clazz; +import proguard.classfile.ProgramClass; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassPoolVisitor; +import proguard.classfile.visitor.ClassVisitor; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class + */ +public class PackageGrouper implements ClassVisitor { + + private final Map packageClassPools = new HashMap<>(); + private static final Logger logger = LogManager.getLogger(PackageGrouper.class); + + @Override + public void visitAnyClass(Clazz clazz) + { + String classPackageName = ClassUtil.internalPackageName(clazz.getName()); + // or + // String classPackageName = ClassUtil.internalPackageName(clazz.getName()); + if (!packageClassPools.containsKey(classPackageName)) + { + logger.info("New package found: {}", classPackageName); + packageClassPools.put(classPackageName, new ClassPool()); + } + packageClassPools.get(classPackageName).addClass(clazz); + } + + public int size() + { + return packageClassPools.size(); + } + + public boolean containsPackage(String packageName) + { + return packageClassPools.containsKey(packageName); + } + + public Iterable packageNames() + { + return packageClassPools.keySet(); + } + + public void packagesAccept(ClassPoolVisitor classPoolVisitor) + { + for (ClassPool packageClassPool : packageClassPools.values()) + { + classPoolVisitor.visitClassPool(packageClassPool); + } + } +} From 26a90ae3192ac7beb2be4fb67155c91bbe00b5aa Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:00:33 +0100 Subject: [PATCH 006/195] MethodCopier: a MemberVisitor that copies methods to a destination class --- .../optimize/kotlin/MethodCopier.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/MethodCopier.java diff --git a/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java b/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java new file mode 100644 index 000000000..976cf275f --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java @@ -0,0 +1,88 @@ +package proguard.optimize.kotlin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.MemberVisitor; + +class MethodCopier implements MemberVisitor, AttributeVisitor, InstructionVisitor +{ + private final ProgramClass destinationClass; + private final ClassBuilder classBuilder; + private final String newMethodNamePrefix; + private final ConstantAdder constantAdder; + private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); + private final ExceptionInfoAdder exceptionInfoAdder; + private int methodCounter = 0; + private static final Logger logger = LogManager.getLogger(MethodCopier.class); + + + public MethodCopier(ProgramClass destinationClass, String newMethodNamePrefix) + { + this.destinationClass = destinationClass; + this.classBuilder = new ClassBuilder(destinationClass); + this.newMethodNamePrefix = newMethodNamePrefix; + this.constantAdder = new ConstantAdder(destinationClass); + this.exceptionInfoAdder = new ExceptionInfoAdder(this.destinationClass, this.codeAttributeComposer); + } + + private int getNewMethodIndex() + { + int methodIndex = this.methodCounter; + this.methodCounter++; + return methodIndex; + } + + @Override + public void visitAnyMember(Clazz clazz, Member member) { + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + codeAttributeComposer.reset(); + programMethod.attributesAccept(programClass, this); + int methodIndex = getNewMethodIndex(); + String newMethodName = newMethodNamePrefix; + if (methodIndex > 1) + { + newMethodName += "$" + methodIndex; + } + ProgramMethod newMethod = classBuilder.addAndReturnMethod(AccessConstants.PRIVATE, newMethodName, programMethod.getDescriptor(programClass)); + codeAttributeComposer.addCodeAttribute(this.destinationClass, newMethod); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) { + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); + // copy code and exceptions + codeAttribute.instructionsAccept(clazz, method, this); + codeAttribute.exceptionsAccept(clazz, method, this.exceptionInfoAdder); + codeAttribute.attributesAccept(clazz, method, this); + codeAttributeComposer.endCodeFragment(); + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + // copy instruction + codeAttributeComposer.appendInstruction(offset, instruction); + } + + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + // ensure the referenced constant is in the constant pool at the correct index + constantInstruction.constantIndex = this.constantAdder.addConstant(clazz, constantInstruction.constantIndex); + // copy instruction + codeAttributeComposer.appendInstruction(offset, constantInstruction); + } +} From 0a6870101f1d7ff0476e67a1c0b5d7db8699a832 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:01:42 +0100 Subject: [PATCH 007/195] LambdaGroupInvokeCodeBuilder: compose code for lambda group invoke --- .../kotlin/LambdaGroupInvokeCodeBuilder.java | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java new file mode 100644 index 000000000..befc4f744 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java @@ -0,0 +1,77 @@ +package proguard.optimize.kotlin; + +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramField; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.CompactCodeAttributeComposer; +import proguard.classfile.instruction.Instruction; + +import java.util.ArrayList; +import java.util.List; + +public class LambdaGroupInvokeCodeBuilder { + + private int caseIndexCounter = 0; + // tune the initial list size depending on the expected amount of lambda's that are merged + private final List caseInstructions = new ArrayList<>(5); + + public void addNewCase(Instruction[] instructions) + { + int caseIndex = getNewCaseIndex(); + caseInstructions.add(instructions); + } + + private int getNewCaseIndex() + { + int caseIndex = this.caseIndexCounter; + this.caseIndexCounter++; + return caseIndex; + } + + public int getCaseIndexCounter() + { + return this.caseIndexCounter; + } + + public ClassBuilder.CodeBuilder build(ProgramClass lambdaGroup, ProgramField classIdField) + { + return composer -> { + //InstructionSequenceBuilder instructionSequenceBuilder = new InstructionSequenceBuilder(); + // TODO: use instruction builder or code attribute composer to build (a part of) + // the code attribute of the invoke method of the lambda group + int cases = getCaseIndexCounter(); + + if (cases == 0) + { + composer.return_(); + return; + } + + CompactCodeAttributeComposer.Label[] caseLabels = new CompactCodeAttributeComposer.Label[cases - 1]; + for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) + { + caseLabels[caseIndex] = composer.createLabel(); + } + CompactCodeAttributeComposer.Label defaultLabel = composer.createLabel(); + CompactCodeAttributeComposer.Label endOfMethodLabel = composer.createLabel(); + composer + .aload_0() + .getfield(lambdaGroup, classIdField) + // add extra instructions? + .tableswitch(defaultLabel, 0, cases - 2, caseLabels); + for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) + { + composer + .label(caseLabels[caseIndex]) + .appendInstructions(caseInstructions.get(caseIndex)) + .goto_(endOfMethodLabel); + } + composer + .label(defaultLabel) + .appendInstructions(caseInstructions.get(cases - 1)) + .label(endOfMethodLabel) + .areturn(); + }; + } +} From a92c963c7416ff932f0f67d3f42bb4ce97e486af Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:02:55 +0100 Subject: [PATCH 008/195] KotlinLambdaEnclosingMethodUpdater: lambda group instead of lambda group --- .../KotlinLambdaEnclosingMethodUpdater.java | 306 ++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java new file mode 100644 index 000000000..a9ba72e56 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -0,0 +1,306 @@ +package proguard.optimize.kotlin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.Configuration; +import proguard.classfile.*; +import proguard.classfile.attribute.*; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.RefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolShrinker; +import proguard.classfile.editor.InnerClassesAttributeEditor; +import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.ClassPrinter; +import proguard.classfile.visitor.MemberVisitor; +import proguard.resources.file.ResourceFilePool; +import proguard.shrink.Shrinker; + +import java.io.IOException; +import java.util.*; + +public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, MemberVisitor, InstructionVisitor { + + private ClassPool programClassPool; + private ClassPool libraryClassPool; + private final ProgramClass lambdaGroup; + private final int classId; + private boolean visitEnclosingMethodAttribute = false; + private boolean visitEnclosingMethod = false; + private boolean visitEnclosingCode = false; + private static final Logger logger = LogManager.getLogger(KotlinLambdaEnclosingMethodUpdater.class); + private Clazz currentLambdaClass; + private SortedSet offsetsWhereLambdaIsReferenced; + private int lambdaConstantIndex; + + public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, + ClassPool libraryClassPool, + ProgramClass lambdaGroup, + int classId) { + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.lambdaGroup = lambdaGroup; + this.classId = classId; + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) + { + + } + + @Override + public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttribute enclosingMethodAttribute) { + // the given method must be the method where the lambda is defined + Clazz enclosingClass = enclosingMethodAttribute.referencedClass; + if (visitEnclosingMethodAttribute || enclosingClass == lambdaClass) { + return; + } + + visitEnclosingMethodAttribute = true; + currentLambdaClass = lambdaClass; + enclosingMethodAttribute.referencedMethodAccept(this); + currentLambdaClass = null; + visitEnclosingMethodAttribute = false; + + // remove lambda class as inner class of its enclosing class + enclosingClass.attributeAccept("InnerClasses", new InnerClassRemover(lambdaClass)); + + // remove all references to lambda class from the constant pool of its enclosing class + enclosingClass.accept(new ConstantPoolShrinker()); + + } + + @Override + public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclosingMethod) { + // the given class must be the class that defines the lambda + // the given method must be the method where the lambda is defined + if (!visitEnclosingMethodAttribute || visitEnclosingMethod) { + return; + } + this.lambdaConstantIndex = findConstantIndexOfLambdaInEnclosingClass(this.currentLambdaClass, enclosingClass); + if (this.lambdaConstantIndex < 0) + { + // the lambda class is not used: this should not happen + logger.warn("Lambda class {} not found in constant pool of class {}", this.currentLambdaClass, enclosingClass); + return; + } + this.offsetsWhereLambdaIsReferenced = new TreeSet<>(); + visitEnclosingMethod = true; + enclosingMethod.attributesAccept(enclosingClass, this); + visitEnclosingMethod = false; + } + + private int findConstantIndexOfLambdaInEnclosingClass(Clazz lambdaClass, ProgramClass enclosingClass) + { + ClassConstantFinder finder = new ClassConstantFinder(lambdaClass); + logger.info("{} constants in constant pool of class {}", enclosingClass.u2constantPoolCount, enclosingClass); + for (int index = 0; index < enclosingClass.u2constantPoolCount; index++) + { + try { + enclosingClass.constantPoolEntryAccept(index, finder); + if (finder.isClassFound()) { + return index; + } + } + catch (NullPointerException exception) + { + logger.error("Could not read constant at index {} in constant pool of class {}", index, enclosingClass); + } + } + return -1; + } + + @Override + public void visitCodeAttribute(Clazz enclosingClass, Method enclosingMethod, CodeAttribute codeAttribute) + { + // This attribute visitor should only be used for program classes. + try + { + visitCodeAttribute((ProgramClass) enclosingClass, (ProgramMethod) enclosingMethod, codeAttribute); + } + catch (ClassCastException exception) + { + logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), enclosingClass, enclosingMethod); + } + } + + public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclosingMethod, CodeAttribute codeAttribute) + { + // the given class must be the class that defines the lambda + // the given method must be the method where the lambda is defined + // the given code attribute must contain the original definition of the lambda: + // - load LambdaClass.INSTANCE + // - or instantiate LambdaClass() + if (!visitEnclosingMethodAttribute || !visitEnclosingMethod || visitEnclosingCode) + { + return; + } + visitEnclosingCode = true; + // find the offsets where the lambda class is instantiated or loaded or referenced + codeAttribute.instructionsAccept(enclosingClass, enclosingMethod, this); + + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + // enclosing method { + // ... + // Function0 lambda = new LambdaGroup(classId); + // ... + // } + // create instructions to instantiate the lambda group with the correct id + InstructionSequenceBuilder builder = new InstructionSequenceBuilder(enclosingClass, + this.programClassPool, + this.libraryClassPool); + Instruction[] instantiateLambdaGroupInstructions = builder + .new_(lambdaGroup) + .dup() + .iconst(this.classId) + .invokespecial(lambdaGroup, lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, "(I)V")) + .instructions(); + + codeAttributeEditor.reset(codeAttribute.u4codeLength); + + logger.info("Amount of offsets where {} is referenced in {}: {}", this.currentLambdaClass, enclosingClass, this.offsetsWhereLambdaIsReferenced.size()); + for (int offset : this.offsetsWhereLambdaIsReferenced) + { + codeAttributeEditor.replaceInstruction(offset, instantiateLambdaGroupInstructions); + } + + codeAttributeEditor.visitCodeAttribute(enclosingClass, enclosingMethod, codeAttribute); + + logger.debug("Updated enclosing class after modifying enclosing method code:"); + //enclosingClass.accept(new ClassPrinter()); + + visitEnclosingCode = false; + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) + { + } + + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + try + { + visitConstantInstruction((ProgramClass) clazz, (ProgramMethod) method, codeAttribute, offset, constantInstruction); + } + catch (ClassCastException exception) + { + logger.error(exception); + } + } + + public void visitConstantInstruction(ProgramClass enclosingClass, ProgramMethod enclosingMethod, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) + { + // check if constant instruction refers to constant in constant pool that is related to the lambda class: the lambda itself, a field in the lambda class + /* we expect: + new LambdaClass + dup + invokespecial LambdaClass.(...)V + ... + or: + getfield LambdaClass.INSTANCE:LambdaClass + */ + + // note offset of this instruction, so it can be replaced later + logger.info("ConstantInstruction {}", constantInstruction); + logger.info("Lambda constant index: {}", this.lambdaConstantIndex); + if (constantReferencesClass(enclosingClass.getConstant(constantInstruction.constantIndex), this.currentLambdaClass)) + { + this.offsetsWhereLambdaIsReferenced.add(offset); + } + } + + private boolean constantReferencesClass(Constant constant, Clazz clazz) + { + try + { + return constantReferencesClass((RefConstant) constant, clazz); + } + catch (ClassCastException exception) + { + return false; + } + } + + private boolean constantReferencesClass(RefConstant refConstant, Clazz clazz) + { + return refConstant.referencedClass == clazz || Objects.equals(refConstant.referencedClass.getName(), clazz.getName()); + } +} + +class ClassConstantFinder implements ConstantVisitor +{ + private Clazz classToBeFound; + private boolean classFound = false; + private static final Logger logger = LogManager.getLogger(ClassConstantFinder.class); + + public ClassConstantFinder(Clazz classToBeFound) + { + this.classToBeFound = classToBeFound; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) + { + } + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + logger.info("Visiting class constant {}, referencing class {}", classConstant, classConstant.referencedClass); + if (classConstant.referencedClass == this.classToBeFound || Objects.equals(classConstant.referencedClass.getName(), this.classToBeFound.getName())) + { + this.classFound = true; + } + } + + public boolean isClassFound() + { + return this.classFound; + } +} + +class InnerClassRemover implements AttributeVisitor, InnerClassesInfoVisitor +{ + private final Clazz classToBeRemoved; + private Set innerClassesEntriesToBeRemoved = new HashSet<>(); + private static final Logger logger = LogManager.getLogger(InnerClassRemover.class); + + public InnerClassRemover(Clazz clazz) + { + this.classToBeRemoved = clazz; + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) { + } + + @Override + public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { + innerClassesAttribute.innerClassEntriesAccept(clazz, this); + InnerClassesAttributeEditor editor = new InnerClassesAttributeEditor(innerClassesAttribute); + logger.info("{} inner class entries are removed from class {}", innerClassesEntriesToBeRemoved.size(), clazz); + for (InnerClassesInfo entry : innerClassesEntriesToBeRemoved) + { + editor.removeInnerClassesInfo(entry); + } + } + + @Override + public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { + String innerClassName = clazz.getClassName(innerClassesInfo.u2innerClassIndex); + if (Objects.equals(innerClassName, this.classToBeRemoved.getName())) + { + logger.info("Removing inner classes entry of class {} enqueued to be removed from class {}", innerClassName, clazz); + innerClassesEntriesToBeRemoved.add(innerClassesInfo); + } + } +} \ No newline at end of file From 60d6326625fd4080dfc98e0c334ca85853ad8d2c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:03:37 +0100 Subject: [PATCH 009/195] KotlinLambdaClassMerger: create a lambda group for the given classpool --- .../kotlin/KotlinLambdaClassMerger.java | 245 ++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java new file mode 100644 index 000000000..d31c07e45 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -0,0 +1,245 @@ +package proguard.optimize.kotlin; + +import jdk.internal.reflect.ConstantPool; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.ExceptionsAttribute; +import proguard.classfile.attribute.LineNumberTableAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.ClassEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.instruction.*; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.ClassSuperHierarchyInitializer; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassPoolVisitor; +import proguard.classfile.visitor.ClassPrinter; +import proguard.classfile.visitor.ClassVisitor; +import proguard.classfile.visitor.MemberVisitor; +import proguard.io.ExtraDataEntryNameMap; + +import java.util.ArrayList; +import java.util.List; + +public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { + + private final ClassVisitor lambdaGroupVisitor; + private ProgramMethod invokeMethod = new ProgramMethod(); + private ProgramClass lambdaGroup = new ProgramClass(); + private ClassBuilder classBuilder; + private LambdaGroupInvokeCodeBuilder invokeCodeBuilder; + private ClassPool programClassPool; + private ClassPool libraryClassPool; + private final ExtraDataEntryNameMap extraDataEntryNameMap; + private static final Logger logger = LogManager.getLogger(KotlinLambdaClassMerger.class); + + public KotlinLambdaClassMerger(ClassPool programClassPool, + ClassPool libraryClassPool, + ClassVisitor lambdaGroupVisitor, + final ExtraDataEntryNameMap extraDataEntryNameMap) + { + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.lambdaGroupVisitor = lambdaGroupVisitor; + this.extraDataEntryNameMap = extraDataEntryNameMap; + } + + @Override + public void visitClassPool(ClassPool lambdaClassPool) + { + // choose a name for the lambda group + // ensure that the lambda group is in the same package as the classes of the class pool + String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + "LambdaGroup"; + logger.info("Creating lambda group with name {}", lambdaGroupName); + // create a lambda group + // let the lambda group extend Lambda + this.classBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, + AccessConstants.PUBLIC, + lambdaGroupName, + ClassConstants.NAME_KOTLIN_LAMBDA); + this.lambdaGroup = classBuilder.getProgramClass(); + this.classBuilder = new ClassBuilder(this.lambdaGroup, + this.programClassPool, + this.libraryClassPool); + + // initialise the super class of the lambda group + this.lambdaGroup.accept(new ClassSuperHierarchyInitializer( + this.programClassPool, + this.libraryClassPool)); + + // let the lambda group implement Function0 + classBuilder.addInterface(ClassConstants.NAME_KOTLIN_FUNCTION0); + + // add a classId field to the lambda group + ProgramField classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, "classId", "I"); + + this.invokeCodeBuilder = new LambdaGroupInvokeCodeBuilder(); + + // add a constructor which takes an id as argument and stores it in the classId field + ProgramMethod initMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + "(I)V", + 50, + code -> { + code + .aload_0() // load this class + .iload_1() // load the id argument + .putfield(lambdaGroup, classIdField) // store the id in a field + .aload_0() // load this class + .iconst_0() // push 0 on stack + .invokespecial(ClassConstants.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + "(I)V") + .return_(); + }); + + logger.info("Lambda group {} before adding lambda implementations:", lambdaGroupName); + //lambdaGroup.accept(new ClassPrinter()); + + // visit each lambda of this package to add their implementations to the lambda group + lambdaClassPool.classesAccept(this); + + // add an invoke method + this.invokeMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + "invoke", + "()Ljava/lang/Object;", + 50, + this.invokeCodeBuilder.build(this.lambdaGroup, classIdField)); + + // FOR DEBUGGING THE LAMBDA GROUP + addMainMethod(initMethod); + + logger.info("Lambda group {} after adding lambda implementations:", lambdaGroupName); + //lambdaGroup.accept(new ClassPrinter()); + + // let the lambda group visitor visit the newly created lambda group + this.lambdaGroupVisitor.visitProgramClass(lambdaGroup); + + // ensure that this newly created class is part of the resulting output + extraDataEntryNameMap.addExtraClass(lambdaGroup.getName()); + } + + private void addMainMethod(ProgramMethod initMethod) + { + + classBuilder.addMethod(AccessConstants.PUBLIC | AccessConstants.STATIC, + "main", + "([Ljava/lang/String;)V", + 50, + code -> code + .new_(this.lambdaGroup) + .dup() + .iconst_0() + .invokespecial(this.lambdaGroup, initMethod) + .invokeinterface(ClassConstants.NAME_KOTLIN_FUNCTION0, "invoke", "()Ljava/lang/Object;") + .return_()); + } + + private String getPackagePrefixOfClasses(ClassPool classPool) + { + // Assume that all classes in the given class pool are in the same package. + String someClassName = classPool.classNames().next(); + return ClassUtil.internalPackagePrefix(someClassName); + } + + @Override + public void visitAnyClass(Clazz clazz) { + + } + + @Override + public void visitProgramClass(ProgramClass lambdaClass) { + // the bridge method has descriptor "()Ljava/lang/Object;" + // but for now: only consider lambda's with "void invoke()" + logger.info("Visiting lambda {}", lambdaClass.getName()); + Method returnVoidInvoke = lambdaClass.findMethod("invoke", "()V"); + /*if (returnVoidInvoke == null) + { + logger.info("{} does not contain an invoke()V method.", lambdaClass.getName()); + return; + }*/ + + logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); + + ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass, returnVoidInvoke); + boolean copiedMethodHasReturnValue = !copiedMethod.getDescriptor(this.lambdaGroup).endsWith(")V"); + + // create a new case in the switch that calls the copied invoke method + // add a case to the table switch instruction + //lambdaClass.attributesAccept(invokeComposer); + // TODO: create the instructions that call the correct method + InstructionSequenceBuilder builder = new InstructionSequenceBuilder(lambdaGroup, + this.programClassPool, + this.libraryClassPool); + builder.aload_0() // load this + .invokevirtual(this.lambdaGroup, copiedMethod); // invoke the lambda implementation + if (!copiedMethodHasReturnValue) { + // ensure there is a return value + builder.getstatic("kotlin/Unit", "INSTANCE", "Lkotlin/Unit;"); + } + builder.areturn(); // return + Instruction[] callToMethod = builder.instructions(); + + this.invokeCodeBuilder.addNewCase(callToMethod); + int lambdaClassId = this.invokeCodeBuilder.getCaseIndexCounter() - 1; + + // replace instantiation of lambda class with instantiation of lambda group + // with correct id + updateLambdaInstantiationSite(lambdaClass, lambdaClassId); + } + + private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass, Method returnVoidInvoke) + { + logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.lambdaGroup.getName()); + + // Note: the lambda class is expected to containt two invoke methods: + // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface + // - a specific method that contains the implementation of the lambda, of which the + // Assumption: the specific invoke method is found before the + ProgramMethod invokeMethod = getInvokeMethod(lambdaClass); + String newMethodName = lambdaClass.getName().substring(lambdaClass.getName().lastIndexOf("/") + 1) + "$invoke"; + invokeMethod.accept(lambdaClass, new MethodCopier(this.lambdaGroup, newMethodName)); + return (ProgramMethod)this.lambdaGroup.findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); + } + + /** + * Returns the specific invoke method of the given lambdaClass, if there is a specific invoke method, + * else returns the bridge invoke method if there is one, else returns null. + * @param lambdaClass + * @return + */ + private ProgramMethod getInvokeMethod(ProgramClass lambdaClass) + { + ProgramMethod bridgeInvokeMethod = null; + for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { + ProgramMethod method = lambdaClass.methods[methodIndex]; + if (method.getName(lambdaClass).equals("invoke")) + { + // we have found an invoke method + if (!method.getDescriptor(lambdaClass).equals("()Ljava/lang/Object;")) + { + // we have found the specific invoke method + return method; + } else { + bridgeInvokeMethod = method; + } + } + } + return bridgeInvokeMethod; + } + + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) + { + logger.info("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); + lambdaClass.attributesAccept(new KotlinLambdaEnclosingMethodUpdater( + this.programClassPool, + this.libraryClassPool, + this.lambdaGroup, + lambdaClassId)); + } +} \ No newline at end of file From 3596717c5e6309928b93cd4b57c5684c95a17b14 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:08:29 +0100 Subject: [PATCH 010/195] KotlinLambdaMerger: constants for each FunctionX interface --- .../optimize/kotlin/KotlinLambdaMerger.java | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 63ea4d527..c87c25d7e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -14,8 +14,31 @@ public class KotlinLambdaMerger { - private final String KOTLIN_LAMBDA_CLASS = "kotlin/jvm/internal/Lambda"; - private final String KOTLIN_FUNCTION0_INTERFACE = "kotlin/jvm/functions/Function0"; + public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; + public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; + public static final String NAME_KOTLIN_FUNCTION1= "kotlin/jvm/functions/Function1"; + public static final String NAME_KOTLIN_FUNCTION2= "kotlin/jvm/functions/Function2"; + public static final String NAME_KOTLIN_FUNCTION3= "kotlin/jvm/functions/Function3"; + public static final String NAME_KOTLIN_FUNCTION4= "kotlin/jvm/functions/Function4"; + public static final String NAME_KOTLIN_FUNCTION5= "kotlin/jvm/functions/Function5"; + public static final String NAME_KOTLIN_FUNCTION6= "kotlin/jvm/functions/Function6"; + public static final String NAME_KOTLIN_FUNCTION7= "kotlin/jvm/functions/Function7"; + public static final String NAME_KOTLIN_FUNCTION8= "kotlin/jvm/functions/Function8"; + public static final String NAME_KOTLIN_FUNCTION9= "kotlin/jvm/functions/Function9"; + public static final String NAME_KOTLIN_FUNCTION10= "kotlin/jvm/functions/Function10"; + public static final String NAME_KOTLIN_FUNCTION11= "kotlin/jvm/functions/Function11"; + public static final String NAME_KOTLIN_FUNCTION12= "kotlin/jvm/functions/Function12"; + public static final String NAME_KOTLIN_FUNCTION13= "kotlin/jvm/functions/Function13"; + public static final String NAME_KOTLIN_FUNCTION14= "kotlin/jvm/functions/Function14"; + public static final String NAME_KOTLIN_FUNCTION15= "kotlin/jvm/functions/Function15"; + public static final String NAME_KOTLIN_FUNCTION16= "kotlin/jvm/functions/Function16"; + public static final String NAME_KOTLIN_FUNCTION17= "kotlin/jvm/functions/Function17"; + public static final String NAME_KOTLIN_FUNCTION18= "kotlin/jvm/functions/Function18"; + public static final String NAME_KOTLIN_FUNCTION19= "kotlin/jvm/functions/Function19"; + public static final String NAME_KOTLIN_FUNCTION20= "kotlin/jvm/functions/Function20"; + public static final String NAME_KOTLIN_FUNCTION21= "kotlin/jvm/functions/Function21"; + public static final String NAME_KOTLIN_FUNCTION22= "kotlin/jvm/functions/Function22"; + public static final String NAME_KOTLIN_FUNCTIONN= "kotlin/jvm/functions/FunctionN"; private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; From 53bd9178e8c2a2828f6b8d1e2c012e7de6fa33fa Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:08:45 +0100 Subject: [PATCH 011/195] KotlinLambdaMerger: imports --- .../optimize/kotlin/KotlinLambdaMerger.java | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index c87c25d7e..d4fe1e9ba 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -3,11 +3,24 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.Configuration; +import proguard.classfile.ClassConstants; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; -import proguard.classfile.visitor.ClassMethodFilter; -import proguard.classfile.visitor.ClassPoolFiller; -import proguard.classfile.visitor.ImplementedClassFilter; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.DebugAttributeVisitor; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.visitor.ConstantTagFilter; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionConstantVisitor; +import proguard.classfile.util.ClassSubHierarchyInitializer; +import proguard.classfile.util.ClassSuperHierarchyInitializer; +import proguard.classfile.visitor.*; +import proguard.io.ExtraDataEntryNameMap; +import proguard.optimize.OptimizationInfoMemberFilter; +import proguard.optimize.TimedClassPoolVisitor; +import proguard.optimize.info.OptimizationCodeAttributeFilter; +import proguard.optimize.info.ParameterUsageMarker; +import proguard.optimize.peephole.MethodInliner; import proguard.resources.file.ResourceFilePool; import java.io.IOException; From 190d8f2342e1b8cf6001706b25cb0f4651240a80 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:09:35 +0100 Subject: [PATCH 012/195] KotlinLambdaMerger: extraDataEntryNameMap as constructor argument --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d4fe1e9ba..34754e947 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -61,9 +61,10 @@ public KotlinLambdaMerger(Configuration configuration) this.configuration = configuration; } - public ClassPool execute(ClassPool programClassPool, - ClassPool libraryClassPool, - ResourceFilePool resourceFilePool) throws IOException + public ClassPool execute(final ClassPool programClassPool, + final ClassPool libraryClassPool, + final ResourceFilePool resourceFilePool, + final ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { // A class pool where the applicable lambda's will be stored ClassPool lambdaClassPool = new ClassPool(); From e9f27752e08d2fbd3db697f0a2e3c3204ba20b13 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:10:23 +0100 Subject: [PATCH 013/195] KotlinLambdaMerger: use new constants --- .../proguard/optimize/kotlin/KotlinLambdaMerger.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 34754e947..0909d1b0a 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -73,10 +73,10 @@ public ClassPool execute(final ClassPool programClassPool, Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); Clazz kotlinFunction0Interface = getKotlinFunction0Interface(programClassPool, libraryClassPool); if (kotlinLambdaClass == null) { - logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", KOTLIN_LAMBDA_CLASS); + logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_LAMBDA); } if (kotlinFunction0Interface == null) { - logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", KOTLIN_FUNCTION0_INTERFACE); + logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_FUNCTION0); } if (kotlinLambdaClass != null && kotlinFunction0Interface != null) { @@ -97,18 +97,18 @@ public ClassPool execute(final ClassPool programClassPool, private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) { - Clazz kotlinLambdaClass = programClassPool.getClass(KOTLIN_LAMBDA_CLASS); + Clazz kotlinLambdaClass = programClassPool.getClass(NAME_KOTLIN_LAMBDA); if (kotlinLambdaClass == null) { - kotlinLambdaClass = libraryClassPool.getClass(KOTLIN_LAMBDA_CLASS); + kotlinLambdaClass = libraryClassPool.getClass(NAME_KOTLIN_LAMBDA); } return kotlinLambdaClass; } private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool libraryClassPool) { - Clazz kotlinFunction0Interface = programClassPool.getClass(KOTLIN_FUNCTION0_INTERFACE); + Clazz kotlinFunction0Interface = programClassPool.getClass(NAME_KOTLIN_FUNCTION0); if (kotlinFunction0Interface == null) { - kotlinFunction0Interface = libraryClassPool.getClass(KOTLIN_FUNCTION0_INTERFACE); + kotlinFunction0Interface = libraryClassPool.getClass(NAME_KOTLIN_FUNCTION0); } return kotlinFunction0Interface; } From b53739bb831edd79fe80debf529d960ef18b31be Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:10:41 +0100 Subject: [PATCH 014/195] KotlinLambdaClassMerger: use new constants --- .../proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index d31c07e45..c01801ec7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -61,7 +61,7 @@ public void visitClassPool(ClassPool lambdaClassPool) this.classBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, AccessConstants.PUBLIC, lambdaGroupName, - ClassConstants.NAME_KOTLIN_LAMBDA); + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA); this.lambdaGroup = classBuilder.getProgramClass(); this.classBuilder = new ClassBuilder(this.lambdaGroup, this.programClassPool, @@ -73,7 +73,7 @@ public void visitClassPool(ClassPool lambdaClassPool) this.libraryClassPool)); // let the lambda group implement Function0 - classBuilder.addInterface(ClassConstants.NAME_KOTLIN_FUNCTION0); + classBuilder.addInterface(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0); // add a classId field to the lambda group ProgramField classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, "classId", "I"); @@ -92,7 +92,7 @@ public void visitClassPool(ClassPool lambdaClassPool) .putfield(lambdaGroup, classIdField) // store the id in a field .aload_0() // load this class .iconst_0() // push 0 on stack - .invokespecial(ClassConstants.NAME_KOTLIN_LAMBDA, + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") .return_(); @@ -136,7 +136,7 @@ private void addMainMethod(ProgramMethod initMethod) .dup() .iconst_0() .invokespecial(this.lambdaGroup, initMethod) - .invokeinterface(ClassConstants.NAME_KOTLIN_FUNCTION0, "invoke", "()Ljava/lang/Object;") + .invokeinterface(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0, "invoke", "()Ljava/lang/Object;") .return_()); } From a43d0deffecc86248b9af3ed77d449fe5bcb2233 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:12:16 +0100 Subject: [PATCH 015/195] ProGuard: pass extraDataEntryNameMap to lambda merger --- base/src/main/java/proguard/ProGuard.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index e2184166f..397ab7c7a 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -428,7 +428,8 @@ private void shrink(boolean afterOptimizer) throws Exception private void mergeKotlinLambdaClasses() throws IOException { programClassPool = new KotlinLambdaMerger(configuration).execute(programClassPool, libraryClassPool, - resourceFilePool); + resourceFilePool, + extraDataEntryNameMap); } From 438709e6898be07f575b751427e2f420b2818a7d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:14:29 +0100 Subject: [PATCH 016/195] KotlinLambdaMerger implemented --- .../optimize/kotlin/KotlinLambdaMerger.java | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 0909d1b0a..f7cd60296 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -66,8 +66,6 @@ public ClassPool execute(final ClassPool programClassPool, final ResourceFilePool resourceFilePool, final ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { - // A class pool where the applicable lambda's will be stored - ClassPool lambdaClassPool = new ClassPool(); // get the Lambda class and the Function0 interface Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); @@ -80,17 +78,47 @@ public ClassPool execute(final ClassPool programClassPool, } if (kotlinLambdaClass != null && kotlinFunction0Interface != null) { + // A class pool where the applicable lambda's will be stored + ClassPool lambdaClassPool = new ClassPool(); + ClassPool newProgramClassPool = new ClassPool(); + ClassPoolFiller newProgramClassPoolFiller = new ClassPoolFiller(newProgramClassPool); // find all lambda classes of arity 0 and with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty programClassPool.classesAccept(new ImplementedClassFilter(kotlinFunction0Interface, false, new ImplementedClassFilter(kotlinLambdaClass, false, - new ClassMethodFilter("", "()V", - new ClassPoolFiller(lambdaClassPool), null), null), null) + new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, + new ClassPoolFiller(lambdaClassPool), + newProgramClassPoolFiller), + newProgramClassPoolFiller), + newProgramClassPoolFiller) ); + // group the lambda's per package + PackageGrouper packageGrouper = new PackageGrouper(); + lambdaClassPool.classesAccept(packageGrouper); + ClassPool lambdaGroupClassPool = new ClassPool(); + // merge the lambda's per package + packageGrouper.packagesAccept(new KotlinLambdaClassMerger( + programClassPool, + libraryClassPool, + new MultiClassVisitor( + new ClassPoolFiller(lambdaGroupClassPool), + newProgramClassPoolFiller), + extraDataEntryNameMap)); + // initialise the super classes of the newly created lambda groups + ClassSubHierarchyInitializer hierarchyInitializer = new ClassSubHierarchyInitializer(); + newProgramClassPool.accept(hierarchyInitializer); + + logger.info("{} lambda class(es) found.", lambdaClassPool.size()); + logger.info("{} lambda group(s) created.", lambdaGroupClassPool.size()); + logger.info("#lambda groups/#lambda classes ratio = {}/{} = {}%", lambdaGroupClassPool.size(), lambdaClassPool.size(), 100 * lambdaGroupClassPool.size() / lambdaClassPool.size()); + logger.info("Size of original program class pool: {}", programClassPool.size()); + logger.info("Size of new program class pool: {}", newProgramClassPool.size()); + + return newProgramClassPool; } return programClassPool; } From 42cdc99f2fea41f49e2e654350e7fc8b79eff7d8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:14:47 +0100 Subject: [PATCH 017/195] ProGuard: call shrinker after lambda merging --- base/src/main/java/proguard/ProGuard.java | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 397ab7c7a..ef9f4d201 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -430,6 +430,7 @@ private void mergeKotlinLambdaClasses() throws IOException { libraryClassPool, resourceFilePool, extraDataEntryNameMap); + shrink(); } From 5fe411280d609edcca3b6e3bdae5e6e6f0174434 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:16:39 +0100 Subject: [PATCH 018/195] Test for PackageGrouper & checking whether lambda merger reduces number of classes --- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt new file mode 100644 index 000000000..48f657b23 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -0,0 +1,118 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2021 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package proguard.optimize.kotlin + +import io.kotest.core.spec.IsolationMode +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldBeIn +import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual +import io.kotest.matchers.shouldBe +import proguard.classfile.ClassConstants +import proguard.classfile.ClassPool +import proguard.classfile.visitor.ClassMethodFilter +import proguard.classfile.visitor.ClassPoolFiller +import proguard.classfile.visitor.ImplementedClassFilter +import proguard.io.ExtraDataEntryNameMap +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class KotlinLambdaMergerTest : FreeSpec({ + + // InstancePerTest so the names are reset before every test + isolationMode = IsolationMode.InstancePerTest + + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + package app.package1 + fun main(index: Int) { + val lambda1 = { println("Lambda1") } + val lambda2 = { println("Lambda2") } + val lambda3 = { println("Lambda3") } + when (index) { + 0 -> lambda1() + 1 -> lambda2() + else -> lambda3() + } + } + """.trimIndent() + ), + KotlinSource( + "Test2.kt", + """ + package app.package2 + fun main() { + val lambda1 = { println("Lambda1") } + val lambda2 = { println("Lambda2") } + val lambda3 = { println("Lambda3") } + lambda1() + lambda2() + lambda3() + } + """.trimIndent() + ) + ) + + + // A class pool where the applicable lambda's will be stored + val lambdaClassPool = ClassPool() + val kotlinLambdaClass = libraryClassPool.getClass(ClassConstants.NAME_KOTLIN_LAMBDA) + val kotlinFunction0Interface = libraryClassPool.getClass(ClassConstants.NAME_KOTLIN_FUNCTION0) + programClassPool.classesAccept(ImplementedClassFilter( + kotlinFunction0Interface, false, + ImplementedClassFilter( + kotlinLambdaClass, false, + ClassMethodFilter( + ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, + ClassPoolFiller(lambdaClassPool), null), null), null)) + + "Given a Kotlin Lambda Package Grouper" - { + val grouper = PackageGrouper() + "When the grouper is applied to lambda's of different packages" - { + programClassPool.classesAccept(grouper) + "Then the grouper has found 2 packages" { + grouper.size() shouldBe 2 + } + "Then the grouper has found the packages app/package1 and app/package2" - { + grouper.containsPackage("app/package1") shouldBe true + grouper.containsPackage("app/package2") shouldBe true + } + "Then the grouper does not contain other packages" - { + grouper.packageNames().forEach { + it shouldBeIn arrayListOf("app/package2", "app/package1") + } + } + } + } + + "Given a Kotlin Lambda Merger and entry name mapper" - { + val merger = KotlinLambdaMerger(null) + val nameMapper = ExtraDataEntryNameMap() + "When the merger is applied to the class pools" - { + val newProgramClassPool = merger.execute(programClassPool, libraryClassPool, null, nameMapper) + "Then the resulting program class pool contains less classes" { + programClassPool.size() shouldBeGreaterThanOrEqual newProgramClassPool.size() + } + } + } +}) From 16a95e760893412d2f6538a5416fd64ae70f6273 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:38:01 +0100 Subject: [PATCH 019/195] KotlinLambdaClassMerger: remove unused invoke method variable --- .../kotlin/KotlinLambdaClassMerger.java | 14 +----- .../optimize/kotlin/KotlinLambdaMerger.java | 50 +++++++++---------- 2 files changed, 27 insertions(+), 37 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index c01801ec7..a682dbc4f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -154,19 +154,9 @@ public void visitAnyClass(Clazz clazz) { @Override public void visitProgramClass(ProgramClass lambdaClass) { - // the bridge method has descriptor "()Ljava/lang/Object;" - // but for now: only consider lambda's with "void invoke()" - logger.info("Visiting lambda {}", lambdaClass.getName()); - Method returnVoidInvoke = lambdaClass.findMethod("invoke", "()V"); - /*if (returnVoidInvoke == null) - { - logger.info("{} does not contain an invoke()V method.", lambdaClass.getName()); - return; - }*/ - logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); - ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass, returnVoidInvoke); + ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); boolean copiedMethodHasReturnValue = !copiedMethod.getDescriptor(this.lambdaGroup).endsWith(")V"); // create a new case in the switch that calls the copied invoke method @@ -193,7 +183,7 @@ public void visitProgramClass(ProgramClass lambdaClass) { updateLambdaInstantiationSite(lambdaClass, lambdaClassId); } - private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass, Method returnVoidInvoke) + private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) { logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.lambdaGroup.getName()); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index f7cd60296..4410cabdc 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -27,31 +27,31 @@ public class KotlinLambdaMerger { - public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; - public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; - public static final String NAME_KOTLIN_FUNCTION1= "kotlin/jvm/functions/Function1"; - public static final String NAME_KOTLIN_FUNCTION2= "kotlin/jvm/functions/Function2"; - public static final String NAME_KOTLIN_FUNCTION3= "kotlin/jvm/functions/Function3"; - public static final String NAME_KOTLIN_FUNCTION4= "kotlin/jvm/functions/Function4"; - public static final String NAME_KOTLIN_FUNCTION5= "kotlin/jvm/functions/Function5"; - public static final String NAME_KOTLIN_FUNCTION6= "kotlin/jvm/functions/Function6"; - public static final String NAME_KOTLIN_FUNCTION7= "kotlin/jvm/functions/Function7"; - public static final String NAME_KOTLIN_FUNCTION8= "kotlin/jvm/functions/Function8"; - public static final String NAME_KOTLIN_FUNCTION9= "kotlin/jvm/functions/Function9"; - public static final String NAME_KOTLIN_FUNCTION10= "kotlin/jvm/functions/Function10"; - public static final String NAME_KOTLIN_FUNCTION11= "kotlin/jvm/functions/Function11"; - public static final String NAME_KOTLIN_FUNCTION12= "kotlin/jvm/functions/Function12"; - public static final String NAME_KOTLIN_FUNCTION13= "kotlin/jvm/functions/Function13"; - public static final String NAME_KOTLIN_FUNCTION14= "kotlin/jvm/functions/Function14"; - public static final String NAME_KOTLIN_FUNCTION15= "kotlin/jvm/functions/Function15"; - public static final String NAME_KOTLIN_FUNCTION16= "kotlin/jvm/functions/Function16"; - public static final String NAME_KOTLIN_FUNCTION17= "kotlin/jvm/functions/Function17"; - public static final String NAME_KOTLIN_FUNCTION18= "kotlin/jvm/functions/Function18"; - public static final String NAME_KOTLIN_FUNCTION19= "kotlin/jvm/functions/Function19"; - public static final String NAME_KOTLIN_FUNCTION20= "kotlin/jvm/functions/Function20"; - public static final String NAME_KOTLIN_FUNCTION21= "kotlin/jvm/functions/Function21"; - public static final String NAME_KOTLIN_FUNCTION22= "kotlin/jvm/functions/Function22"; - public static final String NAME_KOTLIN_FUNCTIONN= "kotlin/jvm/functions/FunctionN"; + public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; + public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; + public static final String NAME_KOTLIN_FUNCTION1 = "kotlin/jvm/functions/Function1"; + public static final String NAME_KOTLIN_FUNCTION2 = "kotlin/jvm/functions/Function2"; + public static final String NAME_KOTLIN_FUNCTION3 = "kotlin/jvm/functions/Function3"; + public static final String NAME_KOTLIN_FUNCTION4 = "kotlin/jvm/functions/Function4"; + public static final String NAME_KOTLIN_FUNCTION5 = "kotlin/jvm/functions/Function5"; + public static final String NAME_KOTLIN_FUNCTION6 = "kotlin/jvm/functions/Function6"; + public static final String NAME_KOTLIN_FUNCTION7 = "kotlin/jvm/functions/Function7"; + public static final String NAME_KOTLIN_FUNCTION8 = "kotlin/jvm/functions/Function8"; + public static final String NAME_KOTLIN_FUNCTION9 = "kotlin/jvm/functions/Function9"; + public static final String NAME_KOTLIN_FUNCTION10 = "kotlin/jvm/functions/Function10"; + public static final String NAME_KOTLIN_FUNCTION11 = "kotlin/jvm/functions/Function11"; + public static final String NAME_KOTLIN_FUNCTION12 = "kotlin/jvm/functions/Function12"; + public static final String NAME_KOTLIN_FUNCTION13 = "kotlin/jvm/functions/Function13"; + public static final String NAME_KOTLIN_FUNCTION14 = "kotlin/jvm/functions/Function14"; + public static final String NAME_KOTLIN_FUNCTION15 = "kotlin/jvm/functions/Function15"; + public static final String NAME_KOTLIN_FUNCTION16 = "kotlin/jvm/functions/Function16"; + public static final String NAME_KOTLIN_FUNCTION17 = "kotlin/jvm/functions/Function17"; + public static final String NAME_KOTLIN_FUNCTION18 = "kotlin/jvm/functions/Function18"; + public static final String NAME_KOTLIN_FUNCTION19 = "kotlin/jvm/functions/Function19"; + public static final String NAME_KOTLIN_FUNCTION20 = "kotlin/jvm/functions/Function20"; + public static final String NAME_KOTLIN_FUNCTION21 = "kotlin/jvm/functions/Function21"; + public static final String NAME_KOTLIN_FUNCTION22 = "kotlin/jvm/functions/Function22"; + public static final String NAME_KOTLIN_FUNCTIONN = "kotlin/jvm/functions/FunctionN"; private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; From 3f54119308342f42d4ac026d28ba90c4329c8abe Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:47:48 +0100 Subject: [PATCH 020/195] KLCM: make invokeMethod local variable --- .../java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index a682dbc4f..d5df43ddf 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -29,7 +29,6 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { private final ClassVisitor lambdaGroupVisitor; - private ProgramMethod invokeMethod = new ProgramMethod(); private ProgramClass lambdaGroup = new ProgramClass(); private ClassBuilder classBuilder; private LambdaGroupInvokeCodeBuilder invokeCodeBuilder; @@ -105,7 +104,7 @@ public void visitClassPool(ClassPool lambdaClassPool) lambdaClassPool.classesAccept(this); // add an invoke method - this.invokeMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + ProgramMethod invokeMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, "invoke", "()Ljava/lang/Object;", 50, From c0e70baf3f188de5e63b40c4133a16d5f7a919f5 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 1 Jan 2022 19:48:15 +0100 Subject: [PATCH 021/195] KLCM: make constructor arguments final --- .../optimize/kotlin/KotlinLambdaClassMerger.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index d5df43ddf..e44e5d845 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -32,14 +32,14 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { private ProgramClass lambdaGroup = new ProgramClass(); private ClassBuilder classBuilder; private LambdaGroupInvokeCodeBuilder invokeCodeBuilder; - private ClassPool programClassPool; - private ClassPool libraryClassPool; + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; private final ExtraDataEntryNameMap extraDataEntryNameMap; private static final Logger logger = LogManager.getLogger(KotlinLambdaClassMerger.class); - public KotlinLambdaClassMerger(ClassPool programClassPool, - ClassPool libraryClassPool, - ClassVisitor lambdaGroupVisitor, + public KotlinLambdaClassMerger(final ClassPool programClassPool, + final ClassPool libraryClassPool, + final ClassVisitor lambdaGroupVisitor, final ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; From 3e7cf66fcf0ad7e0d87de9ba22dfb4f3493e0e27 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 2 Jan 2022 14:06:13 +0100 Subject: [PATCH 022/195] Comment log statements --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index a9ba72e56..8e6cbf907 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -210,8 +210,8 @@ public void visitConstantInstruction(ProgramClass enclosingClass, ProgramMethod */ // note offset of this instruction, so it can be replaced later - logger.info("ConstantInstruction {}", constantInstruction); - logger.info("Lambda constant index: {}", this.lambdaConstantIndex); + //logger.info("ConstantInstruction {}", constantInstruction); + //logger.info("Lambda constant index: {}", this.lambdaConstantIndex); if (constantReferencesClass(enclosingClass.getConstant(constantInstruction.constantIndex), this.currentLambdaClass)) { this.offsetsWhereLambdaIsReferenced.add(offset); @@ -255,7 +255,7 @@ public void visitAnyConstant(Clazz clazz, Constant constant) @Override public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { - logger.info("Visiting class constant {}, referencing class {}", classConstant, classConstant.referencedClass); + //logger.info("Visiting class constant {}, referencing class {}", classConstant, classConstant.referencedClass); if (classConstant.referencedClass == this.classToBeFound || Objects.equals(classConstant.referencedClass.getName(), this.classToBeFound.getName())) { this.classFound = true; From 0d24de5f6acc38cf5eb5e2362b48d703d9c089d4 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 2 Jan 2022 15:28:30 +0100 Subject: [PATCH 023/195] Messy MethodInlinerWrapper class --- .../optimize/MethodInlinerWrapper.java | 319 ++++++++++++++++++ 1 file changed, 319 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/MethodInlinerWrapper.java diff --git a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java new file mode 100644 index 000000000..7ae7a1a50 --- /dev/null +++ b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java @@ -0,0 +1,319 @@ +package proguard.optimize; + +import proguard.Configuration; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.visitor.*; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionConstantVisitor; +import proguard.classfile.instruction.visitor.InstructionCounter; +import proguard.classfile.instruction.visitor.MultiInstructionVisitor; +import proguard.classfile.util.MethodLinker; +import proguard.classfile.visitor.*; +import proguard.optimize.info.*; +import proguard.optimize.peephole.MethodInliner; +import proguard.util.*; + +public class MethodInlinerWrapper implements ClassPoolVisitor { + + private final Configuration configuration; + private static final String METHOD_MARKING_STATIC = "method/marking/static"; + private static final String METHOD_REMOVAL_PARAMETER = "method/removal/parameter"; + + public MethodInlinerWrapper(final Configuration configuration, + final ClassPool programClassPool, + final ClassPool libraryClassPool) + { + this.configuration = configuration; + markClasses(programClassPool, libraryClassPool); + } + + private void markClasses(final ClassPool programClassPool, + final ClassPool libraryClassPool) + { + + // Create a matcher for filtering optimizations. + StringMatcher filter = configuration.optimizations != null ? + new ListParser(new NameParser()).parse(configuration.optimizations) : + new ConstantMatcher(true); + + boolean methodMarkingStatic = filter.matches(METHOD_MARKING_STATIC); + boolean methodRemovalParameter = filter.matches(METHOD_REMOVAL_PARAMETER); + + // Clean up any old processing info. + programClassPool.classesAccept(new ClassCleaner()); + libraryClassPool.classesAccept(new ClassCleaner()); + + // Link all methods that should get the same optimization info. + programClassPool.classesAccept(new BottomClassFilter( + new MethodLinker())); + libraryClassPool.classesAccept(new BottomClassFilter( + new MethodLinker())); + + // Create a visitor for marking the seeds. + final KeepMarker keepMarker = new KeepMarker(); + + // All library classes and library class members remain unchanged. + libraryClassPool.classesAccept(keepMarker); + libraryClassPool.classesAccept(new AllMemberVisitor(keepMarker)); + + // Mark classes that have the DONT_OPTIMIZE flag set. + programClassPool.classesAccept( + new MultiClassVisitor( + new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, + keepMarker), + + new AllMemberVisitor( + new MultiMemberVisitor( + new MemberProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, + keepMarker), + + new AllAttributeVisitor( + new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, + keepMarker)) + )) + )); + + // We also keep all members that have the DONT_OPTIMIZE flag set on their code attribute. + programClassPool.classesAccept( + new AllMemberVisitor( + new AllAttributeVisitor( + new AttributeNameFilter(Attribute.CODE, + new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, + new CodeAttributeToMethodVisitor(keepMarker)))))); + + // We also keep all classes that are involved in .class constructs. + // We're not looking at enum classes though, so they can be simplified. + programClassPool.classesAccept( + new ClassAccessFilter(0, AccessConstants.ENUM, + new AllMethodVisitor( + new AllAttributeVisitor( + new AllInstructionVisitor( + new DotClassClassVisitor(keepMarker)))))); + + // We also keep all classes that are accessed dynamically. + programClassPool.classesAccept( + new AllConstantVisitor( + new ConstantTagFilter(Constant.STRING, + new ReferencedClassVisitor(keepMarker)))); + + // We also keep all class members that are accessed dynamically. + programClassPool.classesAccept( + new AllConstantVisitor( + new ConstantTagFilter(Constant.STRING, + new ReferencedMemberVisitor(keepMarker)))); + + // We also keep all bootstrap method signatures. + programClassPool.classesAccept( + new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, + new AllAttributeVisitor( + new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, + new AllBootstrapMethodInfoVisitor( + new BootstrapMethodHandleTraveler( + new MethodrefTraveler( + new ReferencedMemberVisitor(keepMarker)))))))); + + // We also keep classes and methods referenced from bootstrap + // method arguments. + programClassPool.classesAccept( + new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, + new AllAttributeVisitor( + new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, + new AllBootstrapMethodInfoVisitor( + new AllBootstrapMethodArgumentVisitor( + new MultiConstantVisitor( + // Class constants refer to additional functional + // interfaces (with LambdaMetafactory.altMetafactory). + new ConstantTagFilter(Constant.CLASS, + new ReferencedClassVisitor( + new FunctionalInterfaceFilter( + new ClassHierarchyTraveler(true, false, true, false, + new MultiClassVisitor( + keepMarker, + new AllMethodVisitor( + new MemberAccessFilter(AccessConstants.ABSTRACT, 0, + keepMarker)) + ))))), + + // Method handle constants refer to synthetic lambda + // methods (with LambdaMetafactory.metafactory and + // altMetafactory). + new MethodrefTraveler( + new ReferencedMemberVisitor(keepMarker))))))))); + + // We also keep the classes and abstract methods of functional + // interfaces that are returned by dynamic method invocations. + // These functional interfaces have to remain suitable for the + // dynamic method invocations with LambdaMetafactory. + programClassPool.classesAccept( + new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, + new AllConstantVisitor( + new DynamicReturnedClassVisitor( + new FunctionalInterfaceFilter( + new ClassHierarchyTraveler(true, false, true, false, + new MultiClassVisitor( + keepMarker, + new AllMethodVisitor( + new MemberAccessFilter(AccessConstants.ABSTRACT, 0, + keepMarker)) + ))))))); + + // Attach some optimization info to all classes and class members, so + // it can be filled out later. + programClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); + + programClassPool.classesAccept(new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter())); + + // Give initial marks to read/written fields. side-effect methods, and + // escaping parameters. + final MutableBoolean mutableBoolean = new MutableBoolean(); + + + // Mark all fields as read and written. + programClassPool.classesAccept( + new AllFieldVisitor( + new ReadWriteFieldMarker(mutableBoolean))); + + + // Mark methods based on their headers. + programClassPool.classesAccept( + new AllMethodVisitor( + new OptimizationInfoMemberFilter( + new MultiMemberVisitor( + new SideEffectMethodMarker(), + new ParameterEscapeMarker() + )))); + + programClassPool.accept(new InfluenceFixpointVisitor( + new SideEffectVisitorMarkerFactory())); + + // Mark all used parameters, including the 'this' parameters. + ParallelAllClassVisitor.ClassVisitorFactory markingUsedParametersClassVisitor = + new ParallelAllClassVisitor.ClassVisitorFactory() + { + public ClassVisitor createClassVisitor() + { + return + new AllMethodVisitor( + new OptimizationInfoMemberFilter( + new ParameterUsageMarker(!methodMarkingStatic, + !methodRemovalParameter))); + } + }; + + programClassPool.accept( + new TimedClassPoolVisitor("Marking used parameters", + new ParallelAllClassVisitor( + markingUsedParametersClassVisitor))); + + // Mark all parameters of referenced methods in methods whose code must + // be kept. This prevents shrinking of method descriptors which may not + // be propagated correctly otherwise. + programClassPool.accept( + new TimedClassPoolVisitor("Marking used parameters in kept code attributes", + new AllClassVisitor( + new AllMethodVisitor( + new OptimizationInfoMemberFilter( + null, + + // visit all methods that are kept + new AllAttributeVisitor( + new OptimizationCodeAttributeFilter( + null, + + // visit all code attributes that are kept + new AllInstructionVisitor( + new InstructionConstantVisitor( + new ConstantTagFilter(new int[] { Constant.METHODREF, + Constant.INTERFACE_METHODREF }, + new ReferencedMemberVisitor( + new OptimizationInfoMemberFilter( + // Mark all parameters including "this" of referenced methods + new ParameterUsageMarker(true, true, false)))))))) + ))))); + + + // Mark all classes with package visible members. + // Mark all exception catches of methods. + // Count all method invocations. + // Mark super invocations and other access of methods. + StackSizeComputer stackSizeComputer = new StackSizeComputer(); + + programClassPool.accept( + new TimedClassPoolVisitor("Marking method and referenced class properties", + new MultiClassVisitor( + // Mark classes. + new OptimizationInfoClassFilter( + new MultiClassVisitor( + new PackageVisibleMemberContainingClassMarker(), + new WrapperClassMarker(), + + new AllConstantVisitor( + new PackageVisibleMemberInvokingClassMarker()), + + new AllMemberVisitor( + new ContainsConstructorsMarker()) + )), + + // Mark methods. + new AllMethodVisitor( + new OptimizationInfoMemberFilter( + new AllAttributeVisitor( + new DebugAttributeVisitor("Marking method properties", + new MultiAttributeVisitor( + stackSizeComputer, + new CatchExceptionMarker(), + + new AllInstructionVisitor( + new MultiInstructionVisitor( + new SuperInvocationMarker(), + new DynamicInvocationMarker(), + new BackwardBranchMarker(), + new AccessMethodMarker(), + new SynchronizedBlockMethodMarker(), + new FinalFieldAssignmentMarker(), + new NonEmptyStackReturnMarker(stackSizeComputer) + )) + ))))), + + // Mark referenced classes and methods. + new AllMethodVisitor( + new AllAttributeVisitor( + new DebugAttributeVisitor("Marking referenced class properties", + new MultiAttributeVisitor( + new AllExceptionInfoVisitor( + new ExceptionHandlerConstantVisitor( + new ReferencedClassVisitor( + new OptimizationInfoClassFilter( + new CaughtClassMarker())))), + + new AllInstructionVisitor( + new MultiInstructionVisitor( + new InstantiationClassMarker(), + new InstanceofClassMarker(), + new DotClassMarker(), + new MethodInvocationMarker() + )) + )))) + ))); + } + + @Override + public void visitClassPool(ClassPool classPool) { + InstructionCounter methodInliningUniqueCounter = new InstructionCounter(); + classPool.accept(new TimedClassPoolVisitor("Inlining single methods", + new AllMethodVisitor( + new AllAttributeVisitor( + new DebugAttributeVisitor("Inlining single methods", + new OptimizationCodeAttributeFilter( + new MethodInliner(configuration.microEdition, + configuration.android, + configuration.allowAccessModification, + true, + methodInliningUniqueCounter))))))); + System.out.println("Lambda methods inlined: " + methodInliningUniqueCounter.getCount()); + } +} From cdddc9758aa093580bd0cdd4d07ee4bbdc7481a2 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 10:37:23 +0100 Subject: [PATCH 024/195] Use MethodInliner for inlining invoke methods before & after merging --- .../optimize/kotlin/KotlinLambdaMerger.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 4410cabdc..dc40af825 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -12,6 +12,7 @@ import proguard.classfile.constant.visitor.ConstantTagFilter; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.instruction.visitor.InstructionConstantVisitor; +import proguard.classfile.*; import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.util.ClassSuperHierarchyInitializer; import proguard.classfile.visitor.*; @@ -21,9 +22,11 @@ import proguard.optimize.info.OptimizationCodeAttributeFilter; import proguard.optimize.info.ParameterUsageMarker; import proguard.optimize.peephole.MethodInliner; +import proguard.optimize.*; import proguard.resources.file.ResourceFilePool; import java.io.IOException; +import java.util.Objects; public class KotlinLambdaMerger { @@ -97,6 +100,11 @@ public ClassPool execute(final ClassPool programClassPool, // group the lambda's per package PackageGrouper packageGrouper = new PackageGrouper(); lambdaClassPool.classesAccept(packageGrouper); + + // let the method inliner inline the specific invoke methods into the bridge methods + inlineLambdaInvokeMethods(programClassPool, libraryClassPool, lambdaClassPool); + //lambdaClassPool.classesAccept(new ClassPrinter()); + ClassPool lambdaGroupClassPool = new ClassPool(); // merge the lambda's per package @@ -108,6 +116,8 @@ public ClassPool execute(final ClassPool programClassPool, newProgramClassPoolFiller), extraDataEntryNameMap)); + inlineLambdaGroupInvokeMethods(newProgramClassPool, libraryClassPool, lambdaGroupClassPool); + // initialise the super classes of the newly created lambda groups ClassSubHierarchyInitializer hierarchyInitializer = new ClassSubHierarchyInitializer(); newProgramClassPool.accept(hierarchyInitializer); @@ -140,4 +150,30 @@ private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool } return kotlinFunction0Interface; } + + private void inlineLambdaInvokeMethods(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaClassPool) + { + // Make the non-bridge invoke methods private, so they can be inlined. + lambdaClassPool.classesAccept(new AllMethodVisitor( + new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + if ((programMethod.u2accessFlags & AccessConstants.BRIDGE) == 0) + { + if (Objects.equals(programMethod.getName(programClass), "invoke")) + { + programMethod.u2accessFlags &= ~AccessConstants.PUBLIC; + programMethod.u2accessFlags |= AccessConstants.PRIVATE; + } + } + } + } + )); + lambdaClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); + } + + private void inlineLambdaGroupInvokeMethods(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) + { + lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); + } } From e8b70e5b0fcb0da0dbf1f23e07c0011052a714fc Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 11:38:29 +0100 Subject: [PATCH 025/195] Copy lambda bridge invoke method to lambda group (after inlining specific invoke) --- .../proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index e44e5d845..52ab5ec34 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -204,7 +204,10 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) */ private ProgramMethod getInvokeMethod(ProgramClass lambdaClass) { - ProgramMethod bridgeInvokeMethod = null; + // Assuming that all specific invoke methods have been inlined into the bridge invoke methods + // we can take the bridge invoke method (which returns an Object) + ProgramMethod bridgeInvokeMethod = lambdaClass.findMethod("invoke", "()Ljava/lang/Object;"); + /* for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { ProgramMethod method = lambdaClass.methods[methodIndex]; if (method.getName(lambdaClass).equals("invoke")) @@ -218,7 +221,7 @@ private ProgramMethod getInvokeMethod(ProgramClass lambdaClass) bridgeInvokeMethod = method; } } - } + }*/ return bridgeInvokeMethod; } From fdb84266bab1217c461e7a17d3189cad393e25f5 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 11:40:29 +0100 Subject: [PATCH 026/195] Comment on invoke inlining assumption --- .../proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 52ab5ec34..9dc4149e6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -188,8 +188,9 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) // Note: the lambda class is expected to containt two invoke methods: // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface - // - a specific method that contains the implementation of the lambda, of which the - // Assumption: the specific invoke method is found before the + // - a specific method that contains the implementation of the lambda + // Assumption: the specific invoke method has been inlined into the bridge invoke method, such that + // copying the bridge method to the lambda group is sufficient to retrieve the full implementation ProgramMethod invokeMethod = getInvokeMethod(lambdaClass); String newMethodName = lambdaClass.getName().substring(lambdaClass.getName().lastIndexOf("/") + 1) + "$invoke"; invokeMethod.accept(lambdaClass, new MethodCopier(this.lambdaGroup, newMethodName)); From 43157e8aec2fc216740e2a1276b40941c1e2d217 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 13:06:41 +0100 Subject: [PATCH 027/195] Cast invoke Method to ProgramMethod --- .../java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 9dc4149e6..4be7aa0be 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -207,7 +207,7 @@ private ProgramMethod getInvokeMethod(ProgramClass lambdaClass) { // Assuming that all specific invoke methods have been inlined into the bridge invoke methods // we can take the bridge invoke method (which returns an Object) - ProgramMethod bridgeInvokeMethod = lambdaClass.findMethod("invoke", "()Ljava/lang/Object;"); + ProgramMethod bridgeInvokeMethod = (ProgramMethod)lambdaClass.findMethod("invoke", "()Ljava/lang/Object;"); /* for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { ProgramMethod method = lambdaClass.methods[methodIndex]; From 0cd65e81c83f4ca60424cac462457eb88a6e6070 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 14:31:52 +0100 Subject: [PATCH 028/195] KotlinLambdaMergerTest: assert all referenced classes are in class pool --- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 72 +++++++++++++++---- 1 file changed, 60 insertions(+), 12 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 48f657b23..5858196eb 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -21,13 +21,17 @@ package proguard.optimize.kotlin -import io.kotest.core.spec.IsolationMode import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldBeIn +import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual import io.kotest.matchers.shouldBe +import io.mockk.* +import proguard.Configuration import proguard.classfile.ClassConstants import proguard.classfile.ClassPool +import proguard.classfile.constant.ClassConstant +import proguard.classfile.constant.visitor.ConstantVisitor import proguard.classfile.visitor.ClassMethodFilter import proguard.classfile.visitor.ClassPoolFiller import proguard.classfile.visitor.ImplementedClassFilter @@ -37,16 +41,23 @@ import testutils.KotlinSource class KotlinLambdaMergerTest : FreeSpec({ - // InstancePerTest so the names are reset before every test - isolationMode = IsolationMode.InstancePerTest - val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( "Test.kt", """ package app.package1 fun main(index: Int) { - val lambda1 = { println("Lambda1") } + val lambda1 = { + println("Lambda1") + val lambda1a = { + println("Lambda1a") + val lambda1a1 = { + println("Lambda1a1") + } + lambda1a1() + } + lambda1a() + } val lambda2 = { println("Lambda2") } val lambda3 = { println("Lambda3") } when (index) { @@ -76,8 +87,8 @@ class KotlinLambdaMergerTest : FreeSpec({ // A class pool where the applicable lambda's will be stored val lambdaClassPool = ClassPool() - val kotlinLambdaClass = libraryClassPool.getClass(ClassConstants.NAME_KOTLIN_LAMBDA) - val kotlinFunction0Interface = libraryClassPool.getClass(ClassConstants.NAME_KOTLIN_FUNCTION0) + val kotlinLambdaClass = libraryClassPool.getClass(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA) + val kotlinFunction0Interface = libraryClassPool.getClass(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0) programClassPool.classesAccept(ImplementedClassFilter( kotlinFunction0Interface, false, ImplementedClassFilter( @@ -86,18 +97,18 @@ class KotlinLambdaMergerTest : FreeSpec({ ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, ClassPoolFiller(lambdaClassPool), null), null), null)) - "Given a Kotlin Lambda Package Grouper" - { + "Given a Kotlin Lambda Package Grouper" { val grouper = PackageGrouper() - "When the grouper is applied to lambda's of different packages" - { + "When the grouper is applied to lambda's of different packages" { programClassPool.classesAccept(grouper) "Then the grouper has found 2 packages" { grouper.size() shouldBe 2 } - "Then the grouper has found the packages app/package1 and app/package2" - { + "Then the grouper has found the packages app/package1 and app/package2" { grouper.containsPackage("app/package1") shouldBe true grouper.containsPackage("app/package2") shouldBe true } - "Then the grouper does not contain other packages" - { + "Then the grouper does not contain other packages" { grouper.packageNames().forEach { it shouldBeIn arrayListOf("app/package2", "app/package1") } @@ -106,13 +117,50 @@ class KotlinLambdaMergerTest : FreeSpec({ } "Given a Kotlin Lambda Merger and entry name mapper" - { - val merger = KotlinLambdaMerger(null) + val merger = KotlinLambdaMerger(Configuration()) val nameMapper = ExtraDataEntryNameMap() "When the merger is applied to the class pools" - { val newProgramClassPool = merger.execute(programClassPool, libraryClassPool, null, nameMapper) + val newFullClassPool = newProgramClassPool.classes() union libraryClassPool.classes() "Then the resulting program class pool contains less classes" { programClassPool.size() shouldBeGreaterThanOrEqual newProgramClassPool.size() } + + "Then the program classes should only refer to classes that are longer in the class pool" { + val visitor = mockk() + val slot = slot() + every { + visitor.visitAnyConstant(any(), any()) + visitor.visitDoubleConstant(any(), any()) + visitor.visitDynamicConstant(any(), any()) + visitor.visitAnyRefConstant(any(), any()) + visitor.visitAnyMethodrefConstant(any(), any()) + visitor.visitFieldrefConstant(any(), any()) + visitor.visitFloatConstant(any(), any()) + visitor.visitIntegerConstant(any(), any()) + visitor.visitInterfaceMethodrefConstant(any(), any()) + visitor.visitInvokeDynamicConstant(any(), any()) + visitor.visitLongConstant(any(), any()) + visitor.visitMethodHandleConstant(any(), any()) + visitor.visitMethodrefConstant(any(), any()) + visitor.visitMethodTypeConstant(any(), any()) + visitor.visitModuleConstant(any(), any()) + visitor.visitNameAndTypeConstant(any(), any()) + visitor.visitPackageConstant(any(), any()) + visitor.visitPrimitiveArrayConstant(any(), any()) + visitor.visitStringConstant(any(), any()) + visitor.visitUtf8Constant(any(), any()) + } just Runs + every { + visitor.visitClassConstant(any(), capture(slot)) + } answers { + println("Visit class constant referring to ${slot.captured.referencedClass}") + newFullClassPool shouldContain slot.captured.referencedClass + } + newProgramClassPool.classes().forEach { + it.constantPoolEntriesAccept(visitor) + } + } } } }) From a3a662a2eb532dccb6435c95bad9a10a4ad659a9 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 3 Jan 2022 14:41:03 +0100 Subject: [PATCH 029/195] Correct comment in test --- .../kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 5858196eb..70eb67148 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -126,7 +126,7 @@ class KotlinLambdaMergerTest : FreeSpec({ programClassPool.size() shouldBeGreaterThanOrEqual newProgramClassPool.size() } - "Then the program classes should only refer to classes that are longer in the class pool" { + "Then the program classes should only refer to classes that are in the class pool" { val visitor = mockk() val slot = slot() every { From 506ea6fe4f7a455bae879d10ef288f4e3f288dd8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 4 Jan 2022 19:07:59 +0100 Subject: [PATCH 030/195] Add proguard-core with relative build path to settings.gradle --- settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle b/settings.gradle index af28e14c7..16d615925 100644 --- a/settings.gradle +++ b/settings.gradle @@ -20,5 +20,6 @@ include 'gui' include 'gradle-plugin' include 'ant' include 'annotations' +includeBuild "../proguard-core" project(':gradle-plugin').name = 'gradle' From 94ee87fc79800bc4532e9709260044ff32f9d3cf Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 4 Jan 2022 19:08:39 +0100 Subject: [PATCH 031/195] Update CI action to refer to proguard-core fork --- .github/workflows/continuous_integration.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index e68ec1ab2..97043b85b 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -13,6 +13,9 @@ jobs: - uses: actions/checkout@v2 with: path: proguard-main + - uses: actions/checkout@v2.4.0 + with: + repository: heckej/proguard-core - uses: actions/setup-java@v1 with: java-version: 8 From 604fae1563a39c41757e6b504bdf13dd18973ce3 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 4 Jan 2022 20:05:10 +0100 Subject: [PATCH 032/195] Set path for proguard-core fork --- .github/workflows/continuous_integration.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index 97043b85b..fc4e752a0 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -16,6 +16,7 @@ jobs: - uses: actions/checkout@v2.4.0 with: repository: heckej/proguard-core + path: .. - uses: actions/setup-java@v1 with: java-version: 8 From 4b96ecc0766be94ec466769885d70d558a261579 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 4 Jan 2022 20:06:08 +0100 Subject: [PATCH 033/195] Update path for proguard-core fork --- .github/workflows/continuous_integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous_integration.yml b/.github/workflows/continuous_integration.yml index fc4e752a0..3e540539f 100644 --- a/.github/workflows/continuous_integration.yml +++ b/.github/workflows/continuous_integration.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2.4.0 with: repository: heckej/proguard-core - path: .. + path: proguard-core - uses: actions/setup-java@v1 with: java-version: 8 From d95e082e06b91adf9ae69250bcab03254fafd743 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 4 Jan 2022 20:12:02 +0100 Subject: [PATCH 034/195] Remove unused import of wrong package --- .../kotlin/KotlinLambdaClassMerger.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 4be7aa0be..1cf0e8edd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -1,6 +1,5 @@ package proguard.optimize.kotlin; -import jdk.internal.reflect.ConstantPool; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.*; @@ -24,7 +23,9 @@ import proguard.io.ExtraDataEntryNameMap; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { @@ -35,6 +36,8 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ExtraDataEntryNameMap extraDataEntryNameMap; + private final Map lambdasToLambdaGroups = new HashMap<>(); + private final Map lambdasToIds = new HashMap<>(); private static final Logger logger = LogManager.getLogger(KotlinLambdaClassMerger.class); public KotlinLambdaClassMerger(final ClassPool programClassPool, @@ -48,6 +51,16 @@ public KotlinLambdaClassMerger(final ClassPool programClassPool, this.extraDataEntryNameMap = extraDataEntryNameMap; } + public Map getLambdaToLambdaGroupMapping() + { + return lambdasToLambdaGroups; + } + + public Map getLambdaToIdMapping() + { + return lambdasToIds; + } + @Override public void visitClassPool(ClassPool lambdaClassPool) { @@ -180,13 +193,15 @@ public void visitProgramClass(ProgramClass lambdaClass) { // replace instantiation of lambda class with instantiation of lambda group // with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId); + this.lambdasToLambdaGroups.put(lambdaClass.getName(), this.lambdaGroup); + this.lambdasToIds.put(lambdaClass.getName(), lambdaClassId); } private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) { logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.lambdaGroup.getName()); - // Note: the lambda class is expected to containt two invoke methods: + // Note: the lambda class is expected to contain two invoke methods: // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface // - a specific method that contains the implementation of the lambda // Assumption: the specific invoke method has been inlined into the bridge invoke method, such that From 340fe1658318aee99fe775baac66da8cf93b53a7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 5 Jan 2022 16:54:02 +0100 Subject: [PATCH 035/195] Move PackageGrouper to ProGuard-CORE --- .../optimize/kotlin/PackageGrouper.java | 59 ------------------- 1 file changed, 59 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java diff --git a/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java b/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java deleted file mode 100644 index 02fe06712..000000000 --- a/base/src/main/java/proguard/optimize/kotlin/PackageGrouper.java +++ /dev/null @@ -1,59 +0,0 @@ -package proguard.optimize.kotlin; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import proguard.classfile.ClassPool; -import proguard.classfile.Clazz; -import proguard.classfile.ProgramClass; -import proguard.classfile.util.ClassUtil; -import proguard.classfile.visitor.ClassPoolVisitor; -import proguard.classfile.visitor.ClassVisitor; - -import java.util.HashMap; -import java.util.Map; - -/** - * This class - */ -public class PackageGrouper implements ClassVisitor { - - private final Map packageClassPools = new HashMap<>(); - private static final Logger logger = LogManager.getLogger(PackageGrouper.class); - - @Override - public void visitAnyClass(Clazz clazz) - { - String classPackageName = ClassUtil.internalPackageName(clazz.getName()); - // or - // String classPackageName = ClassUtil.internalPackageName(clazz.getName()); - if (!packageClassPools.containsKey(classPackageName)) - { - logger.info("New package found: {}", classPackageName); - packageClassPools.put(classPackageName, new ClassPool()); - } - packageClassPools.get(classPackageName).addClass(clazz); - } - - public int size() - { - return packageClassPools.size(); - } - - public boolean containsPackage(String packageName) - { - return packageClassPools.containsKey(packageName); - } - - public Iterable packageNames() - { - return packageClassPools.keySet(); - } - - public void packagesAccept(ClassPoolVisitor classPoolVisitor) - { - for (ClassPool packageClassPool : packageClassPools.values()) - { - classPoolVisitor.visitClassPool(packageClassPool); - } - } -} From b6e9ea135ebbd0c0ba21c2515b5d6e4e27d3b3a8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 5 Jan 2022 16:54:16 +0100 Subject: [PATCH 036/195] Move MethodCopier to ProGuard-CORE --- .../optimize/kotlin/MethodCopier.java | 88 ------------------- 1 file changed, 88 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/kotlin/MethodCopier.java diff --git a/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java b/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java deleted file mode 100644 index 976cf275f..000000000 --- a/base/src/main/java/proguard/optimize/kotlin/MethodCopier.java +++ /dev/null @@ -1,88 +0,0 @@ -package proguard.optimize.kotlin; - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import proguard.classfile.*; -import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.editor.*; -import proguard.classfile.instruction.ConstantInstruction; -import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.visitor.MemberVisitor; - -class MethodCopier implements MemberVisitor, AttributeVisitor, InstructionVisitor -{ - private final ProgramClass destinationClass; - private final ClassBuilder classBuilder; - private final String newMethodNamePrefix; - private final ConstantAdder constantAdder; - private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); - private final ExceptionInfoAdder exceptionInfoAdder; - private int methodCounter = 0; - private static final Logger logger = LogManager.getLogger(MethodCopier.class); - - - public MethodCopier(ProgramClass destinationClass, String newMethodNamePrefix) - { - this.destinationClass = destinationClass; - this.classBuilder = new ClassBuilder(destinationClass); - this.newMethodNamePrefix = newMethodNamePrefix; - this.constantAdder = new ConstantAdder(destinationClass); - this.exceptionInfoAdder = new ExceptionInfoAdder(this.destinationClass, this.codeAttributeComposer); - } - - private int getNewMethodIndex() - { - int methodIndex = this.methodCounter; - this.methodCounter++; - return methodIndex; - } - - @Override - public void visitAnyMember(Clazz clazz, Member member) { - } - - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - codeAttributeComposer.reset(); - programMethod.attributesAccept(programClass, this); - int methodIndex = getNewMethodIndex(); - String newMethodName = newMethodNamePrefix; - if (methodIndex > 1) - { - newMethodName += "$" + methodIndex; - } - ProgramMethod newMethod = classBuilder.addAndReturnMethod(AccessConstants.PRIVATE, newMethodName, programMethod.getDescriptor(programClass)); - codeAttributeComposer.addCodeAttribute(this.destinationClass, newMethod); - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) { - } - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); - // copy code and exceptions - codeAttribute.instructionsAccept(clazz, method, this); - codeAttribute.exceptionsAccept(clazz, method, this.exceptionInfoAdder); - codeAttribute.attributesAccept(clazz, method, this); - codeAttributeComposer.endCodeFragment(); - } - - @Override - public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { - // copy instruction - codeAttributeComposer.appendInstruction(offset, instruction); - } - - @Override - public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - // ensure the referenced constant is in the constant pool at the correct index - constantInstruction.constantIndex = this.constantAdder.addConstant(clazz, constantInstruction.constantIndex); - // copy instruction - codeAttributeComposer.appendInstruction(offset, constantInstruction); - } -} From 878aa066d39a59d557879be4eeb6d868f01f1bd4 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 5 Jan 2022 18:08:08 +0100 Subject: [PATCH 037/195] Remove PackageGrouper tests from KotlinLambdaMergerTest --- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 70eb67148..189b6e171 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -97,25 +97,6 @@ class KotlinLambdaMergerTest : FreeSpec({ ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, ClassPoolFiller(lambdaClassPool), null), null), null)) - "Given a Kotlin Lambda Package Grouper" { - val grouper = PackageGrouper() - "When the grouper is applied to lambda's of different packages" { - programClassPool.classesAccept(grouper) - "Then the grouper has found 2 packages" { - grouper.size() shouldBe 2 - } - "Then the grouper has found the packages app/package1 and app/package2" { - grouper.containsPackage("app/package1") shouldBe true - grouper.containsPackage("app/package2") shouldBe true - } - "Then the grouper does not contain other packages" { - grouper.packageNames().forEach { - it shouldBeIn arrayListOf("app/package2", "app/package1") - } - } - } - } - "Given a Kotlin Lambda Merger and entry name mapper" - { val merger = KotlinLambdaMerger(Configuration()) val nameMapper = ExtraDataEntryNameMap() From 21ad1678cf7278e7f8326a33426a168b00f0f94b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 5 Jan 2022 18:09:39 +0100 Subject: [PATCH 038/195] Make MethodCopier public to be used in KotlinLambdaClassMerger --- .../proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 1cf0e8edd..00e7d8282 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -16,10 +16,7 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassSuperHierarchyInitializer; import proguard.classfile.util.ClassUtil; -import proguard.classfile.visitor.ClassPoolVisitor; -import proguard.classfile.visitor.ClassPrinter; -import proguard.classfile.visitor.ClassVisitor; -import proguard.classfile.visitor.MemberVisitor; +import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; import java.util.ArrayList; From 0e41f06ce5fac263338db4bad88ece068c75f564 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:26:32 +0100 Subject: [PATCH 039/195] Separate builder for entire lambda group invoke method --- .../KotlinLambdaGroupInvokeMethodBuilder.java | 173 ++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java new file mode 100644 index 000000000..04089b0fb --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -0,0 +1,173 @@ +package proguard.optimize.kotlin; + +import proguard.classfile.*; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.CompactCodeAttributeComposer; +import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.util.ClassUtil; + +import java.util.ArrayList; +import java.util.List; + +public class KotlinLambdaGroupInvokeMethodBuilder { + + public static final String METHOD_ARGUMENT_TYPE_INVOKE = "Ljava/lang/Object;"; + public static final String METHOD_RETURN_TYPE_INVOKE = "Ljava/lang/Object;"; + public static final String METHOD_NAME_INVOKE = "invoke"; + private final int arity; + private int caseIndexCounter = 0; + private final ClassBuilder classBuilder; + // tune the initial list size depending on the expected amount of lambda's that are merged + private final List methodsToBeCalled = new ArrayList<>(5); + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; + + public KotlinLambdaGroupInvokeMethodBuilder(int arity, ClassBuilder classBuilder, ClassPool programClassPool, ClassPool libraryClassPool) + { + this.arity = arity; + this.classBuilder = classBuilder; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + } + + /** + * Adds an invocation of the given method to the invoke method that is being built. + * @param methodToBeCalled a method that must be called by the invoke method. The method must belong to the + * lambda group that is being built by the class builder of this invoke method builder. + * @return the case index that uniquely identifies the call to the given method within the invoke method + */ + public int addCallTo(Method methodToBeCalled) + { + methodsToBeCalled.add(methodToBeCalled); + return getNewCaseIndex(); + } + + private int getNewCaseIndex() + { + int caseIndex = this.caseIndexCounter; + this.caseIndexCounter++; + return caseIndex; + } + + /** + * Returns the number of cases that have been added to the invoke method that is being built until now. + */ + public int getCaseIndexCounter() + { + return this.caseIndexCounter; + } + + private Instruction[] getInstructionsForCase(int caseIndex) + { + Method methodToBeCalled = this.methodsToBeCalled.get(caseIndex); + InstructionSequenceBuilder builder = new InstructionSequenceBuilder(this.classBuilder.getProgramClass(), + this.programClassPool, + this.libraryClassPool); + builder.aload_0() // load this lambda group + .invokevirtual(this.classBuilder.getProgramClass(), methodToBeCalled); // invoke the lambda implementation + if (!methodDoesNotHaveReturnValue(methodToBeCalled)) { + // ensure there is a return value + builder.getstatic(ClassConstants.NAME_KOTLIN_UNIT, ClassConstants.FIELD_NAME_KOTLIN_UNIT_INSTANCE, ClassConstants.TYPE_KOTLIN_UNIT); + } + builder.areturn(); // return + return builder.instructions(); + } + + private boolean methodDoesNotHaveReturnValue(Method method) + { + String methodDescriptor = method.getDescriptor(this.classBuilder.getProgramClass()); + String returnType = ClassUtil.internalMethodReturnType(methodDescriptor); + return returnType.equals("V"); + } + + private CompactCodeAttributeComposer addLoadArgumentsInstructions(CompactCodeAttributeComposer composer) + { + for (int argumentIndex = 1; argumentIndex <= this.arity; argumentIndex++) + { + composer.aload(argumentIndex); + } + return composer; + } + + /** + * Returns a code builder that builds the code for the invoke method that is being built. + */ + public ClassBuilder.CodeBuilder buildCodeBuilder() + { + return composer -> { + int cases = getCaseIndexCounter(); + + // TODO: decide what to do when no cases have been added + // - create an implementation that simply returns Unit + // - or throw an exception + if (cases == 0) + { + composer.getstatic(ClassConstants.NAME_KOTLIN_UNIT, ClassConstants.FIELD_NAME_KOTLIN_UNIT_INSTANCE, ClassConstants.TYPE_KOTLIN_UNIT); + composer.areturn(); + return; + } + + CompactCodeAttributeComposer.Label[] caseLabels = new CompactCodeAttributeComposer.Label[cases - 1]; + for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) + { + caseLabels[caseIndex] = composer.createLabel(); + } + CompactCodeAttributeComposer.Label defaultLabel = composer.createLabel(); + CompactCodeAttributeComposer.Label endOfMethodLabel = composer.createLabel(); + + addLoadArgumentsInstructions(composer); + + if (cases > 1) + { + // only add a switch when there is more than one case + // if composer.tableswitch() would be called with cases == 1, then the highCase would be lower than the lowCase + composer + .aload_0() + .getfield(this.classBuilder.getProgramClass(), + this.classBuilder.getProgramClass().findField(KotlinLambdaGroupBuilder.FIELD_NAME_ID, + KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) + .tableswitch(defaultLabel, 0, cases - 2, caseLabels); + } + for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) + { + composer + .label(caseLabels[caseIndex]) + .appendInstructions(getInstructionsForCase(caseIndex)) + .goto_(endOfMethodLabel); + } + composer + .label(defaultLabel) + .appendInstructions(getInstructionsForCase(cases - 1)) + .label(endOfMethodLabel) + .areturn(); + }; + } + + private static String getMethodDescriptorForArity(int arity) + { + StringBuilder descriptor = new StringBuilder("("); + for (int argumentIndex = 0; argumentIndex < arity; argumentIndex++) + { + descriptor.append(METHOD_ARGUMENT_TYPE_INVOKE); + } + descriptor.append(")").append(METHOD_RETURN_TYPE_INVOKE); + return descriptor.toString(); + } + + /** + * Adds a new invoke method to the class builder of this invoke method builder. + * If the new method has to replace an existing invoke method, ensure that the original invoke method + * has been removed before calling this method. + * @return the newly created invoke method, which has been added to the program class under construction + */ + public ProgramMethod build() + { + return classBuilder.addAndReturnMethod(AccessConstants.PUBLIC | AccessConstants.SYNTHETIC, + METHOD_NAME_INVOKE, + getMethodDescriptorForArity(this.arity), + 50, + this.buildCodeBuilder()); + + } +} From d87fc353670f57688e8fe324bf62433d6f4e9be6 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:27:24 +0100 Subject: [PATCH 040/195] Separate builder for entire lambda group constructor --- .../kotlin/KotlinLambdaGroupInitBuilder.java | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java new file mode 100644 index 000000000..9aea98c9e --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java @@ -0,0 +1,87 @@ +package proguard.optimize.kotlin; + +import proguard.classfile.AccessConstants; +import proguard.classfile.ClassConstants; +import proguard.classfile.ProgramField; +import proguard.classfile.ProgramMethod; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.CompactCodeAttributeComposer; + +public class KotlinLambdaGroupInitBuilder { + + public static final String TYPE_KOTLIN_LAMBDA_INIT = "(I)V"; + public static final String RETURN_TYPE_INIT = "V"; + public static final String METHOD_ARGUMENT_TYPE_INIT = "Ljava/lang/Object;"; + + private final int closureSize; + private final ClassBuilder classBuilder; + + public KotlinLambdaGroupInitBuilder(int closureSize, ClassBuilder classBuilder) + { + // TODO: support arguments of specific types + // e.g. by taking a list of argument types instead of a closure size + this.closureSize = closureSize; + this.classBuilder = classBuilder; + } + + private static String getInitDescriptorForClosureSize(int closureSize) + { + StringBuilder descriptor = new StringBuilder("("); + for (int argumentIndex = 1; argumentIndex <= closureSize; argumentIndex++) + { + descriptor.append(METHOD_ARGUMENT_TYPE_INIT); + } + descriptor.append(KotlinLambdaGroupBuilder.FIELD_TYPE_ID); + descriptor.append(")") + .append(RETURN_TYPE_INIT); + return descriptor.toString(); + } + + private CompactCodeAttributeComposer addPutFreeVariablesInFieldsInstructions(CompactCodeAttributeComposer composer) + { + for (int argumentIndex = 1; argumentIndex <= this.closureSize; argumentIndex++) + { + composer.aload(argumentIndex) + .putfield(this.classBuilder.getProgramClass(), + this.classBuilder.getProgramClass().findField( + KotlinLambdaGroupBuilder.FIELD_NAME_PREFIX_FREE_VARIABLE + argumentIndex, + KotlinLambdaGroupBuilder.FIELD_TYPE_FREE_VARIABLE + )); + } + return composer; + } + + public ClassBuilder.CodeBuilder buildCodeBuilder() + { + return code -> { + code + .aload_0(); // load this class + // TODO: load parameter variables and store them in their respective fields + addPutFreeVariablesInFieldsInstructions(code) + .iload(this.closureSize + 1) // load the id argument + .putfield(this.classBuilder.getProgramClass(), // store the id in a field + this.classBuilder.getProgramClass() + .findField(KotlinLambdaGroupBuilder.FIELD_NAME_ID, + KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) + .aload_0() // load this class + .iconst_0() // push 0 on stack + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + TYPE_KOTLIN_LAMBDA_INIT) + .return_(); + }; + } + + public ProgramMethod build() + { + // add a classId field to the lambda group + ProgramField classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, "classId", "I"); + + // add a constructor which takes an id as argument and stores it in the classId field + return classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + getInitDescriptorForClosureSize(this.closureSize), + 50, + this.buildCodeBuilder()); + } +} From 0e675242797bf39e167f86be1dae9b2d0ad099e5 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:28:02 +0100 Subject: [PATCH 041/195] Separate builder for lambda groups --- .../kotlin/KotlinLambdaGroupBuilder.java | 256 ++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java new file mode 100644 index 000000000..f620f172c --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -0,0 +1,256 @@ +package proguard.optimize.kotlin; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.Configuration; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; +import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; +import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.*; +import proguard.optimize.MethodInlinerWrapper; +import proguard.optimize.info.ProgramClassOptimizationInfo; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * This ClassVisitor can be used to visit Kotlin lambda classes that should be merged into one lambda group. + */ +public class KotlinLambdaGroupBuilder implements ClassVisitor { + + public static final String FIELD_NAME_ID = "classId"; + public static final String FIELD_TYPE_ID = "I"; + public static final String FIELD_NAME_PREFIX_FREE_VARIABLE = "freeVar"; + public static final String FIELD_TYPE_FREE_VARIABLE = "Ljava/lang/Object;"; + public static final String METHOD_NAME_SUFFIX_INVOKE = "$invoke"; + + private final ClassBuilder classBuilder; + private final Configuration configuration; + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; + private final Map invokeMethodBuilders; + private static final Logger logger = LogManager.getLogger(KotlinLambdaGroupBuilder.class); + + /** + * Initialises a new Kotlin lambda group builder with the given name as the name for the lambda group to be built. + * @param lambdaGroupName a name for the new lambda group + * @param programClassPool a program class pool containing classes that can be referenced by the new lambda group + * @param libraryClassPool a library class pool containing classes that can be referenced by the new lambda group + */ + public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool) + { + this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, libraryClassPool); + this.configuration = configuration; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.invokeMethodBuilders = new HashMap<>(); + initialiseLambdaGroup(); + } + + private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName, ClassPool programClassPool, ClassPool libraryClassPool) + { + // The initial builder is used to set up the initial lambda group class + ClassBuilder initialBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, + AccessConstants.PUBLIC, + lambdaGroupName, + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA); + ProgramClass lambdaGroup = initialBuilder.getProgramClass(); + // The new builder receives the class pools, such that references can be added when necessary + return new ClassBuilder(lambdaGroup, programClassPool, libraryClassPool); + } + + private void initialiseLambdaGroup() + { + addIdField(); + addFreeVariableFields(); + } + + private void addIdField() + { + classBuilder.addAndReturnField(AccessConstants.PRIVATE, FIELD_NAME_ID, FIELD_TYPE_ID); + } + + private void addFreeVariableFields() + { + // TODO: add support for non-empty closures + // by adding fields + } + + private KotlinLambdaGroupInvokeMethodBuilder getInvokeMethodBuilder(int arity) + { + KotlinLambdaGroupInvokeMethodBuilder builder = this.invokeMethodBuilders.get(arity); + if (builder == null) + { + builder = new KotlinLambdaGroupInvokeMethodBuilder(arity, this.classBuilder, this.programClassPool, this.libraryClassPool); + this.invokeMethodBuilders.put(arity, builder); + } + return builder; + } + + @Override + public void visitAnyClass(Clazz clazz) {} + + @Override + public void visitProgramClass(ProgramClass lambdaClass) { + if (!KotlinLambdaMerger.shouldMerge(lambdaClass)) + { + return; + } + + // TODO: check whether lambda class has inner lambda's + // if so, visit inner lambda's first + // TODO: ensure that visited inner classes are lambda's + // TODO: ensure that visited inner classes are lambda's of the expected type (arity, closure size, free variable types) + lambdaClass.attributeAccept(Attribute.INNER_CLASSES, + new AllInnerClassesInfoVisitor( + new InnerClassInfoClassConstantVisitor( + new ClassConstantToClassVisitor( + new ClassNameFilter(lambdaClass.getName(), + (ClassVisitor)null, + (ClassVisitor)this)), // don't revisit the current lambda + null))); + + ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); + logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); + + inlineLambdaInvokeMethods(lambdaClass); + ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); + int arity = ClassUtil.internalMethodParameterCount(copiedMethod.getDescriptor(lambdaGroup)); + int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); + + // inline the helper invoke methods into the general invoke method + inlineMethodsInsideClass(lambdaClass); + + // replace instantiation of lambda class with instantiation of lambda group with correct id + updateLambdaInstantiationSite(lambdaClass, lambdaClassId); + + // update optimisation info of lambda to show lambda has been merged + ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); + optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); + } + + private void inlineMethodsInsideClass(ProgramClass lambdaClass) + { + // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner + ClassPool lambdaClassPool = new ClassPool(lambdaClass); + lambdaClassPool.accept(new MethodInlinerWrapper(this.configuration, this.programClassPool, this.libraryClassPool)); + } + + private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) + { + // Make the non-bridge invoke methods private, so they can be inlined. + lambdaClass.accept(new AllMethodVisitor( + new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + if ((programMethod.u2accessFlags & AccessConstants.BRIDGE) == 0) + { + if (Objects.equals(programMethod.getName(programClass), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE)) + { + programMethod.u2accessFlags &= ~AccessConstants.PUBLIC; + programMethod.u2accessFlags |= AccessConstants.PRIVATE; + } + } + } + } + )); + inlineMethodsInsideClass(lambdaClass); + } + + private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) + { + logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); + + // Note: the lambda class is expected to contain two invoke methods: + // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface + // - a specific method that contains the implementation of the lambda + // Assumption: the specific invoke method has been inlined into the bridge invoke method, such that + // copying the bridge method to the lambda group is sufficient to retrieve the full implementation + ProgramMethod invokeMethod = getInvokeMethod(lambdaClass); + if (invokeMethod == null) + { + throw new NullPointerException("No invoke method was found in lambda class " + lambdaClass); + } + String newMethodName = createDerivedInvokeMethodName(lambdaClass); + invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), newMethodName)); + return (ProgramMethod)this.classBuilder.getProgramClass().findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); + // TODO: ensure that fields that are referenced by the copied method exist in the lambda group, are initialised, + // and cast to the correct type inside the copied method + } + + private static String createDerivedInvokeMethodName(ProgramClass lambdaClass) + { + String shortClassName = ClassUtil.internalShortClassName(lambdaClass.getName()); + return shortClassName + METHOD_NAME_SUFFIX_INVOKE; + } + + /** + * Returns the bridge invoke method of the given class. + * If no bridge invoke method was found, but a non-bridge invoke method was found, then the non-bridge + * invoke method is returned. If no invoke method was found, then null is returned. + * @param lambdaClass the lambda class of which a (bridge) invoke method is to be returned + */ + private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) + { + // Assuming that all specific invoke methods have been inlined into the bridge invoke methods + // we can take the bridge invoke method (which overrides the invoke method of the FunctionX interface) + ProgramMethod nonBridgeInvokeMethod = null; + for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { + ProgramMethod method = lambdaClass.methods[methodIndex]; + if (method.getName(lambdaClass).equals(KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE)) + { + if ((method.u2accessFlags & AccessConstants.BRIDGE) != 0) { + // we have found the bridge invoke method + return method; + } + nonBridgeInvokeMethod = method; + } + } + return nonBridgeInvokeMethod; + } + + /** + * Updates enclosing method of the given lambdaClass to instantiate the lambda group that is built by this builder. + * @param lambdaClass the lambda class of which the enclosing method must be updated + * @param lambdaClassId the id that is used for the given lambda class to identify its implementation in the lambda group + */ + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) + { + logger.info("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); + lambdaClass.attributesAccept(new KotlinLambdaEnclosingMethodUpdater( + this.programClassPool, + this.libraryClassPool, + this.classBuilder.getProgramClass(), + lambdaClassId)); + } + + private void addInitConstructors() + { + // TODO: add support for non-empty closures + KotlinLambdaGroupInitBuilder builder = new KotlinLambdaGroupInitBuilder(0, this.classBuilder); + builder.build(); + } + + private void addInvokeMethods() + { + for (KotlinLambdaGroupInvokeMethodBuilder builder : this.invokeMethodBuilders.values()) + { + builder.build(); + } + } + + public ProgramClass build() + { + // create (int id) + // create invoke(...) method, based on invokeArity + // + addInitConstructors(); + addInvokeMethods(); + return this.classBuilder.getProgramClass(); + } +} From 2c4c0e38bb3e574a19ee5993d7bb87a03b42b9f4 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:28:56 +0100 Subject: [PATCH 042/195] Add lambdaGroup property to ProgramClassOptimizationInfo Modified classes: ProgramClassOptimizationInfo --- .../optimize/info/ProgramClassOptimizationInfo.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java b/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java index 4254cb20f..8255945e3 100644 --- a/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java +++ b/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java @@ -44,6 +44,7 @@ public class ProgramClassOptimizationInfo private volatile boolean mayBeMerged = true; private volatile Clazz wrappedClass; private volatile Clazz targetClass; + private volatile Clazz lambdaGroup; public boolean isKept() @@ -250,6 +251,18 @@ public Clazz getTargetClass() } + public void setLambdaGroup(Clazz lambdaGroup) + { + this.lambdaGroup = lambdaGroup; + } + + + public Clazz getLambdaGroup() + { + return lambdaGroup; + } + + /** * Merges in the given information of a class that is merged. */ From 82b4d0c10f5f948b85d6df64f80e2335d13930e4 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:29:56 +0100 Subject: [PATCH 043/195] Method to check whether lambda should be merged Modified classes: KotlinLambdaMerger --- .../proguard/optimize/kotlin/KotlinLambdaMerger.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index dc40af825..d151ca9d7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -176,4 +176,16 @@ private void inlineLambdaGroupInvokeMethods(ClassPool programClassPool, ClassPoo { lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); } + + /** + * Checks whether the given lambda class should still be merged. + * Returns true if the lambda class has not yet been merged and is allowed to be merged. + * @param lambdaClass the lambda class for which should be checked whether it should be merged + */ + public static boolean shouldMerge(ProgramClass lambdaClass) + { + ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); + return (lambdaClass.getProcessingFlags() & ProcessingFlags.DONT_OPTIMIZE) == 0 + && optimizationInfo.getLambdaGroup() == null; + } } From 764279ad3a2c0907f0bf85b8bce5ef8b5b130367 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:30:52 +0100 Subject: [PATCH 044/195] Add optimisation info to lambda's before merging Modified classes: KotlinLambdaMerger --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d151ca9d7..4be424b82 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -101,6 +101,9 @@ public ClassPool execute(final ClassPool programClassPool, PackageGrouper packageGrouper = new PackageGrouper(); lambdaClassPool.classesAccept(packageGrouper); + // add optimisation info to the lambda's, so that it can be filled out later + lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); + // let the method inliner inline the specific invoke methods into the bridge methods inlineLambdaInvokeMethods(programClassPool, libraryClassPool, lambdaClassPool); //lambdaClassPool.classesAccept(new ClassPrinter()); From c21c7a92c71d5af3b68d3e0d6d6e9fa69596108a Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:32:53 +0100 Subject: [PATCH 045/195] Don't inline before/after entire lambda merging process Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 4be424b82..a4dc711e9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -105,7 +105,6 @@ public ClassPool execute(final ClassPool programClassPool, lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); // let the method inliner inline the specific invoke methods into the bridge methods - inlineLambdaInvokeMethods(programClassPool, libraryClassPool, lambdaClassPool); //lambdaClassPool.classesAccept(new ClassPrinter()); ClassPool lambdaGroupClassPool = new ClassPool(); @@ -119,8 +118,6 @@ public ClassPool execute(final ClassPool programClassPool, newProgramClassPoolFiller), extraDataEntryNameMap)); - inlineLambdaGroupInvokeMethods(newProgramClassPool, libraryClassPool, lambdaGroupClassPool); - // initialise the super classes of the newly created lambda groups ClassSubHierarchyInitializer hierarchyInitializer = new ClassSubHierarchyInitializer(); newProgramClassPool.accept(hierarchyInitializer); @@ -154,32 +151,6 @@ private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool return kotlinFunction0Interface; } - private void inlineLambdaInvokeMethods(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaClassPool) - { - // Make the non-bridge invoke methods private, so they can be inlined. - lambdaClassPool.classesAccept(new AllMethodVisitor( - new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - if ((programMethod.u2accessFlags & AccessConstants.BRIDGE) == 0) - { - if (Objects.equals(programMethod.getName(programClass), "invoke")) - { - programMethod.u2accessFlags &= ~AccessConstants.PUBLIC; - programMethod.u2accessFlags |= AccessConstants.PRIVATE; - } - } - } - } - )); - lambdaClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); - } - - private void inlineLambdaGroupInvokeMethods(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) - { - lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); - } - /** * Checks whether the given lambda class should still be merged. * Returns true if the lambda class has not yet been merged and is allowed to be merged. From ef3c2ddf72898b1235da469e473baf6eabcf4a79 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:33:07 +0100 Subject: [PATCH 046/195] Remove unused imports Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 20 ++++--------------- 1 file changed, 4 insertions(+), 16 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index a4dc711e9..672f2a9ab 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -3,27 +3,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.Configuration; -import proguard.classfile.ClassConstants; -import proguard.classfile.ClassPool; -import proguard.classfile.Clazz; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.attribute.visitor.DebugAttributeVisitor; -import proguard.classfile.constant.Constant; -import proguard.classfile.constant.visitor.ConstantTagFilter; -import proguard.classfile.instruction.visitor.AllInstructionVisitor; -import proguard.classfile.instruction.visitor.InstructionConstantVisitor; import proguard.classfile.*; import proguard.classfile.util.ClassSubHierarchyInitializer; -import proguard.classfile.util.ClassSuperHierarchyInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; -import proguard.optimize.OptimizationInfoMemberFilter; -import proguard.optimize.TimedClassPoolVisitor; -import proguard.optimize.info.OptimizationCodeAttributeFilter; -import proguard.optimize.info.ParameterUsageMarker; -import proguard.optimize.peephole.MethodInliner; -import proguard.optimize.*; +import proguard.optimize.MethodInlinerWrapper; +import proguard.optimize.info.ProgramClassOptimizationInfo; +import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.resources.file.ResourceFilePool; +import proguard.util.ProcessingFlags; import java.io.IOException; import java.util.Objects; From 17afac1321500f8721ba38cb980d4efb06dc3260 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:42:32 +0100 Subject: [PATCH 047/195] KotlinLambdaClassMerger as ClassPoolVisitor uses lambda group builder Modified classes: KotlinLambdaClassMerger --- .../kotlin/KotlinLambdaClassMerger.java | 205 ++---------------- 1 file changed, 19 insertions(+), 186 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 00e7d8282..82f2fa104 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -2,6 +2,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; @@ -24,107 +25,47 @@ import java.util.List; import java.util.Map; -public class KotlinLambdaClassMerger implements ClassPoolVisitor, ClassVisitor { +public class KotlinLambdaClassMerger implements ClassPoolVisitor { + private static final String NAME_LAMBDA_GROUP = "LambdaGroup"; private final ClassVisitor lambdaGroupVisitor; - private ProgramClass lambdaGroup = new ProgramClass(); - private ClassBuilder classBuilder; - private LambdaGroupInvokeCodeBuilder invokeCodeBuilder; + private final Configuration configuration; private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final ExtraDataEntryNameMap extraDataEntryNameMap; - private final Map lambdasToLambdaGroups = new HashMap<>(); - private final Map lambdasToIds = new HashMap<>(); private static final Logger logger = LogManager.getLogger(KotlinLambdaClassMerger.class); - public KotlinLambdaClassMerger(final ClassPool programClassPool, + public KotlinLambdaClassMerger(final Configuration configuration, + final ClassPool programClassPool, final ClassPool libraryClassPool, final ClassVisitor lambdaGroupVisitor, final ExtraDataEntryNameMap extraDataEntryNameMap) { - this.programClassPool = programClassPool; - this.libraryClassPool = libraryClassPool; - this.lambdaGroupVisitor = lambdaGroupVisitor; + this.configuration = configuration; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.lambdaGroupVisitor = lambdaGroupVisitor; this.extraDataEntryNameMap = extraDataEntryNameMap; } - public Map getLambdaToLambdaGroupMapping() - { - return lambdasToLambdaGroups; - } - - public Map getLambdaToIdMapping() - { - return lambdasToIds; - } - @Override public void visitClassPool(ClassPool lambdaClassPool) { // choose a name for the lambda group // ensure that the lambda group is in the same package as the classes of the class pool - String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + "LambdaGroup"; + String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + NAME_LAMBDA_GROUP; logger.info("Creating lambda group with name {}", lambdaGroupName); - // create a lambda group - // let the lambda group extend Lambda - this.classBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, - AccessConstants.PUBLIC, - lambdaGroupName, - KotlinLambdaMerger.NAME_KOTLIN_LAMBDA); - this.lambdaGroup = classBuilder.getProgramClass(); - this.classBuilder = new ClassBuilder(this.lambdaGroup, - this.programClassPool, - this.libraryClassPool); - - // initialise the super class of the lambda group - this.lambdaGroup.accept(new ClassSuperHierarchyInitializer( - this.programClassPool, - this.libraryClassPool)); - - // let the lambda group implement Function0 - classBuilder.addInterface(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0); - - // add a classId field to the lambda group - ProgramField classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, "classId", "I"); - - this.invokeCodeBuilder = new LambdaGroupInvokeCodeBuilder(); - // add a constructor which takes an id as argument and stores it in the classId field - ProgramMethod initMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, - ClassConstants.METHOD_NAME_INIT, - "(I)V", - 50, - code -> { - code - .aload_0() // load this class - .iload_1() // load the id argument - .putfield(lambdaGroup, classIdField) // store the id in a field - .aload_0() // load this class - .iconst_0() // push 0 on stack - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, - ClassConstants.METHOD_NAME_INIT, - "(I)V") - .return_(); - }); - - logger.info("Lambda group {} before adding lambda implementations:", lambdaGroupName); - //lambdaGroup.accept(new ClassPrinter()); + // create a lambda group builder + KotlinLambdaGroupBuilder lambdaGroupBuilder = new KotlinLambdaGroupBuilder(lambdaGroupName, + this.configuration, + this.programClassPool, + this.libraryClassPool); // visit each lambda of this package to add their implementations to the lambda group - lambdaClassPool.classesAccept(this); - - // add an invoke method - ProgramMethod invokeMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, - "invoke", - "()Ljava/lang/Object;", - 50, - this.invokeCodeBuilder.build(this.lambdaGroup, classIdField)); - - // FOR DEBUGGING THE LAMBDA GROUP - addMainMethod(initMethod); + lambdaClassPool.classesAccept(lambdaGroupBuilder); - logger.info("Lambda group {} after adding lambda implementations:", lambdaGroupName); - //lambdaGroup.accept(new ClassPrinter()); + ProgramClass lambdaGroup = lambdaGroupBuilder.build(); // let the lambda group visitor visit the newly created lambda group this.lambdaGroupVisitor.visitProgramClass(lambdaGroup); @@ -133,118 +74,10 @@ public void visitClassPool(ClassPool lambdaClassPool) extraDataEntryNameMap.addExtraClass(lambdaGroup.getName()); } - private void addMainMethod(ProgramMethod initMethod) - { - - classBuilder.addMethod(AccessConstants.PUBLIC | AccessConstants.STATIC, - "main", - "([Ljava/lang/String;)V", - 50, - code -> code - .new_(this.lambdaGroup) - .dup() - .iconst_0() - .invokespecial(this.lambdaGroup, initMethod) - .invokeinterface(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0, "invoke", "()Ljava/lang/Object;") - .return_()); - } - - private String getPackagePrefixOfClasses(ClassPool classPool) + private static String getPackagePrefixOfClasses(ClassPool classPool) { // Assume that all classes in the given class pool are in the same package. String someClassName = classPool.classNames().next(); return ClassUtil.internalPackagePrefix(someClassName); } - - @Override - public void visitAnyClass(Clazz clazz) { - - } - - @Override - public void visitProgramClass(ProgramClass lambdaClass) { - logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); - - ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); - boolean copiedMethodHasReturnValue = !copiedMethod.getDescriptor(this.lambdaGroup).endsWith(")V"); - - // create a new case in the switch that calls the copied invoke method - // add a case to the table switch instruction - //lambdaClass.attributesAccept(invokeComposer); - // TODO: create the instructions that call the correct method - InstructionSequenceBuilder builder = new InstructionSequenceBuilder(lambdaGroup, - this.programClassPool, - this.libraryClassPool); - builder.aload_0() // load this - .invokevirtual(this.lambdaGroup, copiedMethod); // invoke the lambda implementation - if (!copiedMethodHasReturnValue) { - // ensure there is a return value - builder.getstatic("kotlin/Unit", "INSTANCE", "Lkotlin/Unit;"); - } - builder.areturn(); // return - Instruction[] callToMethod = builder.instructions(); - - this.invokeCodeBuilder.addNewCase(callToMethod); - int lambdaClassId = this.invokeCodeBuilder.getCaseIndexCounter() - 1; - - // replace instantiation of lambda class with instantiation of lambda group - // with correct id - updateLambdaInstantiationSite(lambdaClass, lambdaClassId); - this.lambdasToLambdaGroups.put(lambdaClass.getName(), this.lambdaGroup); - this.lambdasToIds.put(lambdaClass.getName(), lambdaClassId); - } - - private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) - { - logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.lambdaGroup.getName()); - - // Note: the lambda class is expected to contain two invoke methods: - // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface - // - a specific method that contains the implementation of the lambda - // Assumption: the specific invoke method has been inlined into the bridge invoke method, such that - // copying the bridge method to the lambda group is sufficient to retrieve the full implementation - ProgramMethod invokeMethod = getInvokeMethod(lambdaClass); - String newMethodName = lambdaClass.getName().substring(lambdaClass.getName().lastIndexOf("/") + 1) + "$invoke"; - invokeMethod.accept(lambdaClass, new MethodCopier(this.lambdaGroup, newMethodName)); - return (ProgramMethod)this.lambdaGroup.findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); - } - - /** - * Returns the specific invoke method of the given lambdaClass, if there is a specific invoke method, - * else returns the bridge invoke method if there is one, else returns null. - * @param lambdaClass - * @return - */ - private ProgramMethod getInvokeMethod(ProgramClass lambdaClass) - { - // Assuming that all specific invoke methods have been inlined into the bridge invoke methods - // we can take the bridge invoke method (which returns an Object) - ProgramMethod bridgeInvokeMethod = (ProgramMethod)lambdaClass.findMethod("invoke", "()Ljava/lang/Object;"); - /* - for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { - ProgramMethod method = lambdaClass.methods[methodIndex]; - if (method.getName(lambdaClass).equals("invoke")) - { - // we have found an invoke method - if (!method.getDescriptor(lambdaClass).equals("()Ljava/lang/Object;")) - { - // we have found the specific invoke method - return method; - } else { - bridgeInvokeMethod = method; - } - } - }*/ - return bridgeInvokeMethod; - } - - private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) - { - logger.info("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); - lambdaClass.attributesAccept(new KotlinLambdaEnclosingMethodUpdater( - this.programClassPool, - this.libraryClassPool, - this.lambdaGroup, - lambdaClassId)); - } } \ No newline at end of file From 6efa42baa86072d31fb68417d8fa52cfaff87b48 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:43:43 +0100 Subject: [PATCH 048/195] Pass configuration to constructor of lambda class merger Modified classes: KotlinLambdaMerger --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 672f2a9ab..5c23304a8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -99,6 +99,7 @@ public ClassPool execute(final ClassPool programClassPool, // merge the lambda's per package packageGrouper.packagesAccept(new KotlinLambdaClassMerger( + this.configuration, programClassPool, libraryClassPool, new MultiClassVisitor( From 29e30dc00baea333709ce3ffaf0e9ae7eaf31a15 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 17:44:32 +0100 Subject: [PATCH 049/195] Remove old (unrelevant) comments Modified classes: KotlinLambdaMerger --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 5c23304a8..09c31af54 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -92,9 +92,6 @@ public ClassPool execute(final ClassPool programClassPool, // add optimisation info to the lambda's, so that it can be filled out later lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); - // let the method inliner inline the specific invoke methods into the bridge methods - //lambdaClassPool.classesAccept(new ClassPrinter()); - ClassPool lambdaGroupClassPool = new ClassPool(); // merge the lambda's per package From eb41035725537020b4138e1d70d30dd6f763f012 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:09:59 +0100 Subject: [PATCH 050/195] Clean classes before lambda merging Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 09c31af54..aea99378e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -58,6 +58,10 @@ public ClassPool execute(final ClassPool programClassPool, final ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException { + // Remove old processing info + programClassPool.classesAccept(new ClassCleaner()); + libraryClassPool.classesAccept(new ClassCleaner()); + // get the Lambda class and the Function0 interface Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); Clazz kotlinFunction0Interface = getKotlinFunction0Interface(programClassPool, libraryClassPool); From 4d22e85ceee8835a542c6c52c74832ac1aac378f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:12:07 +0100 Subject: [PATCH 051/195] Invoke lambda group init without reference (to be initialised later) Modified classes: KotlinLambdaEnclosingMethodUpdater --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 8e6cbf907..c26033faf 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -161,7 +161,7 @@ public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclos .new_(lambdaGroup) .dup() .iconst(this.classId) - .invokespecial(lambdaGroup, lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, "(I)V")) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V") .instructions(); codeAttributeEditor.reset(codeAttribute.u4codeLength); From 9199563ee8e9a387b8831de9dfc6cbdc979dc6ef Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:12:27 +0100 Subject: [PATCH 052/195] Initialise all classes after lambda merging Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index aea99378e..625a39326 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -4,6 +4,7 @@ import org.apache.logging.log4j.Logger; import proguard.Configuration; import proguard.classfile.*; +import proguard.classfile.util.ClassInitializer; import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; @@ -108,9 +109,8 @@ public ClassPool execute(final ClassPool programClassPool, newProgramClassPoolFiller), extraDataEntryNameMap)); - // initialise the super classes of the newly created lambda groups - ClassSubHierarchyInitializer hierarchyInitializer = new ClassSubHierarchyInitializer(); - newProgramClassPool.accept(hierarchyInitializer); + // initialise the references from and to the newly created lambda groups and their enclosing classes + newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, libraryClassPool)); logger.info("{} lambda class(es) found.", lambdaClassPool.size()); logger.info("{} lambda group(s) created.", lambdaGroupClassPool.size()); From f99d34da64c9af887c40f7ec52aa53e7b54dbb2b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:13:13 +0100 Subject: [PATCH 053/195] Add lambd class interfaces to lambda group Modified classes: KotlinLambdaGroupBuilder --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index f620f172c..dfaa821b9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -9,6 +9,8 @@ import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.InterfaceAdder; +import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.optimize.MethodInlinerWrapper; @@ -34,6 +36,7 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; private final Map invokeMethodBuilders; + private final InterfaceAdder interfaceAdder; private static final Logger logger = LogManager.getLogger(KotlinLambdaGroupBuilder.class); /** @@ -49,6 +52,7 @@ public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configurat this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.invokeMethodBuilders = new HashMap<>(); + this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); initialiseLambdaGroup(); } @@ -102,6 +106,10 @@ public void visitProgramClass(ProgramClass lambdaClass) { return; } + // Add interfaces of lambda class to the lambda group + // TODO: ensure that only Function interfaces are added + lambdaClass.interfaceConstantsAccept(this.interfaceAdder); + // TODO: check whether lambda class has inner lambda's // if so, visit inner lambda's first // TODO: ensure that visited inner classes are lambda's From 3ba9c43ab357e380d3e68474fc89b05c298efe73 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:14:15 +0100 Subject: [PATCH 054/195] Referenced class of RefConstant can be null Modified classes: KotlinLambdaEnclosingMethodUpdater --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index c26033faf..021428265 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -232,7 +232,7 @@ private boolean constantReferencesClass(Constant constant, Clazz clazz) private boolean constantReferencesClass(RefConstant refConstant, Clazz clazz) { - return refConstant.referencedClass == clazz || Objects.equals(refConstant.referencedClass.getName(), clazz.getName()); + return refConstant.referencedClass != null && (refConstant.referencedClass == clazz || Objects.equals(refConstant.referencedClass.getName(), clazz.getName())); } } From a4741bd375bc986dd19cc43e8ccb4e00b848ad97 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:55:13 +0100 Subject: [PATCH 055/195] Add support for merging FunctionN lambda's Modified classes: KotlinLambdaGroupInvokeMethodBuilder --- .../kotlin/KotlinLambdaGroupInvokeMethodBuilder.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 04089b0fb..6919e3385 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -15,6 +15,7 @@ public class KotlinLambdaGroupInvokeMethodBuilder { public static final String METHOD_ARGUMENT_TYPE_INVOKE = "Ljava/lang/Object;"; public static final String METHOD_RETURN_TYPE_INVOKE = "Ljava/lang/Object;"; public static final String METHOD_NAME_INVOKE = "invoke"; + public static final String METHOD_TYPE_INVOKE_FUNCTIONN = "([Ljava/lang/Object;)Ljava/lang/Object;"; private final int arity; private int caseIndexCounter = 0; private final ClassBuilder classBuilder; @@ -146,6 +147,11 @@ public ClassBuilder.CodeBuilder buildCodeBuilder() private static String getMethodDescriptorForArity(int arity) { + // arity -1 is used for implementations of FunctionN + if (arity == -1) + { + return METHOD_TYPE_INVOKE_FUNCTIONN; + } StringBuilder descriptor = new StringBuilder("("); for (int argumentIndex = 0; argumentIndex < arity; argumentIndex++) { From 04c434beb1375fa709c04e6a2eb8ae9005696aca Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:55:56 +0100 Subject: [PATCH 056/195] Fix: load lambda group before invoke method arguments Modified classes: KotlinLambdaGroupInvokeMethodBuilder --- .../kotlin/KotlinLambdaGroupInvokeMethodBuilder.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 6919e3385..dfab292d9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -65,9 +65,8 @@ private Instruction[] getInstructionsForCase(int caseIndex) InstructionSequenceBuilder builder = new InstructionSequenceBuilder(this.classBuilder.getProgramClass(), this.programClassPool, this.libraryClassPool); - builder.aload_0() // load this lambda group - .invokevirtual(this.classBuilder.getProgramClass(), methodToBeCalled); // invoke the lambda implementation - if (!methodDoesNotHaveReturnValue(methodToBeCalled)) { + builder.invokevirtual(this.classBuilder.getProgramClass(), methodToBeCalled); // invoke the lambda implementation + if (methodDoesNotHaveReturnValue(methodToBeCalled)) { // ensure there is a return value builder.getstatic(ClassConstants.NAME_KOTLIN_UNIT, ClassConstants.FIELD_NAME_KOTLIN_UNIT_INSTANCE, ClassConstants.TYPE_KOTLIN_UNIT); } @@ -84,6 +83,12 @@ private boolean methodDoesNotHaveReturnValue(Method method) private CompactCodeAttributeComposer addLoadArgumentsInstructions(CompactCodeAttributeComposer composer) { + // load the lambda group, so later on the correct implementation methods can be called on this lambda group + composer.aload_0(); + if (this.arity == -1) + { + return composer.aload_1(); + } for (int argumentIndex = 1; argumentIndex <= this.arity; argumentIndex++) { composer.aload(argumentIndex); From ea38c1ad11e12219af926562567ad6e48d30036e Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:56:29 +0100 Subject: [PATCH 057/195] Use separate invoke method builder for FunctionN lambda's Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index dfaa821b9..d2ea5a4ea 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -129,6 +129,11 @@ public void visitProgramClass(ProgramClass lambdaClass) { inlineLambdaInvokeMethods(lambdaClass); ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); int arity = ClassUtil.internalMethodParameterCount(copiedMethod.getDescriptor(lambdaGroup)); + if (arity == 1 && lambdaClass.extendsOrImplements(KotlinLambdaMerger.NAME_KOTLIN_FUNCTIONN) + && Objects.equals(copiedMethod.getDescriptor(lambdaGroup), KotlinLambdaGroupInvokeMethodBuilder.METHOD_TYPE_INVOKE_FUNCTIONN)) + { + arity = -1; + } int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); // inline the helper invoke methods into the general invoke method From 012eeeaece17a4cc8035663cfa1ba4501080d66e Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 6 Jan 2022 20:59:04 +0100 Subject: [PATCH 058/195] Merge lambda's of all arities (with empty closure) Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 625a39326..1bd8404f8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -81,12 +81,10 @@ public ClassPool execute(final ClassPool programClassPool, // find all lambda classes of arity 0 and with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty - programClassPool.classesAccept(new ImplementedClassFilter(kotlinFunction0Interface, false, - new ImplementedClassFilter(kotlinLambdaClass, false, + programClassPool.classesAccept(new ImplementedClassFilter(kotlinLambdaClass, false, new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassPoolFiller(lambdaClassPool), newProgramClassPoolFiller), - newProgramClassPoolFiller), newProgramClassPoolFiller) ); From b3178bd24a05e6e02bddb260e459307c90f7d6c8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:39:41 +0100 Subject: [PATCH 059/195] Only inline methods of same class for lambda merging Modified classes: MethodInliner, MethodInlinerWrapper --- .../optimize/MethodInlinerWrapper.java | 17 ++++---- .../optimize/peephole/MethodInliner.java | 43 ++----------------- 2 files changed, 13 insertions(+), 47 deletions(-) diff --git a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java index 7ae7a1a50..cd4f3c6f1 100644 --- a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java +++ b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java @@ -179,19 +179,19 @@ private void markClasses(final ClassPool programClassPool, // Mark methods based on their headers. - programClassPool.classesAccept( + /*programClassPool.classesAccept( new AllMethodVisitor( new OptimizationInfoMemberFilter( new MultiMemberVisitor( new SideEffectMethodMarker(), new ParameterEscapeMarker() - )))); + ))));*/ - programClassPool.accept(new InfluenceFixpointVisitor( - new SideEffectVisitorMarkerFactory())); + /*programClassPool.accept(new InfluenceFixpointVisitor( + new SideEffectVisitorMarkerFactory()));*/ // Mark all used parameters, including the 'this' parameters. - ParallelAllClassVisitor.ClassVisitorFactory markingUsedParametersClassVisitor = + /*ParallelAllClassVisitor.ClassVisitorFactory markingUsedParametersClassVisitor = new ParallelAllClassVisitor.ClassVisitorFactory() { public ClassVisitor createClassVisitor() @@ -202,12 +202,12 @@ public ClassVisitor createClassVisitor() new ParameterUsageMarker(!methodMarkingStatic, !methodRemovalParameter))); } - }; + };*/ - programClassPool.accept( + /*programClassPool.accept( new TimedClassPoolVisitor("Marking used parameters", new ParallelAllClassVisitor( - markingUsedParametersClassVisitor))); + markingUsedParametersClassVisitor)));*/ // Mark all parameters of referenced methods in methods whose code must // be kept. This prevents shrinking of method descriptors which may not @@ -313,6 +313,7 @@ public void visitClassPool(ClassPool classPool) { configuration.android, configuration.allowAccessModification, true, + false, methodInliningUniqueCounter))))))); System.out.println("Lambda methods inlined: " + methodInliningUniqueCounter.getCount()); } diff --git a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java index 3df531bc3..ec2f69fff 100644 --- a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java +++ b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java @@ -153,48 +153,11 @@ public MethodInliner(boolean microEdition, boolean allowAccessModification, InstructionVisitor extraInlinedInvocationVisitor) { - this(microEdition, - android, - defaultMaxResultingCodeLength(microEdition), - allowAccessModification, - true, - extraInlinedInvocationVisitor); - } - - - /** - * Creates a new MethodInliner. - * - * @param microEdition Indicates whether the resulting code is - * targeted at Java Micro Edition. - * @param android Indicates whether the resulting code is - * targeted at the Dalvik VM. - * @param maxResultingCodeLength Configures the inliner with a max resulting - * code length. - * @param allowAccessModification Indicates whether the access modifiers of - * classes and class members can be changed - * in order to inline methods. - * @param usesOptimizationInfo Indicates whether this inliner needs to perform checks - * that require optimization info. - * @param extraInlinedInvocationVisitor An optional extra visitor for all - * inlined invocation instructions. - */ - public MethodInliner(boolean microEdition, - boolean android, - int maxResultingCodeLength, - boolean allowAccessModification, - boolean usesOptimizationInfo, - InstructionVisitor extraInlinedInvocationVisitor) - { - if (maxResultingCodeLength > MAXIMUM_RESULTING_CODE_LENGTH_JVM) - { - throw new IllegalArgumentException("Maximum resulting code length cannot exceed " + MAXIMUM_RESULTING_CODE_LENGTH_JVM); - } this.microEdition = microEdition; this.android = android; this.maxResultingCodeLength = maxResultingCodeLength; this.allowAccessModification = allowAccessModification; - this.usesOptimizationInfo = usesOptimizationInfo; + this.inlineSingleInvocations = inlineSingleInvocations; this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; } @@ -768,7 +731,9 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM // Only inline the method if its related static initializers don't // have any side effects. - !SideEffectClassChecker.mayHaveSideEffects(targetClass, programClass, programMethod)))) + !SideEffectClassChecker.mayHaveSideEffects(targetClass, + programClass, + programMethod)) { boolean oldInlining = inlining; From 1aeee8553314946b6d22496fb2b17a06d7cd171f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:40:32 +0100 Subject: [PATCH 060/195] Class must extend to be merged Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 1bd8404f8..982a61d9d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -11,6 +11,7 @@ import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; +import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; import proguard.resources.file.ResourceFilePool; import proguard.util.ProcessingFlags; @@ -148,6 +149,8 @@ public static boolean shouldMerge(ProgramClass lambdaClass) { ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); return (lambdaClass.getProcessingFlags() & ProcessingFlags.DONT_OPTIMIZE) == 0 - && optimizationInfo.getLambdaGroup() == null; + && optimizationInfo.getLambdaGroup() == null + && optimizationInfo.mayBeMerged() + && lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA); } } From af673b6cb108d1c8789b4ac15facb87034a345b9 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:41:47 +0100 Subject: [PATCH 061/195] Filter DONT_OPTIMIZE classes before merging + inline lambda groups Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 33 ++++++++++++++++--- 1 file changed, 28 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 982a61d9d..2c07d5106 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -48,6 +48,7 @@ public class KotlinLambdaMerger { private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; + public static MethodInlinerWrapper methodInlinerWrapper; public KotlinLambdaMerger(Configuration configuration) { @@ -82,31 +83,47 @@ public ClassPool execute(final ClassPool programClassPool, // find all lambda classes of arity 0 and with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty - programClassPool.classesAccept(new ImplementedClassFilter(kotlinLambdaClass, false, + programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); + programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, + new ImplementedClassFilter(kotlinLambdaClass, false, new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassPoolFiller(lambdaClassPool), newProgramClassPoolFiller), - newProgramClassPoolFiller) + newProgramClassPoolFiller)) ); // group the lambda's per package PackageGrouper packageGrouper = new PackageGrouper(); lambdaClassPool.classesAccept(packageGrouper); + methodInlinerWrapper = new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool); + // add optimisation info to the lambda's, so that it can be filled out later - lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); + lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter(true)); + lambdaClassPool.classesAccept(new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter(true))); ClassPool lambdaGroupClassPool = new ClassPool(); // merge the lambda's per package - packageGrouper.packagesAccept(new KotlinLambdaClassMerger( + KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger( this.configuration, programClassPool, libraryClassPool, new MultiClassVisitor( new ClassPoolFiller(lambdaGroupClassPool), newProgramClassPoolFiller), - extraDataEntryNameMap)); + extraDataEntryNameMap); + //packageGrouper.packageAccept("androidx/compose/foundation/layout", merger); // ColumnKt + packageGrouper.packagesAccept(merger); + + // inline the helper invoke methods into the general invoke method + inlineMethodsInsideLambdaGroups(newProgramClassPool, libraryClassPool, lambdaGroupClassPool); + + lambdaGroupClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); + + lambdaGroupClassPool.classesAccept(new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter(true))); // initialise the references from and to the newly created lambda groups and their enclosing classes newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, libraryClassPool)); @@ -122,6 +139,12 @@ public ClassPool execute(final ClassPool programClassPool, return programClassPool; } + private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) + { + // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner + lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); + } + private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) { Clazz kotlinLambdaClass = programClassPool.getClass(NAME_KOTLIN_LAMBDA); From 345e34e5faeab692ca335ec60486e5d078e25492 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:43:40 +0100 Subject: [PATCH 062/195] Set optimisation info lambda group of lambda group Modified classes: KotlinLambdaClassMerger --- .../proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 82f2fa104..3b0e7612f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -19,6 +19,7 @@ import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; +import proguard.optimize.info.ProgramClassOptimizationInfo; import java.util.ArrayList; import java.util.HashMap; @@ -67,6 +68,10 @@ public void visitClassPool(ClassPool lambdaClassPool) ProgramClass lambdaGroup = lambdaGroupBuilder.build(); + ProgramClassOptimizationInfo.setProgramClassOptimizationInfo(lambdaGroup); + ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaGroup); + optimizationInfo.setLambdaGroup(lambdaGroup); + // let the lambda group visitor visit the newly created lambda group this.lambdaGroupVisitor.visitProgramClass(lambdaGroup); From f024bbfd4cea0287a5bc697fb56d4f297c8438ef Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:45:43 +0100 Subject: [PATCH 063/195] Add lambda group as dependency of enclosing classes Modified classes: KotlinLambdaClassMerger, KotlinLambdaEnclosingMethodUpdater, kotlinLambdaGroupBuilder --- .../kotlin/KotlinLambdaClassMerger.java | 5 +++-- .../KotlinLambdaEnclosingMethodUpdater.java | 9 +++++++-- .../kotlin/KotlinLambdaGroupBuilder.java | 17 +++++++++++------ 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 3b0e7612f..8b4a98486 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -61,7 +61,8 @@ public void visitClassPool(ClassPool lambdaClassPool) KotlinLambdaGroupBuilder lambdaGroupBuilder = new KotlinLambdaGroupBuilder(lambdaGroupName, this.configuration, this.programClassPool, - this.libraryClassPool); + this.libraryClassPool, + this.extraDataEntryNameMap); // visit each lambda of this package to add their implementations to the lambda group lambdaClassPool.classesAccept(lambdaGroupBuilder); @@ -76,7 +77,7 @@ public void visitClassPool(ClassPool lambdaClassPool) this.lambdaGroupVisitor.visitProgramClass(lambdaGroup); // ensure that this newly created class is part of the resulting output - extraDataEntryNameMap.addExtraClass(lambdaGroup.getName()); + //extraDataEntryNameMap.addExtraClass(lambdaGroup.getName()); } private static String getPackagePrefixOfClasses(ClassPool classPool) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 021428265..5d2595dbd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -20,6 +20,7 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; +import proguard.io.ExtraDataEntryNameMap; import proguard.resources.file.ResourceFilePool; import proguard.shrink.Shrinker; @@ -32,6 +33,7 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private ClassPool libraryClassPool; private final ProgramClass lambdaGroup; private final int classId; + private final ExtraDataEntryNameMap extraDataEntryNameMap; private boolean visitEnclosingMethodAttribute = false; private boolean visitEnclosingMethod = false; private boolean visitEnclosingCode = false; @@ -43,11 +45,13 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, ProgramClass lambdaGroup, - int classId) { + int classId, + ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.lambdaGroup = lambdaGroup; this.classId = classId; + this.extraDataEntryNameMap = extraDataEntryNameMap; } @Override @@ -75,7 +79,8 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr // remove all references to lambda class from the constant pool of its enclosing class enclosingClass.accept(new ConstantPoolShrinker()); - + // ensure that the newly created lambda group is part of the resulting output as a dependency of this enclosing class + this.extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); } @Override diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index d2ea5a4ea..464b69489 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -13,6 +13,7 @@ import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; +import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; @@ -37,6 +38,7 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { private final ClassPool libraryClassPool; private final Map invokeMethodBuilders; private final InterfaceAdder interfaceAdder; + private final ExtraDataEntryNameMap extraDataEntryNameMap; private static final Logger logger = LogManager.getLogger(KotlinLambdaGroupBuilder.class); /** @@ -45,7 +47,8 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { * @param programClassPool a program class pool containing classes that can be referenced by the new lambda group * @param libraryClassPool a library class pool containing classes that can be referenced by the new lambda group */ - public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool) + public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, + final ExtraDataEntryNameMap extraDataEntryNameMap) { this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, libraryClassPool); this.configuration = configuration; @@ -53,6 +56,7 @@ public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configurat this.libraryClassPool = libraryClassPool; this.invokeMethodBuilders = new HashMap<>(); this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); + this.extraDataEntryNameMap = extraDataEntryNameMap; initialiseLambdaGroup(); } @@ -235,11 +239,12 @@ private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) { logger.info("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); - lambdaClass.attributesAccept(new KotlinLambdaEnclosingMethodUpdater( - this.programClassPool, - this.libraryClassPool, - this.classBuilder.getProgramClass(), - lambdaClassId)); + lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, + new KotlinLambdaEnclosingMethodUpdater(this.programClassPool, + this.libraryClassPool, + this.classBuilder.getProgramClass(), + lambdaClassId, + this.extraDataEntryNameMap)); } private void addInitConstructors() From 99ae022dc847192bdf32bb4cb94423b358a13c64 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:46:23 +0100 Subject: [PATCH 064/195] Don't inline lambda group methods right after creation Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 464b69489..91ad8b55f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -140,9 +140,6 @@ public void visitProgramClass(ProgramClass lambdaClass) { } int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); - // inline the helper invoke methods into the general invoke method - inlineMethodsInsideClass(lambdaClass); - // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId); From 5cde21edf15a635232b51545f14ff414a9c59b24 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:47:11 +0100 Subject: [PATCH 065/195] Don't visit inner lambda's that have wrong constructor Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 91ad8b55f..d3e8a04dd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -122,9 +122,11 @@ public void visitProgramClass(ProgramClass lambdaClass) { new AllInnerClassesInfoVisitor( new InnerClassInfoClassConstantVisitor( new ClassConstantToClassVisitor( + new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, - (ClassVisitor)this)), // don't revisit the current lambda + (ClassVisitor)this), + null)), // don't revisit the current lambda null))); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); From 44032ffc956ab1cf1998f2a480b1190f821d524f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:47:51 +0100 Subject: [PATCH 066/195] Set optimisation info of lambda before visiting inner classes Modified classes: KotlinLambdaGroupBuilder --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index d3e8a04dd..ac9aacdf3 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -110,9 +110,9 @@ public void visitProgramClass(ProgramClass lambdaClass) { return; } - // Add interfaces of lambda class to the lambda group - // TODO: ensure that only Function interfaces are added - lambdaClass.interfaceConstantsAccept(this.interfaceAdder); + // update optimisation info of lambda to show lambda has been merged or is going to be merged + ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); + optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); // TODO: check whether lambda class has inner lambda's // if so, visit inner lambda's first @@ -129,6 +129,10 @@ public void visitProgramClass(ProgramClass lambdaClass) { null)), // don't revisit the current lambda null))); + // Add interfaces of lambda class to the lambda group + // TODO: ensure that only Function interfaces are added + lambdaClass.interfaceConstantsAccept(this.interfaceAdder); + ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); @@ -146,8 +150,8 @@ public void visitProgramClass(ProgramClass lambdaClass) { updateLambdaInstantiationSite(lambdaClass, lambdaClassId); // update optimisation info of lambda to show lambda has been merged - ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); - optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); + /*ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); + optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass());*/ } private void inlineMethodsInsideClass(ProgramClass lambdaClass) From 781f9462591c532aa0ff87fb5c679ff67788fce8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 10:49:03 +0100 Subject: [PATCH 067/195] Use static method inliner for lambda groups Modified classes: KotlinLambdaGroupBuilder --- .../java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index ac9aacdf3..77506a069 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -158,7 +158,7 @@ private void inlineMethodsInsideClass(ProgramClass lambdaClass) { // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner ClassPool lambdaClassPool = new ClassPool(lambdaClass); - lambdaClassPool.accept(new MethodInlinerWrapper(this.configuration, this.programClassPool, this.libraryClassPool)); + lambdaClassPool.accept(KotlinLambdaMerger.methodInlinerWrapper); } private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) From 90852c140af34d66273203d297d226099c0db99d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 14:55:11 +0100 Subject: [PATCH 068/195] Method inlining within class Modified classes: MethodInlinerWrapper New classes: SameClassMethodInliner --- .../optimize/MethodInlinerWrapper.java | 11 ++- .../peephole/SameClassMethodInliner.java | 82 +++++++++++++++++++ 2 files changed, 87 insertions(+), 6 deletions(-) create mode 100644 base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java diff --git a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java index cd4f3c6f1..d05c3bc2a 100644 --- a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java +++ b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java @@ -14,6 +14,7 @@ import proguard.classfile.visitor.*; import proguard.optimize.info.*; import proguard.optimize.peephole.MethodInliner; +import proguard.optimize.peephole.SameClassMethodInliner; import proguard.util.*; public class MethodInlinerWrapper implements ClassPoolVisitor { @@ -309,12 +310,10 @@ public void visitClassPool(ClassPool classPool) { new AllAttributeVisitor( new DebugAttributeVisitor("Inlining single methods", new OptimizationCodeAttributeFilter( - new MethodInliner(configuration.microEdition, - configuration.android, - configuration.allowAccessModification, - true, - false, - methodInliningUniqueCounter))))))); + new SameClassMethodInliner(configuration.microEdition, + configuration.android, + configuration.allowAccessModification, + methodInliningUniqueCounter))))))); System.out.println("Lambda methods inlined: " + methodInliningUniqueCounter.getCount()); } } diff --git a/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java b/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java new file mode 100644 index 000000000..bd3babca8 --- /dev/null +++ b/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java @@ -0,0 +1,82 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.peephole; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.optimize.info.MethodInvocationMarker; + +/** + * This AttributeVisitor inlines methods that are only invoked once, in the code attributes that it visits. + */ +public class SameClassMethodInliner extends MethodInliner { + + private Clazz startClass; + + public SameClassMethodInliner(boolean microEdition, + boolean android, + boolean allowAccessModification) + { + super(microEdition, android, allowAccessModification); + } + + public SameClassMethodInliner(boolean microEdition, + boolean android, + boolean allowAccessModification, + InstructionVisitor extraInlinedInvocationVisitor) + { + super(microEdition, android, allowAccessModification, extraInlinedInvocationVisitor); + } + + // Implementations for MethodInliner. + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + boolean startClassWasNull = this.startClass == null; + if (clazz == this.startClass || this.startClass == null) { + this.startClass = clazz; + super.visitCodeAttribute(clazz, method, codeAttribute); + } + if (startClassWasNull) + { + this.startClass = null; + } + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + if (programClass == this.startClass) { + super.visitProgramMethod(programClass, programMethod); + } + } + + @Override + protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + return clazz == this.startClass; + } +} From d23de15a0ca00d275737d21acdfdc7b594116717 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Fri, 7 Jan 2022 15:01:13 +0100 Subject: [PATCH 069/195] Remove unused imports Modified classes: KotlinLambdaMergerTest --- .../kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 189b6e171..6aa381584 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -22,10 +22,8 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual -import io.kotest.matchers.shouldBe import io.mockk.* import proguard.Configuration import proguard.classfile.ClassConstants From 031193eacd3f5b0a964b7c6d034a23c80eec24d6 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:35:51 +0100 Subject: [PATCH 070/195] Set INJECTED flag on newly built lambda groups Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 77506a069..0d6f856a1 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -16,6 +16,7 @@ import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; +import proguard.util.ProcessingFlags; import java.util.HashMap; import java.util.Map; @@ -272,6 +273,8 @@ public ProgramClass build() // addInitConstructors(); addInvokeMethods(); - return this.classBuilder.getProgramClass(); + ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); + lambdaGroup.setProcessingFlags(lambdaGroup.getProcessingFlags() | ProcessingFlags.INJECTED); + return lambdaGroup; } } From 6e519f05cfaba05373456773abed8304a84da87c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:37:21 +0100 Subject: [PATCH 071/195] Use SameClassMethodInliner to inline invoke methods within lambda class Modified classes: KotlinLambdaGroupBuilder --- .../kotlin/KotlinLambdaGroupBuilder.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 0d6f856a1..cc6540d44 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -5,6 +5,7 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; @@ -16,6 +17,9 @@ import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; +import proguard.optimize.info.ProgramClassOptimizationInfoSetter; +import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; +import proguard.optimize.peephole.SameClassMethodInliner; import proguard.util.ProcessingFlags; import java.util.HashMap; @@ -157,9 +161,16 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void inlineMethodsInsideClass(ProgramClass lambdaClass) { - // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner - ClassPool lambdaClassPool = new ClassPool(lambdaClass); - lambdaClassPool.accept(KotlinLambdaMerger.methodInlinerWrapper); + lambdaClass.accept(new ProgramClassOptimizationInfoSetter()); + + lambdaClass.accept(new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter())); + + lambdaClass.accept(new AllMethodVisitor( + new AllAttributeVisitor( + new SameClassMethodInliner(configuration.microEdition, + configuration.android, + configuration.allowAccessModification)))); } private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) From d9b384ade07329b2c4153dbc366a93a6e3cbc010 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:39:19 +0100 Subject: [PATCH 072/195] After merging: shrink Lambda Groups instead of all classes Modified classes: KotlinLambdaMerger, ProGuard --- base/src/main/java/proguard/ProGuard.java | 1 - .../optimize/kotlin/KotlinLambdaMerger.java | 34 +++++++++++++------ 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index ef9f4d201..397ab7c7a 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -430,7 +430,6 @@ private void mergeKotlinLambdaClasses() throws IOException { libraryClassPool, resourceFilePool, extraDataEntryNameMap); - shrink(); } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 2c07d5106..78f4151b2 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -5,7 +5,6 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.util.ClassInitializer; -import proguard.classfile.util.ClassSubHierarchyInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.MethodInlinerWrapper; @@ -13,10 +12,13 @@ import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; import proguard.resources.file.ResourceFilePool; +import proguard.shrink.ClassShrinker; +import proguard.shrink.ClassUsageMarker; +import proguard.shrink.SimpleUsageMarker; +import proguard.shrink.UsageMarker; import proguard.util.ProcessingFlags; import java.io.IOException; -import java.util.Objects; public class KotlinLambdaMerger { @@ -117,18 +119,16 @@ public ClassPool execute(final ClassPool programClassPool, //packageGrouper.packageAccept("androidx/compose/foundation/layout", merger); // ColumnKt packageGrouper.packagesAccept(merger); + // initialise the references from and to the newly created lambda groups and their enclosing classes + newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, libraryClassPool)); + // inline the helper invoke methods into the general invoke method inlineMethodsInsideLambdaGroups(newProgramClassPool, libraryClassPool, lambdaGroupClassPool); - lambdaGroupClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); - - lambdaGroupClassPool.classesAccept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter(true))); + // remove the unused helper methods from the lambda groups + shrinkLambdaGroups(newProgramClassPool, libraryClassPool, resourceFilePool, lambdaGroupClassPool); - // initialise the references from and to the newly created lambda groups and their enclosing classes - newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, libraryClassPool)); - - logger.info("{} lambda class(es) found.", lambdaClassPool.size()); + logger.info("{} lambda class(es) found that can be merged.", lambdaClassPool.size()); logger.info("{} lambda group(s) created.", lambdaGroupClassPool.size()); logger.info("#lambda groups/#lambda classes ratio = {}/{} = {}%", lambdaGroupClassPool.size(), lambdaClassPool.size(), 100 * lambdaGroupClassPool.size() / lambdaClassPool.size()); logger.info("Size of original program class pool: {}", programClassPool.size()); @@ -143,6 +143,20 @@ private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPo { // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); + private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, ClassPool lambdaGroupClassPool) + { + SimpleUsageMarker simpleUsageMarker = new SimpleUsageMarker(); + ClassUsageMarker classUsageMarker = new ClassUsageMarker(simpleUsageMarker); + + // make sure that the used methods of the lambda groups are marked as used + // note: if -dontshrink is + new UsageMarker(configuration).mark(programClassPool, + libraryClassPool, + resourceFilePool, + simpleUsageMarker, + classUsageMarker); + // remove the unused parts of the lambda groups, such as the inlined invoke helper methods + lambdaGroupClassPool.classesAccept(new ClassShrinker(simpleUsageMarker)); } private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) From 35736d4e45d1086d1f82a23d29fe6a44eda1ae74 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:44:09 +0100 Subject: [PATCH 073/195] Use SameClassMethodInliner to inline invoke helper methods within LG's Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 78f4151b2..38ec88614 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -3,7 +3,12 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.Configuration; -import proguard.classfile.*; +import proguard.classfile.ClassConstants; +import proguard.classfile.ClassPool; +import proguard.classfile.Clazz; +import proguard.classfile.ProgramClass; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.util.ClassInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; @@ -11,6 +16,7 @@ import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; +import proguard.optimize.peephole.SameClassMethodInliner; import proguard.resources.file.ResourceFilePool; import proguard.shrink.ClassShrinker; import proguard.shrink.ClassUsageMarker; @@ -50,7 +56,6 @@ public class KotlinLambdaMerger { private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; - public static MethodInlinerWrapper methodInlinerWrapper; public KotlinLambdaMerger(Configuration configuration) { @@ -98,8 +103,6 @@ public ClassPool execute(final ClassPool programClassPool, PackageGrouper packageGrouper = new PackageGrouper(); lambdaClassPool.classesAccept(packageGrouper); - methodInlinerWrapper = new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool); - // add optimisation info to the lambda's, so that it can be filled out later lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter(true)); lambdaClassPool.classesAccept(new AllMemberVisitor( @@ -141,8 +144,21 @@ public ClassPool execute(final ClassPool programClassPool, private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) { - // TODO: use a simpler, more reliable, specific method inliner instead of the current wrapper around the MethodInliner - lambdaGroupClassPool.accept(new MethodInlinerWrapper(this.configuration, programClassPool, libraryClassPool)); + InstructionCounter methodInliningCounter = new InstructionCounter(); + + lambdaGroupClassPool.classesAccept(new MultiClassVisitor( + new ProgramClassOptimizationInfoSetter(), + new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter()), + new AllMethodVisitor( + new AllAttributeVisitor( + new SameClassMethodInliner(configuration.microEdition, + configuration.android, + configuration.allowAccessModification, + methodInliningCounter))))); + logger.debug("{} methods inlined inside lambda groups.", methodInliningCounter.getCount()); + } + private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, ClassPool lambdaGroupClassPool) { SimpleUsageMarker simpleUsageMarker = new SimpleUsageMarker(); From 4fd009415a031ce571a56a7c14baa72a384d4c1d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:45:27 +0100 Subject: [PATCH 074/195] Don't require Function0 to start merging Modified classes: KotlinLambdaMerger --- .../proguard/optimize/kotlin/KotlinLambdaMerger.java | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 38ec88614..e1d6cbaca 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -74,20 +74,17 @@ public ClassPool execute(final ClassPool programClassPool, // get the Lambda class and the Function0 interface Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); - Clazz kotlinFunction0Interface = getKotlinFunction0Interface(programClassPool, libraryClassPool); if (kotlinLambdaClass == null) { logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_LAMBDA); } - if (kotlinFunction0Interface == null) { - logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_FUNCTION0); - } - - if (kotlinLambdaClass != null && kotlinFunction0Interface != null) { + else + { // A class pool where the applicable lambda's will be stored ClassPool lambdaClassPool = new ClassPool(); ClassPool newProgramClassPool = new ClassPool(); ClassPoolFiller newProgramClassPoolFiller = new ClassPoolFiller(newProgramClassPool); - // find all lambda classes of arity 0 and with an empty closure + + // find all lambda classes with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); From ed1e9624dde30ae727a8efe6e2ba4474731e2383 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:46:35 +0100 Subject: [PATCH 075/195] Set OptimizationInfo, if necessary, before lambda merging Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index e1d6cbaca..c009f5c37 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -101,9 +101,9 @@ public ClassPool execute(final ClassPool programClassPool, lambdaClassPool.classesAccept(packageGrouper); // add optimisation info to the lambda's, so that it can be filled out later - lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter(true)); + lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); lambdaClassPool.classesAccept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter(true))); + new ProgramMemberOptimizationInfoSetter())); ClassPool lambdaGroupClassPool = new ClassPool(); From 76b6eb9e6d06d19f32e38df094d3819f27bd22b6 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 08:47:21 +0100 Subject: [PATCH 076/195] Code outlining Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index c009f5c37..e88889dcc 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -108,15 +108,13 @@ public ClassPool execute(final ClassPool programClassPool, ClassPool lambdaGroupClassPool = new ClassPool(); // merge the lambda's per package - KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger( - this.configuration, - programClassPool, - libraryClassPool, - new MultiClassVisitor( - new ClassPoolFiller(lambdaGroupClassPool), - newProgramClassPoolFiller), - extraDataEntryNameMap); - //packageGrouper.packageAccept("androidx/compose/foundation/layout", merger); // ColumnKt + KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger(this.configuration, + programClassPool, + libraryClassPool, + new MultiClassVisitor( + new ClassPoolFiller(lambdaGroupClassPool), + newProgramClassPoolFiller), + extraDataEntryNameMap); packageGrouper.packagesAccept(merger); // initialise the references from and to the newly created lambda groups and their enclosing classes From ef1629bb5b9549a7bc18fbe13948392b651c6268 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 10:35:20 +0100 Subject: [PATCH 077/195] Apply the LineNumberLinearizer to all lambda groups after inlining Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index e88889dcc..cf6713cfe 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -16,6 +16,7 @@ import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; +import proguard.optimize.peephole.LineNumberLinearizer; import proguard.optimize.peephole.SameClassMethodInliner; import proguard.resources.file.ResourceFilePool; import proguard.shrink.ClassShrinker; @@ -167,7 +168,10 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla simpleUsageMarker, classUsageMarker); // remove the unused parts of the lambda groups, such as the inlined invoke helper methods - lambdaGroupClassPool.classesAccept(new ClassShrinker(simpleUsageMarker)); + // and make sure that the line numbers are updated + lambdaGroupClassPool.classesAccept(new MultiClassVisitor( + new ClassShrinker(simpleUsageMarker), + new LineNumberLinearizer())); } private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) From 5ff81d5d608a0b057d988db22bad19659b2b188b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 11:14:23 +0100 Subject: [PATCH 078/195] Make lambda group name public field Modified classes: KotlinLambdaClassMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 8b4a98486..cb3471df7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -28,7 +28,7 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor { - private static final String NAME_LAMBDA_GROUP = "LambdaGroup"; + public static final String NAME_LAMBDA_GROUP = "LambdaGroup"; private final ClassVisitor lambdaGroupVisitor; private final Configuration configuration; private final ClassPool programClassPool; From e1acdf2cb20454f7e9e7a8d844980888b4f69d2a Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 11:15:28 +0100 Subject: [PATCH 079/195] Mark all non-LG classes and methods as used before shrinking LG's Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index cf6713cfe..d074cec9d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -161,12 +161,22 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla ClassUsageMarker classUsageMarker = new ClassUsageMarker(simpleUsageMarker); // make sure that the used methods of the lambda groups are marked as used + // by marking all classes and methods // note: if -dontshrink is - new UsageMarker(configuration).mark(programClassPool, + /*new UsageMarker(configuration).mark(programClassPool, libraryClassPool, resourceFilePool, simpleUsageMarker, - classUsageMarker); + classUsageMarker);*/ + libraryClassPool.classesAccept(classUsageMarker); + // but don't mark the lambda groups if they are not used + programClassPool.classesAccept(new ClassNameFilter( + "**/" + KotlinLambdaClassMerger.NAME_LAMBDA_GROUP, + (ClassVisitor) null, + new MultiClassVisitor( + classUsageMarker, + new AllMemberVisitor( + classUsageMarker)))); // remove the unused parts of the lambda groups, such as the inlined invoke helper methods // and make sure that the line numbers are updated lambdaGroupClassPool.classesAccept(new MultiClassVisitor( From db8a8cd97168393f1c14e38e002aacf57a422d43 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 12:50:46 +0100 Subject: [PATCH 080/195] Keep interfaces of lambda groups Modified classes: KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMerger.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d074cec9d..3e7ce5ee5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -19,10 +19,7 @@ import proguard.optimize.peephole.LineNumberLinearizer; import proguard.optimize.peephole.SameClassMethodInliner; import proguard.resources.file.ResourceFilePool; -import proguard.shrink.ClassShrinker; -import proguard.shrink.ClassUsageMarker; -import proguard.shrink.SimpleUsageMarker; -import proguard.shrink.UsageMarker; +import proguard.shrink.*; import proguard.util.ProcessingFlags; import java.io.IOException; @@ -169,7 +166,8 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla simpleUsageMarker, classUsageMarker);*/ libraryClassPool.classesAccept(classUsageMarker); - // but don't mark the lambda groups if they are not used + // but don't mark the lambda groups and their members in case they are not used, + // e.g. inlined helper invoke methods programClassPool.classesAccept(new ClassNameFilter( "**/" + KotlinLambdaClassMerger.NAME_LAMBDA_GROUP, (ClassVisitor) null, @@ -177,6 +175,11 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla classUsageMarker, new AllMemberVisitor( classUsageMarker)))); + + // ensure that the interfaces of the lambda group are not removed + lambdaGroupClassPool.classesAccept(new InterfaceUsageMarker( + classUsageMarker)); + // remove the unused parts of the lambda groups, such as the inlined invoke helper methods // and make sure that the line numbers are updated lambdaGroupClassPool.classesAccept(new MultiClassVisitor( From 9a5703e3d3cef7b8cd05d5ce674e05a03b50cd0b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 16:48:43 +0100 Subject: [PATCH 081/195] Don't try to merge classes that don't have optimisation info Modified classes: KotlinLambdaMerger --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 3e7ce5ee5..7b8e1ccbc 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -213,9 +213,10 @@ private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool public static boolean shouldMerge(ProgramClass lambdaClass) { ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); - return (lambdaClass.getProcessingFlags() & ProcessingFlags.DONT_OPTIMIZE) == 0 + return (lambdaClass.getProcessingFlags() & (ProcessingFlags.DONT_OPTIMIZE | ProcessingFlags.DONT_SHRINK)) == 0 + && lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA) + && optimizationInfo != null // if optimisation info is null, then the lambda was not enqueued to be merged && optimizationInfo.getLambdaGroup() == null - && optimizationInfo.mayBeMerged() - && lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA); + && optimizationInfo.mayBeMerged(); } } From f3ad14fe5f63bf9e73b6676a95df177d36156365 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 16:50:19 +0100 Subject: [PATCH 082/195] Use code patterns to replace instructions in enclosing method Modified classes: KotlinLambdaEnclosingMethodUpdater --- .../KotlinLambdaEnclosingMethodUpdater.java | 216 +++++------------- 1 file changed, 57 insertions(+), 159 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 5d2595dbd..3c219c6c4 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -2,7 +2,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; @@ -11,26 +10,23 @@ import proguard.classfile.constant.Constant; import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.CodeAttributeEditor; -import proguard.classfile.editor.ConstantPoolShrinker; -import proguard.classfile.editor.InnerClassesAttributeEditor; -import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.editor.*; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.visitor.ClassPrinter; +import proguard.classfile.kotlin.KotlinConstants; +import proguard.classfile.util.BranchTargetFinder; import proguard.classfile.visitor.MemberVisitor; +import proguard.configuration.ConfigurationLoggingAdder; +import proguard.configuration.ConfigurationLoggingInstructionSequencesReplacer; import proguard.io.ExtraDataEntryNameMap; -import proguard.resources.file.ResourceFilePool; -import proguard.shrink.Shrinker; -import java.io.IOException; import java.util.*; -public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, MemberVisitor, InstructionVisitor { +public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, MemberVisitor { - private ClassPool programClassPool; - private ClassPool libraryClassPool; + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; private final ProgramClass lambdaGroup; private final int classId; private final ExtraDataEntryNameMap extraDataEntryNameMap; @@ -40,7 +36,6 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private static final Logger logger = LogManager.getLogger(KotlinLambdaEnclosingMethodUpdater.class); private Clazz currentLambdaClass; private SortedSet offsetsWhereLambdaIsReferenced; - private int lambdaConstantIndex; public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, @@ -55,10 +50,7 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, } @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) - { - - } + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttribute enclosingMethodAttribute) { @@ -75,10 +67,12 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr visitEnclosingMethodAttribute = false; // remove lambda class as inner class of its enclosing class - enclosingClass.attributeAccept("InnerClasses", new InnerClassRemover(lambdaClass)); + enclosingClass.attributeAccept(Attribute.INNER_CLASSES, + new InnerClassRemover(lambdaClass)); // remove all references to lambda class from the constant pool of its enclosing class enclosingClass.accept(new ConstantPoolShrinker()); + // ensure that the newly created lambda group is part of the resulting output as a dependency of this enclosing class this.extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); } @@ -90,39 +84,11 @@ public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclos if (!visitEnclosingMethodAttribute || visitEnclosingMethod) { return; } - this.lambdaConstantIndex = findConstantIndexOfLambdaInEnclosingClass(this.currentLambdaClass, enclosingClass); - if (this.lambdaConstantIndex < 0) - { - // the lambda class is not used: this should not happen - logger.warn("Lambda class {} not found in constant pool of class {}", this.currentLambdaClass, enclosingClass); - return; - } - this.offsetsWhereLambdaIsReferenced = new TreeSet<>(); visitEnclosingMethod = true; enclosingMethod.attributesAccept(enclosingClass, this); visitEnclosingMethod = false; } - private int findConstantIndexOfLambdaInEnclosingClass(Clazz lambdaClass, ProgramClass enclosingClass) - { - ClassConstantFinder finder = new ClassConstantFinder(lambdaClass); - logger.info("{} constants in constant pool of class {}", enclosingClass.u2constantPoolCount, enclosingClass); - for (int index = 0; index < enclosingClass.u2constantPoolCount; index++) - { - try { - enclosingClass.constantPoolEntryAccept(index, finder); - if (finder.isClassFound()) { - return index; - } - } - catch (NullPointerException exception) - { - logger.error("Could not read constant at index {} in constant pool of class {}", index, enclosingClass); - } - } - return -1; - } - @Override public void visitCodeAttribute(Clazz enclosingClass, Method enclosingMethod, CodeAttribute codeAttribute) { @@ -149,134 +115,66 @@ public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclos return; } visitEnclosingCode = true; - // find the offsets where the lambda class is instantiated or loaded or referenced - codeAttribute.instructionsAccept(enclosingClass, enclosingMethod, this); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); - // enclosing method { - // ... - // Function0 lambda = new LambdaGroup(classId); - // ... - // } - // create instructions to instantiate the lambda group with the correct id - InstructionSequenceBuilder builder = new InstructionSequenceBuilder(enclosingClass, - this.programClassPool, - this.libraryClassPool); - Instruction[] instantiateLambdaGroupInstructions = builder - .new_(lambdaGroup) - .dup() - .iconst(this.classId) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V") - .instructions(); - - codeAttributeEditor.reset(codeAttribute.u4codeLength); - - logger.info("Amount of offsets where {} is referenced in {}: {}", this.currentLambdaClass, enclosingClass, this.offsetsWhereLambdaIsReferenced.size()); - for (int offset : this.offsetsWhereLambdaIsReferenced) - { - codeAttributeEditor.replaceInstruction(offset, instantiateLambdaGroupInstructions); - } + BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); - codeAttributeEditor.visitCodeAttribute(enclosingClass, enclosingMethod, codeAttribute); - - logger.debug("Updated enclosing class after modifying enclosing method code:"); - //enclosingClass.accept(new ClassPrinter()); + InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor); + codeAttribute.accept(enclosingClass, + enclosingMethod, + new PeepholeEditor(branchTargetFinder, + codeAttributeEditor, + replacer)); visitEnclosingCode = false; } - @Override - public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) + private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTargetFinder branchTargetFinder, + CodeAttributeEditor codeAttributeEditor) { + InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClassPool, libraryClassPool); + Instruction[][][] replacementPatterns = createReplacementPatternsForLambda(builder); + return new InstructionSequencesReplacer(builder.constants(), + replacementPatterns, + branchTargetFinder, + codeAttributeEditor); } - @Override - public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - try - { - visitConstantInstruction((ProgramClass) clazz, (ProgramMethod) method, codeAttribute, offset, constantInstruction); - } - catch (ClassCastException exception) - { - logger.error(exception); - } - } - - public void visitConstantInstruction(ProgramClass enclosingClass, ProgramMethod enclosingMethod, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) - { - // check if constant instruction refers to constant in constant pool that is related to the lambda class: the lambda itself, a field in the lambda class - /* we expect: - new LambdaClass - dup - invokespecial LambdaClass.(...)V - ... - or: - getfield LambdaClass.INSTANCE:LambdaClass - */ - - // note offset of this instruction, so it can be replaced later - //logger.info("ConstantInstruction {}", constantInstruction); - //logger.info("Lambda constant index: {}", this.lambdaConstantIndex); - if (constantReferencesClass(enclosingClass.getConstant(constantInstruction.constantIndex), this.currentLambdaClass)) - { - this.offsetsWhereLambdaIsReferenced.add(offset); - } - } - - private boolean constantReferencesClass(Constant constant, Clazz clazz) - { - try - { - return constantReferencesClass((RefConstant) constant, clazz); - } - catch (ClassCastException exception) - { - return false; - } - } - - private boolean constantReferencesClass(RefConstant refConstant, Clazz clazz) - { - return refConstant.referencedClass != null && (refConstant.referencedClass == clazz || Objects.equals(refConstant.referencedClass.getName(), clazz.getName())); - } -} - -class ClassConstantFinder implements ConstantVisitor -{ - private Clazz classToBeFound; - private boolean classFound = false; - private static final Logger logger = LogManager.getLogger(ClassConstantFinder.class); - - public ClassConstantFinder(Clazz classToBeFound) - { - this.classToBeFound = classToBeFound; - } - - @Override - public void visitAnyConstant(Clazz clazz, Constant constant) - { - } - - @Override - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) - { - //logger.info("Visiting class constant {}, referencing class {}", classConstant, classConstant.referencedClass); - if (classConstant.referencedClass == this.classToBeFound || Objects.equals(classConstant.referencedClass.getName(), this.classToBeFound.getName())) - { - this.classFound = true; - } - } - - public boolean isClassFound() + private Instruction[][][] createReplacementPatternsForLambda(InstructionSequenceBuilder builder) { - return this.classFound; + Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, "()V"); + return new Instruction[][][] + { + { + // Lambda is 'instantiated' by referring to its static INSTANCE field + builder.getstatic(currentLambdaClass.getName(), + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, + "L" + currentLambdaClass.getName() + ";").__(), + + builder.new_(lambdaGroup) + .dup() + .iconst(classId) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V").__() + }, + { + // Lambda is explicitly instantiated + builder.new_(currentLambdaClass) + .dup() + .invokespecial(currentLambdaClass, initMethod).__(), + + builder.new_(lambdaGroup) + .dup() + .iconst(classId) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V").__() + } + }; } } class InnerClassRemover implements AttributeVisitor, InnerClassesInfoVisitor { private final Clazz classToBeRemoved; - private Set innerClassesEntriesToBeRemoved = new HashSet<>(); + private final Set innerClassesEntriesToBeRemoved = new HashSet<>(); private static final Logger logger = LogManager.getLogger(InnerClassRemover.class); public InnerClassRemover(Clazz clazz) @@ -292,7 +190,7 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) { public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { innerClassesAttribute.innerClassEntriesAccept(clazz, this); InnerClassesAttributeEditor editor = new InnerClassesAttributeEditor(innerClassesAttribute); - logger.info("{} inner class entries are removed from class {}", innerClassesEntriesToBeRemoved.size(), clazz); + logger.trace("{} inner class entries are removed from class {}", innerClassesEntriesToBeRemoved.size(), clazz); for (InnerClassesInfo entry : innerClassesEntriesToBeRemoved) { editor.removeInnerClassesInfo(entry); @@ -304,7 +202,7 @@ public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo String innerClassName = clazz.getClassName(innerClassesInfo.u2innerClassIndex); if (Objects.equals(innerClassName, this.classToBeRemoved.getName())) { - logger.info("Removing inner classes entry of class {} enqueued to be removed from class {}", innerClassName, clazz); + logger.trace("Removing inner classes entry of class {} enqueued to be removed from class {}", innerClassName, clazz); innerClassesEntriesToBeRemoved.add(innerClassesInfo); } } From e147b4bbd49426c8712d0cf9a72ae1bec84c0664 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 18:16:08 +0100 Subject: [PATCH 083/195] Keep track of lambda's that are not merged + don't merge single lambda's Modified classes: KotlinLambdaMerger, KotlinLambdaClassMerger, KotlinLambdaGroupBuilder --- .../kotlin/KotlinLambdaClassMerger.java | 25 ++++++--- .../kotlin/KotlinLambdaGroupBuilder.java | 52 +++++++++++++------ .../optimize/kotlin/KotlinLambdaMerger.java | 12 ++++- 3 files changed, 64 insertions(+), 25 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index cb3471df7..044f54fb0 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -30,6 +30,7 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor { public static final String NAME_LAMBDA_GROUP = "LambdaGroup"; private final ClassVisitor lambdaGroupVisitor; + private final ClassVisitor notMergedLambdaVisitor; private final Configuration configuration; private final ClassPool programClassPool; private final ClassPool libraryClassPool; @@ -40,18 +41,29 @@ public KotlinLambdaClassMerger(final Configuration configuration, final ClassPool programClassPool, final ClassPool libraryClassPool, final ClassVisitor lambdaGroupVisitor, + final ClassVisitor notMergedLambdaVisitor, final ExtraDataEntryNameMap extraDataEntryNameMap) { - this.configuration = configuration; - this.programClassPool = programClassPool; - this.libraryClassPool = libraryClassPool; - this.lambdaGroupVisitor = lambdaGroupVisitor; - this.extraDataEntryNameMap = extraDataEntryNameMap; + this.configuration = configuration; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.lambdaGroupVisitor = lambdaGroupVisitor; + this.notMergedLambdaVisitor = notMergedLambdaVisitor; + this.extraDataEntryNameMap = extraDataEntryNameMap; } @Override public void visitClassPool(ClassPool lambdaClassPool) { + // don't merge lambda's if there is only one or zero + if (lambdaClassPool.size() < 2) + { + if (notMergedLambdaVisitor != null) { + lambdaClassPool.classesAccept(notMergedLambdaVisitor); + } + return; + } + // choose a name for the lambda group // ensure that the lambda group is in the same package as the classes of the class pool String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + NAME_LAMBDA_GROUP; @@ -62,7 +74,8 @@ public void visitClassPool(ClassPool lambdaClassPool) this.configuration, this.programClassPool, this.libraryClassPool, - this.extraDataEntryNameMap); + this.extraDataEntryNameMap, + this.notMergedLambdaVisitor); // visit each lambda of this package to add their implementations to the lambda group lambdaClassPool.classesAccept(lambdaGroupBuilder); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index cc6540d44..603e6adf9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -41,6 +41,7 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { private final Configuration configuration; private final ClassPool programClassPool; private final ClassPool libraryClassPool; + private final ClassVisitor notMergedLambdaVisitor; private final Map invokeMethodBuilders; private final InterfaceAdder interfaceAdder; private final ExtraDataEntryNameMap extraDataEntryNameMap; @@ -52,16 +53,21 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { * @param programClassPool a program class pool containing classes that can be referenced by the new lambda group * @param libraryClassPool a library class pool containing classes that can be referenced by the new lambda group */ - public KotlinLambdaGroupBuilder(String lambdaGroupName, Configuration configuration, ClassPool programClassPool, ClassPool libraryClassPool, - final ExtraDataEntryNameMap extraDataEntryNameMap) + public KotlinLambdaGroupBuilder(final String lambdaGroupName, + final Configuration configuration, + final ClassPool programClassPool, + final ClassPool libraryClassPool, + final ExtraDataEntryNameMap extraDataEntryNameMap, + final ClassVisitor notMergedLambdaVisitor) { - this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, libraryClassPool); - this.configuration = configuration; - this.programClassPool = programClassPool; - this.libraryClassPool = libraryClassPool; - this.invokeMethodBuilders = new HashMap<>(); - this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); - this.extraDataEntryNameMap = extraDataEntryNameMap; + this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, libraryClassPool); + this.configuration = configuration; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.invokeMethodBuilders = new HashMap<>(); + this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); + this.extraDataEntryNameMap = extraDataEntryNameMap; + this.notMergedLambdaVisitor = notMergedLambdaVisitor; initialiseLambdaGroup(); } @@ -115,23 +121,35 @@ public void visitProgramClass(ProgramClass lambdaClass) { return; } + try + { + mergeLambdaClass(lambdaClass); + } + catch(Exception exception) + { + logger.error("Lambda class {} could not be merged: {}", lambdaClass, exception); + if (this.notMergedLambdaVisitor != null) { + lambdaClass.accept(this.notMergedLambdaVisitor); + } + } + } + + private void mergeLambdaClass(ProgramClass lambdaClass) + { // update optimisation info of lambda to show lambda has been merged or is going to be merged ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); - // TODO: check whether lambda class has inner lambda's - // if so, visit inner lambda's first - // TODO: ensure that visited inner classes are lambda's - // TODO: ensure that visited inner classes are lambda's of the expected type (arity, closure size, free variable types) + // merge any inner lambda's before merging the current lambda lambdaClass.attributeAccept(Attribute.INNER_CLASSES, new AllInnerClassesInfoVisitor( new InnerClassInfoClassConstantVisitor( new ClassConstantToClassVisitor( new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, - new ClassNameFilter(lambdaClass.getName(), - (ClassVisitor)null, - (ClassVisitor)this), - null)), // don't revisit the current lambda + new ClassNameFilter(lambdaClass.getName(), + (ClassVisitor)null, + (ClassVisitor)this), + null)), // don't revisit the current lambda null))); // Add interfaces of lambda class to the lambda group diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 7b8e1ccbc..a3dce7558 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -104,6 +104,7 @@ public ClassPool execute(final ClassPool programClassPool, new ProgramMemberOptimizationInfoSetter())); ClassPool lambdaGroupClassPool = new ClassPool(); + ClassPool notMergedLambdaClassPool = new ClassPool(); // merge the lambda's per package KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger(this.configuration, @@ -112,6 +113,9 @@ public ClassPool execute(final ClassPool programClassPool, new MultiClassVisitor( new ClassPoolFiller(lambdaGroupClassPool), newProgramClassPoolFiller), + new MultiClassVisitor( + new ClassPoolFiller(notMergedLambdaClassPool), + newProgramClassPoolFiller), extraDataEntryNameMap); packageGrouper.packagesAccept(merger); @@ -124,9 +128,13 @@ public ClassPool execute(final ClassPool programClassPool, // remove the unused helper methods from the lambda groups shrinkLambdaGroups(newProgramClassPool, libraryClassPool, resourceFilePool, lambdaGroupClassPool); - logger.info("{} lambda class(es) found that can be merged.", lambdaClassPool.size()); + logger.info("Considered {} lambda classes for merging", lambdaClassPool.size()); + logger.info("of which {} lambda classes were not merged.", notMergedLambdaClassPool.size()); logger.info("{} lambda group(s) created.", lambdaGroupClassPool.size()); - logger.info("#lambda groups/#lambda classes ratio = {}/{} = {}%", lambdaGroupClassPool.size(), lambdaClassPool.size(), 100 * lambdaGroupClassPool.size() / lambdaClassPool.size()); + logger.info("#lambda groups/#merged lambda classes ratio = {}/{} = {}%", + lambdaGroupClassPool.size(), + lambdaClassPool.size() - notMergedLambdaClassPool.size(), + 100 * lambdaGroupClassPool.size() / (lambdaClassPool.size() - notMergedLambdaClassPool.size())); logger.info("Size of original program class pool: {}", programClassPool.size()); logger.info("Size of new program class pool: {}", newProgramClassPool.size()); From 1de8e87608ce6ffb54018d956e25327d548905d0 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 18:16:55 +0100 Subject: [PATCH 084/195] Make info statements debug Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 603e6adf9..82c05f004 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -157,7 +157,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) lambdaClass.interfaceConstantsAccept(this.interfaceAdder); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); - logger.info("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); + logger.debug("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); inlineLambdaInvokeMethods(lambdaClass); ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); @@ -271,7 +271,7 @@ private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) */ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) { - logger.info("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); + logger.debug("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, new KotlinLambdaEnclosingMethodUpdater(this.programClassPool, this.libraryClassPool, From 697578527a07faebb3649cb9cd0f1c200ad023c5 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 8 Jan 2022 18:17:22 +0100 Subject: [PATCH 085/195] Remove redundant comment Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 82c05f004..e0475131e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -171,10 +171,6 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId); - - // update optimisation info of lambda to show lambda has been merged - /*ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); - optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass());*/ } private void inlineMethodsInsideClass(ProgramClass lambdaClass) From 3f3e552562a334910aff61129ed7ee5da764ccd7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 9 Jan 2022 07:45:34 +0100 Subject: [PATCH 086/195] Remove unused imports Modified classes: KotlinLambdaGroupBuilder --- .../java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index e0475131e..4184b9d20 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -11,11 +11,9 @@ import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.InterfaceAdder; -import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; -import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; From 02e996e8e03ffe51d8b5766fc81702f15ae917f8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 9 Jan 2022 07:52:21 +0100 Subject: [PATCH 087/195] Set access flags of LG to FINAL and SUPER Modified classes: KotlinLambdaGroupBuilder --- .../java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 4184b9d20..b534c7d87 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -73,7 +73,7 @@ private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName { // The initial builder is used to set up the initial lambda group class ClassBuilder initialBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, - AccessConstants.PUBLIC, + AccessConstants.FINAL | AccessConstants.SUPER, lambdaGroupName, KotlinLambdaMerger.NAME_KOTLIN_LAMBDA); ProgramClass lambdaGroup = initialBuilder.getProgramClass(); From aa30700057a273d98def7b768a222b3d26c10312 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 9 Jan 2022 07:53:35 +0100 Subject: [PATCH 088/195] Replace info logging by trace logging Modified classes: KotlinLambdaGroupBuilder --- .../java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index b534c7d87..2445387e9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -208,7 +208,7 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) { - logger.info("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); + logger.trace("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); // Note: the lambda class is expected to contain two invoke methods: // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface From 20104004ce1cd08baa17ee8a1166690dcb07d399 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 9 Jan 2022 11:12:12 +0100 Subject: [PATCH 089/195] Arity as constructor argument of lambda group Modified classes: KotlinLambdaGroupBuilder, KotlinLambdaGroupInitBuilder, KotlinLambdaEnclosingMethodUpdater --- .../kotlin/KotlinLambdaEnclosingMethodUpdater.java | 9 +++++++-- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 +++-- .../optimize/kotlin/KotlinLambdaGroupInitBuilder.java | 7 ++++--- 3 files changed, 14 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 3c219c6c4..46f183f3d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -29,6 +29,7 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private final ClassPool libraryClassPool; private final ProgramClass lambdaGroup; private final int classId; + private final int arity; private final ExtraDataEntryNameMap extraDataEntryNameMap; private boolean visitEnclosingMethodAttribute = false; private boolean visitEnclosingMethod = false; @@ -41,11 +42,13 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, ProgramClass lambdaGroup, int classId, + int arity, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.lambdaGroup = lambdaGroup; this.classId = classId; + this.arity = arity; this.extraDataEntryNameMap = extraDataEntryNameMap; } @@ -154,7 +157,8 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence builder.new_(lambdaGroup) .dup() .iconst(classId) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V").__() + .iconst(arity) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() }, { // Lambda is explicitly instantiated @@ -165,7 +169,8 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence builder.new_(lambdaGroup) .dup() .iconst(classId) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(I)V").__() + .iconst(arity) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() } }; } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 2445387e9..5ef9e686e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -168,7 +168,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); // replace instantiation of lambda class with instantiation of lambda group with correct id - updateLambdaInstantiationSite(lambdaClass, lambdaClassId); + updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity); } private void inlineMethodsInsideClass(ProgramClass lambdaClass) @@ -263,7 +263,7 @@ private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) * @param lambdaClass the lambda class of which the enclosing method must be updated * @param lambdaClassId the id that is used for the given lambda class to identify its implementation in the lambda group */ - private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId) + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity) { logger.debug("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, @@ -271,6 +271,7 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaC this.libraryClassPool, this.classBuilder.getProgramClass(), lambdaClassId, + arity, this.extraDataEntryNameMap)); } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java index 9aea98c9e..04fcaf2a2 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java @@ -31,8 +31,9 @@ private static String getInitDescriptorForClosureSize(int closureSize) { descriptor.append(METHOD_ARGUMENT_TYPE_INIT); } - descriptor.append(KotlinLambdaGroupBuilder.FIELD_TYPE_ID); - descriptor.append(")") + descriptor.append(KotlinLambdaGroupBuilder.FIELD_TYPE_ID) + .append("I") // arity argument - TODO: use constant instead of string value + .append(")") .append(RETURN_TYPE_INIT); return descriptor.toString(); } @@ -64,7 +65,7 @@ public ClassBuilder.CodeBuilder buildCodeBuilder() .findField(KotlinLambdaGroupBuilder.FIELD_NAME_ID, KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) .aload_0() // load this class - .iconst_0() // push 0 on stack + .iload(this.closureSize + 2) // load arity argument .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, TYPE_KOTLIN_LAMBDA_INIT) From 9d74ea0da51ed68d30c07f2488820bcd9f99ed67 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 9 Jan 2022 13:06:47 +0100 Subject: [PATCH 090/195] Use ModifiedAllInnerClassesInfoVisitor to visit all inner classes Modified classes: KotlinLambdaGroupBuilder --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 5ef9e686e..010fa1513 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -5,10 +5,7 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; -import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; -import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; +import proguard.classfile.attribute.visitor.*; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.InterfaceAdder; import proguard.classfile.util.ClassUtil; @@ -140,7 +137,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // merge any inner lambda's before merging the current lambda lambdaClass.attributeAccept(Attribute.INNER_CLASSES, - new AllInnerClassesInfoVisitor( + new ModifiedAllInnerClassesInfoVisitor( new InnerClassInfoClassConstantVisitor( new ClassConstantToClassVisitor( new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, From 3d0f394756d5c2c1ac18db2624f054035e9ec6fb Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 8 Feb 2022 14:25:59 +0100 Subject: [PATCH 091/195] Remove unused LambdaGroupInvokeCodeBuilder class --- .../kotlin/LambdaGroupInvokeCodeBuilder.java | 77 ------------------- 1 file changed, 77 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java deleted file mode 100644 index befc4f744..000000000 --- a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupInvokeCodeBuilder.java +++ /dev/null @@ -1,77 +0,0 @@ -package proguard.optimize.kotlin; - -import proguard.classfile.ProgramClass; -import proguard.classfile.ProgramField; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.editor.ClassBuilder; -import proguard.classfile.editor.CompactCodeAttributeComposer; -import proguard.classfile.instruction.Instruction; - -import java.util.ArrayList; -import java.util.List; - -public class LambdaGroupInvokeCodeBuilder { - - private int caseIndexCounter = 0; - // tune the initial list size depending on the expected amount of lambda's that are merged - private final List caseInstructions = new ArrayList<>(5); - - public void addNewCase(Instruction[] instructions) - { - int caseIndex = getNewCaseIndex(); - caseInstructions.add(instructions); - } - - private int getNewCaseIndex() - { - int caseIndex = this.caseIndexCounter; - this.caseIndexCounter++; - return caseIndex; - } - - public int getCaseIndexCounter() - { - return this.caseIndexCounter; - } - - public ClassBuilder.CodeBuilder build(ProgramClass lambdaGroup, ProgramField classIdField) - { - return composer -> { - //InstructionSequenceBuilder instructionSequenceBuilder = new InstructionSequenceBuilder(); - // TODO: use instruction builder or code attribute composer to build (a part of) - // the code attribute of the invoke method of the lambda group - int cases = getCaseIndexCounter(); - - if (cases == 0) - { - composer.return_(); - return; - } - - CompactCodeAttributeComposer.Label[] caseLabels = new CompactCodeAttributeComposer.Label[cases - 1]; - for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) - { - caseLabels[caseIndex] = composer.createLabel(); - } - CompactCodeAttributeComposer.Label defaultLabel = composer.createLabel(); - CompactCodeAttributeComposer.Label endOfMethodLabel = composer.createLabel(); - composer - .aload_0() - .getfield(lambdaGroup, classIdField) - // add extra instructions? - .tableswitch(defaultLabel, 0, cases - 2, caseLabels); - for (int caseIndex = 0; caseIndex < cases - 1; caseIndex++) - { - composer - .label(caseLabels[caseIndex]) - .appendInstructions(caseInstructions.get(caseIndex)) - .goto_(endOfMethodLabel); - } - composer - .label(defaultLabel) - .appendInstructions(caseInstructions.get(cases - 1)) - .label(endOfMethodLabel) - .areturn(); - }; - } -} From 7ba723f899879881df3e01afce63edb98f096bba Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 8 Feb 2022 16:16:41 +0100 Subject: [PATCH 092/195] Revert "Only inline methods of same class for lambda merging" + remove MethodInlinerWrapper This reverts commit b3178bd24a05e6e02bddb260e459307c90f7d6c8. --- .../optimize/MethodInlinerWrapper.java | 319 ------------------ .../optimize/kotlin/KotlinLambdaMerger.java | 1 - .../optimize/peephole/MethodInliner.java | 43 ++- 3 files changed, 39 insertions(+), 324 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/MethodInlinerWrapper.java diff --git a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java b/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java deleted file mode 100644 index d05c3bc2a..000000000 --- a/base/src/main/java/proguard/optimize/MethodInlinerWrapper.java +++ /dev/null @@ -1,319 +0,0 @@ -package proguard.optimize; - -import proguard.Configuration; -import proguard.classfile.*; -import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.visitor.*; -import proguard.classfile.constant.Constant; -import proguard.classfile.constant.visitor.*; -import proguard.classfile.instruction.visitor.AllInstructionVisitor; -import proguard.classfile.instruction.visitor.InstructionConstantVisitor; -import proguard.classfile.instruction.visitor.InstructionCounter; -import proguard.classfile.instruction.visitor.MultiInstructionVisitor; -import proguard.classfile.util.MethodLinker; -import proguard.classfile.visitor.*; -import proguard.optimize.info.*; -import proguard.optimize.peephole.MethodInliner; -import proguard.optimize.peephole.SameClassMethodInliner; -import proguard.util.*; - -public class MethodInlinerWrapper implements ClassPoolVisitor { - - private final Configuration configuration; - private static final String METHOD_MARKING_STATIC = "method/marking/static"; - private static final String METHOD_REMOVAL_PARAMETER = "method/removal/parameter"; - - public MethodInlinerWrapper(final Configuration configuration, - final ClassPool programClassPool, - final ClassPool libraryClassPool) - { - this.configuration = configuration; - markClasses(programClassPool, libraryClassPool); - } - - private void markClasses(final ClassPool programClassPool, - final ClassPool libraryClassPool) - { - - // Create a matcher for filtering optimizations. - StringMatcher filter = configuration.optimizations != null ? - new ListParser(new NameParser()).parse(configuration.optimizations) : - new ConstantMatcher(true); - - boolean methodMarkingStatic = filter.matches(METHOD_MARKING_STATIC); - boolean methodRemovalParameter = filter.matches(METHOD_REMOVAL_PARAMETER); - - // Clean up any old processing info. - programClassPool.classesAccept(new ClassCleaner()); - libraryClassPool.classesAccept(new ClassCleaner()); - - // Link all methods that should get the same optimization info. - programClassPool.classesAccept(new BottomClassFilter( - new MethodLinker())); - libraryClassPool.classesAccept(new BottomClassFilter( - new MethodLinker())); - - // Create a visitor for marking the seeds. - final KeepMarker keepMarker = new KeepMarker(); - - // All library classes and library class members remain unchanged. - libraryClassPool.classesAccept(keepMarker); - libraryClassPool.classesAccept(new AllMemberVisitor(keepMarker)); - - // Mark classes that have the DONT_OPTIMIZE flag set. - programClassPool.classesAccept( - new MultiClassVisitor( - new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, - keepMarker), - - new AllMemberVisitor( - new MultiMemberVisitor( - new MemberProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, - keepMarker), - - new AllAttributeVisitor( - new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, - keepMarker)) - )) - )); - - // We also keep all members that have the DONT_OPTIMIZE flag set on their code attribute. - programClassPool.classesAccept( - new AllMemberVisitor( - new AllAttributeVisitor( - new AttributeNameFilter(Attribute.CODE, - new AttributeProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, - new CodeAttributeToMethodVisitor(keepMarker)))))); - - // We also keep all classes that are involved in .class constructs. - // We're not looking at enum classes though, so they can be simplified. - programClassPool.classesAccept( - new ClassAccessFilter(0, AccessConstants.ENUM, - new AllMethodVisitor( - new AllAttributeVisitor( - new AllInstructionVisitor( - new DotClassClassVisitor(keepMarker)))))); - - // We also keep all classes that are accessed dynamically. - programClassPool.classesAccept( - new AllConstantVisitor( - new ConstantTagFilter(Constant.STRING, - new ReferencedClassVisitor(keepMarker)))); - - // We also keep all class members that are accessed dynamically. - programClassPool.classesAccept( - new AllConstantVisitor( - new ConstantTagFilter(Constant.STRING, - new ReferencedMemberVisitor(keepMarker)))); - - // We also keep all bootstrap method signatures. - programClassPool.classesAccept( - new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, - new AllAttributeVisitor( - new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, - new AllBootstrapMethodInfoVisitor( - new BootstrapMethodHandleTraveler( - new MethodrefTraveler( - new ReferencedMemberVisitor(keepMarker)))))))); - - // We also keep classes and methods referenced from bootstrap - // method arguments. - programClassPool.classesAccept( - new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, - new AllAttributeVisitor( - new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, - new AllBootstrapMethodInfoVisitor( - new AllBootstrapMethodArgumentVisitor( - new MultiConstantVisitor( - // Class constants refer to additional functional - // interfaces (with LambdaMetafactory.altMetafactory). - new ConstantTagFilter(Constant.CLASS, - new ReferencedClassVisitor( - new FunctionalInterfaceFilter( - new ClassHierarchyTraveler(true, false, true, false, - new MultiClassVisitor( - keepMarker, - new AllMethodVisitor( - new MemberAccessFilter(AccessConstants.ABSTRACT, 0, - keepMarker)) - ))))), - - // Method handle constants refer to synthetic lambda - // methods (with LambdaMetafactory.metafactory and - // altMetafactory). - new MethodrefTraveler( - new ReferencedMemberVisitor(keepMarker))))))))); - - // We also keep the classes and abstract methods of functional - // interfaces that are returned by dynamic method invocations. - // These functional interfaces have to remain suitable for the - // dynamic method invocations with LambdaMetafactory. - programClassPool.classesAccept( - new ClassVersionFilter(VersionConstants.CLASS_VERSION_1_7, - new AllConstantVisitor( - new DynamicReturnedClassVisitor( - new FunctionalInterfaceFilter( - new ClassHierarchyTraveler(true, false, true, false, - new MultiClassVisitor( - keepMarker, - new AllMethodVisitor( - new MemberAccessFilter(AccessConstants.ABSTRACT, 0, - keepMarker)) - ))))))); - - // Attach some optimization info to all classes and class members, so - // it can be filled out later. - programClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); - - programClassPool.classesAccept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter())); - - // Give initial marks to read/written fields. side-effect methods, and - // escaping parameters. - final MutableBoolean mutableBoolean = new MutableBoolean(); - - - // Mark all fields as read and written. - programClassPool.classesAccept( - new AllFieldVisitor( - new ReadWriteFieldMarker(mutableBoolean))); - - - // Mark methods based on their headers. - /*programClassPool.classesAccept( - new AllMethodVisitor( - new OptimizationInfoMemberFilter( - new MultiMemberVisitor( - new SideEffectMethodMarker(), - new ParameterEscapeMarker() - ))));*/ - - /*programClassPool.accept(new InfluenceFixpointVisitor( - new SideEffectVisitorMarkerFactory()));*/ - - // Mark all used parameters, including the 'this' parameters. - /*ParallelAllClassVisitor.ClassVisitorFactory markingUsedParametersClassVisitor = - new ParallelAllClassVisitor.ClassVisitorFactory() - { - public ClassVisitor createClassVisitor() - { - return - new AllMethodVisitor( - new OptimizationInfoMemberFilter( - new ParameterUsageMarker(!methodMarkingStatic, - !methodRemovalParameter))); - } - };*/ - - /*programClassPool.accept( - new TimedClassPoolVisitor("Marking used parameters", - new ParallelAllClassVisitor( - markingUsedParametersClassVisitor)));*/ - - // Mark all parameters of referenced methods in methods whose code must - // be kept. This prevents shrinking of method descriptors which may not - // be propagated correctly otherwise. - programClassPool.accept( - new TimedClassPoolVisitor("Marking used parameters in kept code attributes", - new AllClassVisitor( - new AllMethodVisitor( - new OptimizationInfoMemberFilter( - null, - - // visit all methods that are kept - new AllAttributeVisitor( - new OptimizationCodeAttributeFilter( - null, - - // visit all code attributes that are kept - new AllInstructionVisitor( - new InstructionConstantVisitor( - new ConstantTagFilter(new int[] { Constant.METHODREF, - Constant.INTERFACE_METHODREF }, - new ReferencedMemberVisitor( - new OptimizationInfoMemberFilter( - // Mark all parameters including "this" of referenced methods - new ParameterUsageMarker(true, true, false)))))))) - ))))); - - - // Mark all classes with package visible members. - // Mark all exception catches of methods. - // Count all method invocations. - // Mark super invocations and other access of methods. - StackSizeComputer stackSizeComputer = new StackSizeComputer(); - - programClassPool.accept( - new TimedClassPoolVisitor("Marking method and referenced class properties", - new MultiClassVisitor( - // Mark classes. - new OptimizationInfoClassFilter( - new MultiClassVisitor( - new PackageVisibleMemberContainingClassMarker(), - new WrapperClassMarker(), - - new AllConstantVisitor( - new PackageVisibleMemberInvokingClassMarker()), - - new AllMemberVisitor( - new ContainsConstructorsMarker()) - )), - - // Mark methods. - new AllMethodVisitor( - new OptimizationInfoMemberFilter( - new AllAttributeVisitor( - new DebugAttributeVisitor("Marking method properties", - new MultiAttributeVisitor( - stackSizeComputer, - new CatchExceptionMarker(), - - new AllInstructionVisitor( - new MultiInstructionVisitor( - new SuperInvocationMarker(), - new DynamicInvocationMarker(), - new BackwardBranchMarker(), - new AccessMethodMarker(), - new SynchronizedBlockMethodMarker(), - new FinalFieldAssignmentMarker(), - new NonEmptyStackReturnMarker(stackSizeComputer) - )) - ))))), - - // Mark referenced classes and methods. - new AllMethodVisitor( - new AllAttributeVisitor( - new DebugAttributeVisitor("Marking referenced class properties", - new MultiAttributeVisitor( - new AllExceptionInfoVisitor( - new ExceptionHandlerConstantVisitor( - new ReferencedClassVisitor( - new OptimizationInfoClassFilter( - new CaughtClassMarker())))), - - new AllInstructionVisitor( - new MultiInstructionVisitor( - new InstantiationClassMarker(), - new InstanceofClassMarker(), - new DotClassMarker(), - new MethodInvocationMarker() - )) - )))) - ))); - } - - @Override - public void visitClassPool(ClassPool classPool) { - InstructionCounter methodInliningUniqueCounter = new InstructionCounter(); - classPool.accept(new TimedClassPoolVisitor("Inlining single methods", - new AllMethodVisitor( - new AllAttributeVisitor( - new DebugAttributeVisitor("Inlining single methods", - new OptimizationCodeAttributeFilter( - new SameClassMethodInliner(configuration.microEdition, - configuration.android, - configuration.allowAccessModification, - methodInliningUniqueCounter))))))); - System.out.println("Lambda methods inlined: " + methodInliningUniqueCounter.getCount()); - } -} diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index a3dce7558..08d0bf369 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -12,7 +12,6 @@ import proguard.classfile.util.ClassInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; -import proguard.optimize.MethodInlinerWrapper; import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; diff --git a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java index ec2f69fff..3df531bc3 100644 --- a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java +++ b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java @@ -153,11 +153,48 @@ public MethodInliner(boolean microEdition, boolean allowAccessModification, InstructionVisitor extraInlinedInvocationVisitor) { + this(microEdition, + android, + defaultMaxResultingCodeLength(microEdition), + allowAccessModification, + true, + extraInlinedInvocationVisitor); + } + + + /** + * Creates a new MethodInliner. + * + * @param microEdition Indicates whether the resulting code is + * targeted at Java Micro Edition. + * @param android Indicates whether the resulting code is + * targeted at the Dalvik VM. + * @param maxResultingCodeLength Configures the inliner with a max resulting + * code length. + * @param allowAccessModification Indicates whether the access modifiers of + * classes and class members can be changed + * in order to inline methods. + * @param usesOptimizationInfo Indicates whether this inliner needs to perform checks + * that require optimization info. + * @param extraInlinedInvocationVisitor An optional extra visitor for all + * inlined invocation instructions. + */ + public MethodInliner(boolean microEdition, + boolean android, + int maxResultingCodeLength, + boolean allowAccessModification, + boolean usesOptimizationInfo, + InstructionVisitor extraInlinedInvocationVisitor) + { + if (maxResultingCodeLength > MAXIMUM_RESULTING_CODE_LENGTH_JVM) + { + throw new IllegalArgumentException("Maximum resulting code length cannot exceed " + MAXIMUM_RESULTING_CODE_LENGTH_JVM); + } this.microEdition = microEdition; this.android = android; this.maxResultingCodeLength = maxResultingCodeLength; this.allowAccessModification = allowAccessModification; - this.inlineSingleInvocations = inlineSingleInvocations; + this.usesOptimizationInfo = usesOptimizationInfo; this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; } @@ -731,9 +768,7 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM // Only inline the method if its related static initializers don't // have any side effects. - !SideEffectClassChecker.mayHaveSideEffects(targetClass, - programClass, - programMethod)) + !SideEffectClassChecker.mayHaveSideEffects(targetClass, programClass, programMethod)))) { boolean oldInlining = inlining; From ee4c6a9778d31ac088a3f76e2555a3731191e88f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 17 Feb 2022 15:13:20 +0100 Subject: [PATCH 093/195] Passify KotlinLambdaMerger --- base/src/main/java/proguard/ProGuard.java | 7 +-- .../optimize/kotlin/KotlinLambdaMerger.java | 49 +++++++++---------- 2 files changed, 26 insertions(+), 30 deletions(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 397ab7c7a..1b8ed3c15 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -425,11 +425,8 @@ private void shrink(boolean afterOptimizer) throws Exception * Reduce the size needed to represent Kotlin lambda's. * The classes that are generated for lambda's with a same structure and from the same package are merged into a group. */ - private void mergeKotlinLambdaClasses() throws IOException { - programClassPool = new KotlinLambdaMerger(configuration).execute(programClassPool, - libraryClassPool, - resourceFilePool, - extraDataEntryNameMap); + private void mergeKotlinLambdaClasses() throws Exception { + passRunner.run(new KotlinLambdaMerger(configuration), appView); } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 08d0bf369..eab901652 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -2,6 +2,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import proguard.AppView; import proguard.Configuration; import proguard.classfile.ClassConstants; import proguard.classfile.ClassPool; @@ -17,13 +18,14 @@ import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.optimize.peephole.SameClassMethodInliner; +import proguard.pass.Pass; import proguard.resources.file.ResourceFilePool; import proguard.shrink.*; import proguard.util.ProcessingFlags; import java.io.IOException; -public class KotlinLambdaMerger { +public class KotlinLambdaMerger implements Pass { public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; @@ -59,18 +61,15 @@ public KotlinLambdaMerger(Configuration configuration) this.configuration = configuration; } - public ClassPool execute(final ClassPool programClassPool, - final ClassPool libraryClassPool, - final ResourceFilePool resourceFilePool, - final ExtraDataEntryNameMap extraDataEntryNameMap) throws IOException + @Override + public void execute(AppView appView) throws Exception { - // Remove old processing info - programClassPool.classesAccept(new ClassCleaner()); - libraryClassPool.classesAccept(new ClassCleaner()); + appView.programClassPool.classesAccept(new ClassCleaner()); + appView.libraryClassPool.classesAccept(new ClassCleaner()); // get the Lambda class and the Function0 interface - Clazz kotlinLambdaClass = getKotlinLambdaClass(programClassPool, libraryClassPool); + Clazz kotlinLambdaClass = getKotlinLambdaClass(appView.programClassPool, appView.libraryClassPool); if (kotlinLambdaClass == null) { logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_LAMBDA); } @@ -84,13 +83,13 @@ public ClassPool execute(final ClassPool programClassPool, // find all lambda classes with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty - programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); - programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, - new ImplementedClassFilter(kotlinLambdaClass, false, - new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, - new ClassPoolFiller(lambdaClassPool), - newProgramClassPoolFiller), - newProgramClassPoolFiller)) + appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); + appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, + new ImplementedClassFilter(kotlinLambdaClass, false, + new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, + new ClassPoolFiller(lambdaClassPool), + newProgramClassPoolFiller), + newProgramClassPoolFiller)) ); // group the lambda's per package @@ -107,25 +106,25 @@ public ClassPool execute(final ClassPool programClassPool, // merge the lambda's per package KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger(this.configuration, - programClassPool, - libraryClassPool, + appView.programClassPool, + appView.libraryClassPool, new MultiClassVisitor( new ClassPoolFiller(lambdaGroupClassPool), newProgramClassPoolFiller), new MultiClassVisitor( new ClassPoolFiller(notMergedLambdaClassPool), newProgramClassPoolFiller), - extraDataEntryNameMap); + appView.extraDataEntryNameMap); packageGrouper.packagesAccept(merger); // initialise the references from and to the newly created lambda groups and their enclosing classes - newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, libraryClassPool)); + newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, appView.libraryClassPool)); // inline the helper invoke methods into the general invoke method - inlineMethodsInsideLambdaGroups(newProgramClassPool, libraryClassPool, lambdaGroupClassPool); + inlineMethodsInsideLambdaGroups(newProgramClassPool, appView.libraryClassPool, lambdaGroupClassPool); // remove the unused helper methods from the lambda groups - shrinkLambdaGroups(newProgramClassPool, libraryClassPool, resourceFilePool, lambdaGroupClassPool); + shrinkLambdaGroups(newProgramClassPool, appView.libraryClassPool, appView.resourceFilePool, lambdaGroupClassPool); logger.info("Considered {} lambda classes for merging", lambdaClassPool.size()); logger.info("of which {} lambda classes were not merged.", notMergedLambdaClassPool.size()); @@ -134,12 +133,12 @@ public ClassPool execute(final ClassPool programClassPool, lambdaGroupClassPool.size(), lambdaClassPool.size() - notMergedLambdaClassPool.size(), 100 * lambdaGroupClassPool.size() / (lambdaClassPool.size() - notMergedLambdaClassPool.size())); - logger.info("Size of original program class pool: {}", programClassPool.size()); + logger.info("Size of original program class pool: {}", appView.programClassPool.size()); logger.info("Size of new program class pool: {}", newProgramClassPool.size()); - return newProgramClassPool; + appView.programClassPool.clear(); + newProgramClassPool.classesAccept(new ClassPoolFiller(appView.programClassPool)); } - return programClassPool; } private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) From a7d453ac92e25827603b9842a886670608f6ddb3 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Feb 2022 18:08:04 +0100 Subject: [PATCH 094/195] Add new option to configuration: -mergekotlinlambdaclasses --- base/src/main/java/proguard/Configuration.java | 5 +++++ base/src/main/java/proguard/ConfigurationConstants.java | 1 + base/src/main/java/proguard/ConfigurationParser.java | 1 + base/src/main/java/proguard/ConfigurationWriter.java | 1 + 4 files changed, 8 insertions(+) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 78cd9c0ec..aea96dae5 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -200,6 +200,11 @@ public class Configuration */ public boolean mergeInterfacesAggressively = false; + /** + * Specifies whether Kotlin lambda classes can be merged into lambda groups. + */ + public boolean mergeKotlinLambdaClasses; + /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index b542d0542..609731e7f 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -66,6 +66,7 @@ public class ConfigurationConstants public static final String ASSUME_VALUES_OPTION = "-assumevalues"; public static final String ALLOW_ACCESS_MODIFICATION_OPTION = "-allowaccessmodification"; public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively"; + public static final String MERGE_KOTLIN_LAMBDA_CLASSES_OPTION = "-mergekotlinlambdaclasses"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; public static final String PRINT_MAPPING_OPTION = "-printmapping"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index 80d7f83aa..51f77dc38 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -190,6 +190,7 @@ public void parse(Configuration configuration) else if (ConfigurationConstants.ASSUME_VALUES_OPTION .startsWith(nextWord)) configuration.assumeValues = parseAssumeClassSpecificationArguments(configuration.assumeValues); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); + else if (ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION .startsWith(nextWord)) configuration.mergeKotlinLambdaClasses = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); diff --git a/base/src/main/java/proguard/ConfigurationWriter.java b/base/src/main/java/proguard/ConfigurationWriter.java index 57406d515..e5f9ba971 100644 --- a/base/src/main/java/proguard/ConfigurationWriter.java +++ b/base/src/main/java/proguard/ConfigurationWriter.java @@ -121,6 +121,7 @@ public void write(Configuration configuration) throws IOException writeOption(ConfigurationConstants.OPTIMIZATION_PASSES, configuration.optimizationPasses); writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); + writeOption(ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION, configuration.mergeKotlinLambdaClasses); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); From 3cbec149e903ebdfbc6283d078e9b54ab0e3b10c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Feb 2022 18:10:15 +0100 Subject: [PATCH 095/195] Add new option to configuration: -printlambdagroupmapping --- base/src/main/java/proguard/Configuration.java | 6 ++++++ base/src/main/java/proguard/ConfigurationConstants.java | 1 + base/src/main/java/proguard/ConfigurationParser.java | 1 + base/src/main/java/proguard/ConfigurationWriter.java | 1 + 4 files changed, 9 insertions(+) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index aea96dae5..4d970ca30 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -200,6 +200,12 @@ public class Configuration */ public boolean mergeInterfacesAggressively = false; + /** + * An optional output file for listing the lambda to lambdagroup mapping. + * An empty file name means the standard output. + */ + public File printLambdaGroupMapping; + /** * Specifies whether Kotlin lambda classes can be merged into lambda groups. */ diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index 609731e7f..20a8c1922 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -66,6 +66,7 @@ public class ConfigurationConstants public static final String ASSUME_VALUES_OPTION = "-assumevalues"; public static final String ALLOW_ACCESS_MODIFICATION_OPTION = "-allowaccessmodification"; public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively"; + public static final String PRINT_LAMBDAGROUP_MAPPING_OPTION = "-printlambdagroupmapping"; public static final String MERGE_KOTLIN_LAMBDA_CLASSES_OPTION = "-mergekotlinlambdaclasses"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index 51f77dc38..96d3f639f 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -190,6 +190,7 @@ public void parse(Configuration configuration) else if (ConfigurationConstants.ASSUME_VALUES_OPTION .startsWith(nextWord)) configuration.assumeValues = parseAssumeClassSpecificationArguments(configuration.assumeValues); else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); + else if (ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION .startsWith(nextWord)) configuration.printLambdaGroupMapping = parseOptionalFile(); else if (ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION .startsWith(nextWord)) configuration.mergeKotlinLambdaClasses = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); diff --git a/base/src/main/java/proguard/ConfigurationWriter.java b/base/src/main/java/proguard/ConfigurationWriter.java index e5f9ba971..dfb3aa8e5 100644 --- a/base/src/main/java/proguard/ConfigurationWriter.java +++ b/base/src/main/java/proguard/ConfigurationWriter.java @@ -121,6 +121,7 @@ public void write(Configuration configuration) throws IOException writeOption(ConfigurationConstants.OPTIMIZATION_PASSES, configuration.optimizationPasses); writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); + writeOption(ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION, configuration.printLambdaGroupMapping); writeOption(ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION, configuration.mergeKotlinLambdaClasses); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); From 514ecbf1509622de3a3650d531e69de753138ba9 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Feb 2022 23:14:35 +0100 Subject: [PATCH 096/195] Print mapping of lambda's to lambda group --- .../optimize/kotlin/KotlinLambdaMerger.java | 26 ++++++++++++ .../kotlin/LambdaGroupMappingPrinter.java | 42 +++++++++++++++++++ 2 files changed, 68 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index eab901652..d5e882ab9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -13,6 +13,7 @@ import proguard.classfile.util.ClassInitializer; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; +import proguard.obfuscate.MappingPrinter; import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; @@ -21,9 +22,12 @@ import proguard.pass.Pass; import proguard.resources.file.ResourceFilePool; import proguard.shrink.*; +import proguard.util.PrintWriterUtil; import proguard.util.ProcessingFlags; +import java.io.File; import java.io.IOException; +import java.io.PrintWriter; public class KotlinLambdaMerger implements Pass { @@ -117,6 +121,28 @@ public void execute(AppView appView) throws Exception appView.extraDataEntryNameMap); packageGrouper.packagesAccept(merger); + // Print out the mapping, if requested. + PrintWriter out = new PrintWriter(System.out, true); + if (configuration.printLambdaGroupMapping != null) + { + logger.info("Printing lambda group mapping to [{}]...", PrintWriterUtil.fileName(configuration.printLambdaGroupMapping)); + + PrintWriter mappingWriter = + PrintWriterUtil.createPrintWriter(configuration.printLambdaGroupMapping, out); + + try + { + // Print out lambda's that have been merged and their lambda groups. + lambdaClassPool.classesAcceptAlphabetically( + new LambdaGroupMappingPrinter(mappingWriter)); + } + finally + { + PrintWriterUtil.closePrintWriter(configuration.printMapping, + mappingWriter); + } + } + // initialise the references from and to the newly created lambda groups and their enclosing classes newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, appView.libraryClassPool)); diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java new file mode 100644 index 000000000..d93ddff96 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java @@ -0,0 +1,42 @@ +package proguard.optimize.kotlin; + +import proguard.classfile.Clazz; +import proguard.classfile.ProgramClass; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassVisitor; +import proguard.optimize.info.ProgramClassOptimizationInfo; + +import java.io.PrintWriter; + +public class LambdaGroupMappingPrinter implements ClassVisitor { + + private final PrintWriter pw; + + /** + * Creates a new LambdaGroupMappingPrinter that prints to the given writer. + * @param printWriter the writer to which to print. + */ + public LambdaGroupMappingPrinter(PrintWriter printWriter) + { + this.pw = printWriter; + } + + + @Override + public void visitAnyClass(Clazz clazz) { } + + @Override + public void visitProgramClass(ProgramClass programClass) { + String name = programClass.getName(); + ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(programClass); + Clazz lambdaGroup = optimizationInfo.getLambdaGroup(); + if (lambdaGroup != null) + { + String lambdaGroupName = lambdaGroup.getName(); + // Print out the class to lambda group mapping. + pw.println(ClassUtil.externalClassName(name) + + " -> " + + ClassUtil.externalClassName(lambdaGroupName)); + } + } +} From 304a6e28d17759743bc2c516fd80d28e7c1a2e06 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 14:54:48 +0100 Subject: [PATCH 097/195] Enable lambda merging based on configuration --- base/src/main/java/proguard/ProGuard.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index f7a5adfe7..ee338c8db 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -178,10 +178,10 @@ public void execute() throws Exception shrink(false); } - // if (configuration.mergeKotlinLambdaClasses) - //{ + if (configuration.mergeKotlinLambdaClasses) + { mergeKotlinLambdaClasses(); - //} + } // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? From 8e95bfd00638b2168a089050e9f3b373036da094 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 14:57:36 +0100 Subject: [PATCH 098/195] Mention lambda class id in mapping --- .../optimize/info/ProgramClassOptimizationInfo.java | 12 ++++++++++++ .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 1 + .../optimize/kotlin/LambdaGroupMappingPrinter.java | 5 ++++- 3 files changed, 17 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java b/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java index 8255945e3..bad188d3e 100644 --- a/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java +++ b/base/src/main/java/proguard/optimize/info/ProgramClassOptimizationInfo.java @@ -45,6 +45,7 @@ public class ProgramClassOptimizationInfo private volatile Clazz wrappedClass; private volatile Clazz targetClass; private volatile Clazz lambdaGroup; + private volatile int lambdaGroupClassId; public boolean isKept() @@ -262,6 +263,17 @@ public Clazz getLambdaGroup() return lambdaGroup; } + public void setLambdaGroupClassId(int classId) + { + this.lambdaGroupClassId = classId; + } + + + public int getLambdaGroupClassId() + { + return lambdaGroupClassId; + } + /** * Merges in the given information of a class that is merged. diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 010fa1513..2c2830eff 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -166,6 +166,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity); + optimizationInfo.setLambdaGroupClassId(lambdaClassId); } private void inlineMethodsInsideClass(ProgramClass lambdaClass) diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java index d93ddff96..fa6679e19 100644 --- a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java @@ -36,7 +36,10 @@ public void visitProgramClass(ProgramClass programClass) { // Print out the class to lambda group mapping. pw.println(ClassUtil.externalClassName(name) + " -> " + - ClassUtil.externalClassName(lambdaGroupName)); + ClassUtil.externalClassName(lambdaGroupName) + + " (case " + + optimizationInfo.getLambdaGroupClassId() + + ")"); } } } From 0b74332b15f984b876c80e519006b128e4dc06bc Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 15:04:27 +0100 Subject: [PATCH 099/195] Call different of lambda group, depending on closure size For each free variable, an extra constructor argument is used, so the correct constructor has to be called. --- .../KotlinLambdaEnclosingMethodUpdater.java | 70 +++++++++++++------ .../kotlin/KotlinLambdaGroupBuilder.java | 5 +- 2 files changed, 53 insertions(+), 22 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 46f183f3d..6aabc35f8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -30,6 +30,7 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private final ProgramClass lambdaGroup; private final int classId; private final int arity; + private final int closureSize; private final ExtraDataEntryNameMap extraDataEntryNameMap; private boolean visitEnclosingMethodAttribute = false; private boolean visitEnclosingMethod = false; @@ -43,12 +44,15 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ProgramClass lambdaGroup, int classId, int arity, - ExtraDataEntryNameMap extraDataEntryNameMap) { - this.programClassPool = programClassPool; - this.libraryClassPool = libraryClassPool; - this.lambdaGroup = lambdaGroup; - this.classId = classId; - this.arity = arity; + int closureSize, + ExtraDataEntryNameMap extraDataEntryNameMap) + { + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + this.lambdaGroup = lambdaGroup; + this.classId = classId; + this.arity = arity; + this.closureSize = closureSize; this.extraDataEntryNameMap = extraDataEntryNameMap; } @@ -56,7 +60,8 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override - public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttribute enclosingMethodAttribute) { + public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttribute enclosingMethodAttribute) + { // the given method must be the method where the lambda is defined Clazz enclosingClass = enclosingMethodAttribute.referencedClass; if (visitEnclosingMethodAttribute || enclosingClass == lambdaClass) { @@ -81,7 +86,8 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr } @Override - public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclosingMethod) { + public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclosingMethod) + { // the given class must be the class that defines the lambda // the given method must be the method where the lambda is defined if (!visitEnclosingMethodAttribute || visitEnclosingMethod) { @@ -145,32 +151,56 @@ private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTar private Instruction[][][] createReplacementPatternsForLambda(InstructionSequenceBuilder builder) { - Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, "()V"); + // TODO: ensure that the correct method is selected + // TODO: decide what to do if multiple methods exist + Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); return new Instruction[][][] { + // Empty closure lambda's { // Lambda is 'instantiated' by referring to its static INSTANCE field builder.getstatic(currentLambdaClass.getName(), - KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, - "L" + currentLambdaClass.getName() + ";").__(), + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, + "L" + currentLambdaClass.getName() + ";").__(), builder.new_(lambdaGroup) - .dup() - .iconst(classId) - .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() + .dup() + .iconst(classId) + .iconst(arity) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() }, { // Lambda is explicitly instantiated builder.new_(currentLambdaClass) - .dup() - .invokespecial(currentLambdaClass, initMethod).__(), + .dup() + .invokespecial(currentLambdaClass, initMethod).__(), builder.new_(lambdaGroup) - .dup() - .iconst(classId) + .dup() + .iconst(classId) + .iconst(arity) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() + }, + // Non-empty closure lambda's + { + // Lambda is explicitly instantiated with free variables as arguments (part 1) + builder.new_(currentLambdaClass) + //.dup() + .__(), + + builder.new_(lambdaGroup) + //.dup() + .__() + }, + { + builder.invokespecial(currentLambdaClass, initMethod).__(), + builder.iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, + //createInitDescriptor(this.closureSize, KotlinLambdaGroupBuilder.FIELD_TYPE_FREE_VARIABLE, KotlinLambdaGroupBuilder.FIELD_TYPE_ID) + KotlinLambdaGroupInitBuilder.getInitDescriptorForClosureSize(this.closureSize) + ) + .__() } }; } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 2c2830eff..fdb6dce65 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -165,7 +165,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); // replace instantiation of lambda class with instantiation of lambda group with correct id - updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity); + updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, closureSize); optimizationInfo.setLambdaGroupClassId(lambdaClassId); } @@ -261,7 +261,7 @@ private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) * @param lambdaClass the lambda class of which the enclosing method must be updated * @param lambdaClassId the id that is used for the given lambda class to identify its implementation in the lambda group */ - private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity) + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity, int closureSize) { logger.debug("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, @@ -270,6 +270,7 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaC this.classBuilder.getProgramClass(), lambdaClassId, arity, + closureSize, this.extraDataEntryNameMap)); } From e91d24a9e894342f74506acbb2127d3799ffc121 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:44:04 +0100 Subject: [PATCH 100/195] KotlinLambdaGroupInitUpdater A member visitor that adds instructions to a given init method to call the super constructor with the given arity as argument and store the class id in a field --- .../kotlin/KotlinLambdaGroupInitUpdater.java | 107 ++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java new file mode 100644 index 000000000..8722dc382 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -0,0 +1,107 @@ +package proguard.optimize.kotlin; + + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.*; +import proguard.classfile.attribute.*; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.util.BranchTargetFinder; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.util.InstructionSequenceMatcher; +import proguard.classfile.visitor.MemberVisitor; + +public class KotlinLambdaGroupInitUpdater implements AttributeVisitor, MemberVisitor { + + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; + private static final Logger logger = LogManager.getLogger(KotlinLambdaGroupInitUpdater.class); + + public KotlinLambdaGroupInitUpdater(ClassPool programClassPool, + ClassPool libraryClassPool) + { + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) + { + logger.trace("Updating method " + programMethod.getName(programClass) + programMethod.getDescriptor(programClass) + " in class " + programClass); + programMethod.attributesAccept(programClass, this); + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) + { + // This attribute visitor should only be used for program classes. + try + { + visitCodeAttribute((ProgramClass) clazz, (ProgramMethod) method, codeAttribute); + } + catch (ClassCastException exception) + { + logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), clazz, method); + } + } + + public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programMethod, CodeAttribute codeAttribute) + { + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); + + // The arity argument is the last of all. + // Note that the arguments start counting from 1, as the class itself is at index 0. + int arityIndex = ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass)); + + InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor, arityIndex, programClass); + + codeAttribute.accept(programClass, + programMethod, + new PeepholeEditor(branchTargetFinder, + codeAttributeEditor, + replacer)); + } + + private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTargetFinder branchTargetFinder, + CodeAttributeEditor codeAttributeEditor, + int arityIndex, + ProgramClass lambdaGroup) + { + InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClassPool, libraryClassPool); + Instruction[][][] replacementPatterns = createReplacementPatternsForInit(builder, arityIndex, lambdaGroup); + return new InstructionSequencesReplacer(builder.constants(), + replacementPatterns, + branchTargetFinder, + codeAttributeEditor); + } + + private Instruction[][][] createReplacementPatternsForInit(InstructionSequenceBuilder builder, + int arityIndex, + ProgramClass lambdaGroup) + { + // TODO: store classId in field + final int X = InstructionSequenceMatcher.X; + return new Instruction[][][] + { + { + builder.aload_0() + .iconst(X) + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .__(), + builder.aload_0() + .iload(arityIndex - 1) + .putfield(lambdaGroup, // store the id in a field + lambdaGroup + .findField(KotlinLambdaGroupBuilder.FIELD_NAME_ID, + KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) + .aload_0() + .iload(arityIndex) + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .__() + } + }; + } +} From 7e0318e5e03a1493c342fed7dbc8e07569d9ef50 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:45:40 +0100 Subject: [PATCH 101/195] Use the init updater for building a lambda group --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index fdb6dce65..e0b927602 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -40,6 +40,7 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { private final Map invokeMethodBuilders; private final InterfaceAdder interfaceAdder; private final ExtraDataEntryNameMap extraDataEntryNameMap; + private final KotlinLambdaGroupInitUpdater initUpdater; private static final Logger logger = LogManager.getLogger(KotlinLambdaGroupBuilder.class); /** @@ -63,6 +64,7 @@ public KotlinLambdaGroupBuilder(final String lambdaGroupName, this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); this.extraDataEntryNameMap = extraDataEntryNameMap; this.notMergedLambdaVisitor = notMergedLambdaVisitor; + this.initUpdater = new KotlinLambdaGroupInitUpdater(programClassPool, libraryClassPool); initialiseLambdaGroup(); } @@ -164,6 +166,9 @@ private void mergeLambdaClass(ProgramClass lambdaClass) } int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); + Method initMethod = copyLambdaInitToLambdaGroup(lambdaClass); + initMethod.accept(lambdaGroup, this.initUpdater); + // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, closureSize); optimizationInfo.setLambdaGroupClassId(lambdaClassId); @@ -294,7 +299,7 @@ public ProgramClass build() // create (int id) // create invoke(...) method, based on invokeArity // - addInitConstructors(); + //addInitConstructors(); addInvokeMethods(); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); lambdaGroup.setProcessingFlags(lambdaGroup.getProcessingFlags() | ProcessingFlags.INJECTED); From f3d5717c98cf95a20a17fda8a493cef206a60ae8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:47:07 +0100 Subject: [PATCH 102/195] Use descriptor to select init when instantiating lambda group --- .../kotlin/KotlinLambdaEnclosingMethodUpdater.java | 11 ++++------- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 7 ++++--- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 6aabc35f8..7923f0710 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -30,7 +30,7 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private final ProgramClass lambdaGroup; private final int classId; private final int arity; - private final int closureSize; + private final String constructorDescriptor; private final ExtraDataEntryNameMap extraDataEntryNameMap; private boolean visitEnclosingMethodAttribute = false; private boolean visitEnclosingMethod = false; @@ -44,7 +44,7 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ProgramClass lambdaGroup, int classId, int arity, - int closureSize, + String constructorDescriptor, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; @@ -52,7 +52,7 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, this.lambdaGroup = lambdaGroup; this.classId = classId; this.arity = arity; - this.closureSize = closureSize; + this.constructorDescriptor = constructorDescriptor; this.extraDataEntryNameMap = extraDataEntryNameMap; } @@ -196,10 +196,7 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence builder.invokespecial(currentLambdaClass, initMethod).__(), builder.iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, - //createInitDescriptor(this.closureSize, KotlinLambdaGroupBuilder.FIELD_TYPE_FREE_VARIABLE, KotlinLambdaGroupBuilder.FIELD_TYPE_ID) - KotlinLambdaGroupInitBuilder.getInitDescriptorForClosureSize(this.closureSize) - ) + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT,constructorDescriptor) .__() } }; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index e0b927602..8a15aea55 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -167,10 +167,11 @@ private void mergeLambdaClass(ProgramClass lambdaClass) int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); Method initMethod = copyLambdaInitToLambdaGroup(lambdaClass); + String constructorDescriptor = initMethod.getDescriptor(lambdaGroup); initMethod.accept(lambdaGroup, this.initUpdater); // replace instantiation of lambda class with instantiation of lambda group with correct id - updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, closureSize); + updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, constructorDescriptor); optimizationInfo.setLambdaGroupClassId(lambdaClassId); } @@ -266,7 +267,7 @@ private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) * @param lambdaClass the lambda class of which the enclosing method must be updated * @param lambdaClassId the id that is used for the given lambda class to identify its implementation in the lambda group */ - private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity, int closureSize) + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity, String constructorDescriptor) { logger.debug("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, @@ -275,7 +276,7 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaC this.classBuilder.getProgramClass(), lambdaClassId, arity, - closureSize, + constructorDescriptor, this.extraDataEntryNameMap)); } From fb316d9885ef18e0ee5cbf967bbd0f5c7bf2adc3 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:47:51 +0100 Subject: [PATCH 103/195] Copy lambda init methods into lambda group + updated method copier --- .../kotlin/KotlinLambdaGroupBuilder.java | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 8a15aea55..5fd47e85e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -210,6 +210,20 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM inlineMethodsInsideClass(lambdaClass); } + private ProgramMethod copyLambdaInitToLambdaGroup(ProgramClass lambdaClass) + { + logger.trace("Copying method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); + ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); + if (initMethod == null) + { + throw new NullPointerException("No method was found in lambda class " + lambdaClass); + } + String oldInitDescriptor = initMethod.getDescriptor(lambdaClass); + String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; + initMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), ClassConstants.METHOD_NAME_INIT, newInitDescriptor, AccessConstants.PUBLIC)); + return (ProgramMethod)this.classBuilder.getProgramClass().findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + } + private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) { logger.trace("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); @@ -225,8 +239,9 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) throw new NullPointerException("No invoke method was found in lambda class " + lambdaClass); } String newMethodName = createDerivedInvokeMethodName(lambdaClass); - invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), newMethodName)); - return (ProgramMethod)this.classBuilder.getProgramClass().findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); + invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), newMethodName, AccessConstants.PRIVATE)); + ProgramMethod newInitMethod = (ProgramMethod)this.classBuilder.getProgramClass().findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); + return newInitMethod; // TODO: ensure that fields that are referenced by the copied method exist in the lambda group, are initialised, // and cast to the correct type inside the copied method } From f834eca27c51095cfce1596974e9036f4d73ebe2 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:50:05 +0100 Subject: [PATCH 104/195] Increase maximum code size of invoke methods Choose 10*[merged lambda's] --- .../optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index dfab292d9..027ac4e2b 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -177,7 +177,7 @@ public ProgramMethod build() return classBuilder.addAndReturnMethod(AccessConstants.PUBLIC | AccessConstants.SYNTHETIC, METHOD_NAME_INVOKE, getMethodDescriptorForArity(this.arity), - 50, + this.methodsToBeCalled.size() * 10, this.buildCodeBuilder()); } From bb64f6c4c9983944f80f940f18ee2bbf7d2fc9a0 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 22 Mar 2022 22:50:20 +0100 Subject: [PATCH 105/195] Consider lambda's with constructor arguments for merging --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 7 ++++--- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 ++-- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 5fd47e85e..263c3f40e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -142,11 +142,12 @@ private void mergeLambdaClass(ProgramClass lambdaClass) new ModifiedAllInnerClassesInfoVisitor( new InnerClassInfoClassConstantVisitor( new ClassConstantToClassVisitor( - new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, + //new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, - (ClassVisitor)this), - null)), // don't revisit the current lambda + (ClassVisitor)this)//, + // null) + ), // don't revisit the current lambda null))); // Add interfaces of lambda class to the lambda group diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d5e882ab9..cf019dbf5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -90,9 +90,9 @@ public void execute(AppView appView) throws Exception appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, new ImplementedClassFilter(kotlinLambdaClass, false, - new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, + //new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassPoolFiller(lambdaClassPool), - newProgramClassPoolFiller), + //newProgramClassPoolFiller), newProgramClassPoolFiller)) ); From 5f6bb4394e9a5370b26849ee753c8408ec457ac0 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Mar 2022 22:43:35 +0100 Subject: [PATCH 106/195] Add space after comma --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 7923f0710..559bf9d56 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -196,8 +196,8 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence builder.invokespecial(currentLambdaClass, initMethod).__(), builder.iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT,constructorDescriptor) - .__() + .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, constructorDescriptor) + .__() } }; } From 6a99a3dae675811e2ecdf5098040815bae0657c1 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Mar 2022 22:44:31 +0100 Subject: [PATCH 107/195] Fix bug related to miscounting Double/Long args as 1 byte instead of 2 --- .../kotlin/KotlinLambdaGroupInitUpdater.java | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index 8722dc382..0a4c07943 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -53,8 +53,10 @@ public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programM BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); // The arity argument is the last of all. - // Note that the arguments start counting from 1, as the class itself is at index 0. - int arityIndex = ClassUtil.internalMethodParameterCount(programMethod.getDescriptor(programClass)); + // Note that the arguments start counting from 1, as the class itself is at byte 0. + // long and double arguments take 2 bytes. + // The classId and arity arguments are the 2 last, which take 1 byte each, as they are of type int. + int arityIndex = KotlinLambdaGroupInitUpdater.countParameterBytes(programMethod.getDescriptor(programClass)) - 1; InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor, arityIndex, programClass); @@ -65,6 +67,51 @@ public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programM replacer)); } + private static int countParameterBytes(String descriptor) + { + int counter = 1; + int index = 1; + + char oldC = 0; + char c = descriptor.charAt(index++); + while (true) + { + switch (c) + { + case TypeConstants.ARRAY: + { + // Just ignore all array characters. + break; + } + case TypeConstants.CLASS_START: + { + counter++; + + // Skip the class name. + index = descriptor.indexOf(TypeConstants.CLASS_END, index) + 1; + break; + } + case TypeConstants.LONG: + case TypeConstants.DOUBLE: + if (oldC != TypeConstants.ARRAY) + { + counter++; + } + default: + { + counter++; + break; + } + case TypeConstants.METHOD_ARGUMENTS_CLOSE: + { + return counter; + } + } + oldC = c; + c = descriptor.charAt(index++); + } + } + private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTargetFinder branchTargetFinder, CodeAttributeEditor codeAttributeEditor, int arityIndex, From 7150365c418cfa028fd29add24aff7e6eda2d235 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 24 Mar 2022 22:45:07 +0100 Subject: [PATCH 108/195] Don't merge lambda's that don't have exactly 1 constructor --- .../kotlin/KotlinLambdaGroupBuilder.java | 4 ++++ .../optimize/kotlin/KotlinLambdaMerger.java | 19 +++++++++++++++---- 2 files changed, 19 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 263c3f40e..2ce85e3a5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -133,6 +133,10 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void mergeLambdaClass(ProgramClass lambdaClass) { + if (!KotlinLambdaMerger.lambdaClassHasExactlyOneInitConstructor(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged because it has more than 1 constructor."); + } // update optimisation info of lambda to show lambda has been merged or is going to be merged ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index cf019dbf5..0de688f53 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -4,10 +4,7 @@ import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.Configuration; -import proguard.classfile.ClassConstants; -import proguard.classfile.ClassPool; -import proguard.classfile.Clazz; -import proguard.classfile.ProgramClass; +import proguard.classfile.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.util.ClassInitializer; @@ -251,4 +248,18 @@ public static boolean shouldMerge(ProgramClass lambdaClass) && optimizationInfo.getLambdaGroup() == null && optimizationInfo.mayBeMerged(); } + + public static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambdaClass) + { + int count = 0; + for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) + { + Method method = lambdaClass.methods[methodIndex]; + if (method.getName(lambdaClass).equals(ClassConstants.METHOD_NAME_INIT)) + { + count++; + } + } + return count == 1; + } } From 809f19c01f09ff642cde4e8692c2cbe31fd9c04c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:06:22 +0200 Subject: [PATCH 109/195] Exclude certain lambda classes from merging Don't merge lambda classes that have one of the following properties: * they contain 'unexpected' methods, i.e. methods other than ``, `` and `invoke` * they have no `` constructor or more than 1 * their specific `invoke` method is called directly on the lambda class itself (not via the Function interface) --- .../kotlin/KotlinLambdaGroupBuilder.java | 8 +-- .../optimize/kotlin/KotlinLambdaMerger.java | 62 +++++++++++++++++++ 2 files changed, 66 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 2ce85e3a5..663afb022 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -133,10 +133,10 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void mergeLambdaClass(ProgramClass lambdaClass) { - if (!KotlinLambdaMerger.lambdaClassHasExactlyOneInitConstructor(lambdaClass)) - { - throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged because it has more than 1 constructor."); - } + KotlinLambdaMerger.ensureCanMerge(lambdaClass); + + ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); + // update optimisation info of lambda to show lambda has been merged or is going to be merged ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 0de688f53..b39a41246 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -249,6 +249,47 @@ public static boolean shouldMerge(ProgramClass lambdaClass) && optimizationInfo.mayBeMerged(); } + public static void ensureCanMerge(ProgramClass lambdaClass) throws IllegalArgumentException + { + if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it has more than 1 constructor."); + } + else if (!lambdaClassHasNoUnexpectedMethods(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it contains unexpected methods."); + } + else if (!lambdaClassIsNotDirectlyInvoked(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it is directly invoked with its specific invoke method."); + } + } + + public static boolean canMerge(ProgramClass lambdaClass) + { + return lambdaClassHasExactlyOneInitConstructor(lambdaClass) && lambdaClassHasNoUnexpectedMethods(lambdaClass); + } + + public static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) + { + for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) + { + Method method = lambdaClass.methods[methodIndex]; + String methodName = method.getName(lambdaClass); + switch (methodName) + { + case ClassConstants.METHOD_NAME_INIT: + case ClassConstants.METHOD_NAME_CLINIT: + case KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE: + break; + default: + logger.warn("Lambda class {} contains an unexpected method: {}", lambdaClass, ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), methodName, method.getDescriptor(lambdaClass))); + return false; + } + } + return true; + } + public static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambdaClass) { int count = 0; @@ -260,6 +301,27 @@ public static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambd count++; } } + if (count != 1) + { + logger.warn("Lambda class {} has {} constructors.", lambdaClass, count); + } return count == 1; } + + public static boolean lambdaClassIsNotDirectlyInvoked(ProgramClass lambdaClass) + { + Method invokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod(lambdaClass, false); + if (invokeMethod == null) + { + return true; + } + MethodReferenceFinder methodReferenceFinder = new MethodReferenceFinder(invokeMethod, lambdaClass); + lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, new AttributeVisitor() { + @Override + public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { + enclosingMethodAttribute.referencedClass.constantPoolEntriesAccept(methodReferenceFinder); + } + }); + return !methodReferenceFinder.methodReferenceFound(); + } } From c4832fd72e9680a2c68a126bdaaaf19097bf9103 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:06:51 +0200 Subject: [PATCH 110/195] Set optimisation info on lambda groups, when created --- .../java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 663afb022..0c4dffc49 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -76,6 +76,8 @@ private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName lambdaGroupName, KotlinLambdaMerger.NAME_KOTLIN_LAMBDA); ProgramClass lambdaGroup = initialBuilder.getProgramClass(); + lambdaGroup.accept(new ProgramClassOptimizationInfoSetter()); + // The new builder receives the class pools, such that references can be added when necessary return new ClassBuilder(lambdaGroup, programClassPool, libraryClassPool); } From 8f2cf0fa529026bb47a56dac3897b5500a19e076 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:07:10 +0200 Subject: [PATCH 111/195] Set lambda group class as the target class of merged lambda's --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 0c4dffc49..2dd37609b 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -141,7 +141,8 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // update optimisation info of lambda to show lambda has been merged or is going to be merged ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); - optimizationInfo.setLambdaGroup(this.classBuilder.getProgramClass()); + optimizationInfo.setLambdaGroup(lambdaGroup); + optimizationInfo.setTargetClass(lambdaGroup); // merge any inner lambda's before merging the current lambda lambdaClass.attributeAccept(Attribute.INNER_CLASSES, From 52217a56a0e67bd375f88be10b87fbe4f997ef79 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:11:11 +0200 Subject: [PATCH 112/195] Support merging lambda classes that have conflicting constructors All fields in a lambda class are renamed according to their descriptor and order of occurrence in the class, except for the `INSTANCE` field, which is never renamed, to avoid issues with shared UTF8 constants (out of practical experience - a more decente solution might be needed) --- .../kotlin/KotlinLambdaGroupBuilder.java | 60 ++++++++++++++++--- 1 file changed, 52 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 2dd37609b..8208d294e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -157,14 +157,23 @@ private void mergeLambdaClass(ProgramClass lambdaClass) ), // don't revisit the current lambda null))); + canonicalizeLambdaClassFields(lambdaClass); + // Add interfaces of lambda class to the lambda group // TODO: ensure that only Function interfaces are added lambdaClass.interfaceConstantsAccept(this.interfaceAdder); - ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); logger.debug("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); + // First copy the constructors to the lambda group + Method initMethod = copyOrMergeLambdaInitIntoLambdaGroup(lambdaClass); + + String constructorDescriptor = initMethod.getDescriptor(lambdaGroup); + + // Then inline the specific invoke methods into the bridge invoke methods, within the lambda class inlineLambdaInvokeMethods(lambdaClass); + + // and copy the bridge invoke methods to the lambda group ProgramMethod copiedMethod = copyLambdaInvokeToLambdaGroup(lambdaClass); int arity = ClassUtil.internalMethodParameterCount(copiedMethod.getDescriptor(lambdaGroup)); if (arity == 1 && lambdaClass.extendsOrImplements(KotlinLambdaMerger.NAME_KOTLIN_FUNCTIONN) @@ -174,15 +183,32 @@ private void mergeLambdaClass(ProgramClass lambdaClass) } int lambdaClassId = getInvokeMethodBuilder(arity).addCallTo(copiedMethod); - Method initMethod = copyLambdaInitToLambdaGroup(lambdaClass); - String constructorDescriptor = initMethod.getDescriptor(lambdaGroup); - initMethod.accept(lambdaGroup, this.initUpdater); // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, constructorDescriptor); optimizationInfo.setLambdaGroupClassId(lambdaClassId); } + private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) + { + FieldRenamer fieldRenamer = new FieldRenamer(true); + // Note: the order of the fields is not necessarily the order in which they are assigned + // For now, let's assume the order matches the order in which they are assigned. + lambdaClass.fieldsAccept( + new MemberVisitor() { + @Override + public void visitProgramField(ProgramClass programClass, ProgramField programField) { + // Assumption: the only name clash of fields of different classes is + // for fields with the name "INSTANCE". We don't need these fields anyway, so we don't rename them. + // TODO: handle name clashes correctly + if (!programField.getName(programClass).equals("INSTANCE")) + { + fieldRenamer.visitProgramField(programClass, programField); + } + } + }); + } + private void inlineMethodsInsideClass(ProgramClass lambdaClass) { lambdaClass.accept(new ProgramClassOptimizationInfoSetter()); @@ -218,18 +244,36 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM inlineMethodsInsideClass(lambdaClass); } - private ProgramMethod copyLambdaInitToLambdaGroup(ProgramClass lambdaClass) + private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaClass) { + ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); logger.trace("Copying method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); - ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); + ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); //, 0, AccessConstants.PRIVATE); if (initMethod == null) { throw new NullPointerException("No method was found in lambda class " + lambdaClass); } + logger.trace(" method of lambda class {}: {}", lambdaClass, ClassUtil.externalFullMethodDescription(lambdaClass.getName(), initMethod.getAccessFlags(), initMethod.getName(lambdaClass), initMethod.getDescriptor(lambdaClass))); + String oldInitDescriptor = initMethod.getDescriptor(lambdaClass); String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; - initMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), ClassConstants.METHOD_NAME_INIT, newInitDescriptor, AccessConstants.PUBLIC)); - return (ProgramMethod)this.classBuilder.getProgramClass().findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + + // Check whether an init method with this descriptor exists already + ProgramMethod existingInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + + if (existingInitMethod != null) + { + //logger.info("Multiple lambda classes (of which {}) with the same descriptor ({}) are merged in the same lambda group ({}).", lambdaClass, oldInitDescriptor, this.classBuilder.getProgramClass()); + //new ClassEditor(lambdaGroup).removeMethod(existingInitMethod); + return existingInitMethod; + } + + initMethod.accept(lambdaClass, new MethodCopier(lambdaGroup, ClassConstants.METHOD_NAME_INIT, newInitDescriptor, AccessConstants.PUBLIC));//, true)); + ProgramMethod newInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + + // Add the necessary instructions to entirely new methods + newInitMethod.accept(lambdaGroup, this.initUpdater); + return newInitMethod; } private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) From 9869a3eb308ea2fe473027bed5c8265c37b67aae Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:16:39 +0200 Subject: [PATCH 113/195] Add optimisation info to all program classes before merging --- .../optimize/kotlin/KotlinLambdaMerger.java | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index b39a41246..202743ca6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -5,12 +5,14 @@ import proguard.AppView; import proguard.Configuration; import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.EnclosingMethodAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.util.ClassInitializer; +import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; -import proguard.io.ExtraDataEntryNameMap; -import proguard.obfuscate.MappingPrinter; import proguard.optimize.info.ProgramClassOptimizationInfo; import proguard.optimize.info.ProgramClassOptimizationInfoSetter; import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; @@ -21,9 +23,6 @@ import proguard.shrink.*; import proguard.util.PrintWriterUtil; import proguard.util.ProcessingFlags; - -import java.io.File; -import java.io.IOException; import java.io.PrintWriter; public class KotlinLambdaMerger implements Pass { @@ -98,9 +97,9 @@ public void execute(AppView appView) throws Exception lambdaClassPool.classesAccept(packageGrouper); // add optimisation info to the lambda's, so that it can be filled out later - lambdaClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); - lambdaClassPool.classesAccept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter())); + appView.programClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); + appView.programClassPool.classesAccept(new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter())); ClassPool lambdaGroupClassPool = new ClassPool(); ClassPool notMergedLambdaClassPool = new ClassPool(); From 63168e91ed3f87555c8b0754c6848e55b2d01fc7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:17:36 +0200 Subject: [PATCH 114/195] Method for looking up (non)-bridge invoke method of lambda --- .../kotlin/KotlinLambdaGroupBuilder.java | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 8208d294e..9e30335ea 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -284,8 +284,8 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface // - a specific method that contains the implementation of the lambda // Assumption: the specific invoke method has been inlined into the bridge invoke method, such that - // copying the bridge method to the lambda group is sufficient to retrieve the full implementation - ProgramMethod invokeMethod = getInvokeMethod(lambdaClass); + // copying the bridge method to th e lambda group is sufficient to retrieve the full implementation + ProgramMethod invokeMethod = getBridgeInvokeMethod(lambdaClass); if (invokeMethod == null) { throw new NullPointerException("No invoke method was found in lambda class " + lambdaClass); @@ -310,23 +310,29 @@ private static String createDerivedInvokeMethodName(ProgramClass lambdaClass) * invoke method is returned. If no invoke method was found, then null is returned. * @param lambdaClass the lambda class of which a (bridge) invoke method is to be returned */ - private static ProgramMethod getInvokeMethod(ProgramClass lambdaClass) + private static ProgramMethod getBridgeInvokeMethod(ProgramClass lambdaClass) { // Assuming that all specific invoke methods have been inlined into the bridge invoke methods // we can take the bridge invoke method (which overrides the invoke method of the FunctionX interface) - ProgramMethod nonBridgeInvokeMethod = null; + return getInvokeMethod(lambdaClass, true); + } + + public static ProgramMethod getInvokeMethod(ProgramClass lambdaClass, boolean isBridgeMethod) + { + ProgramMethod invokeMethod = null; for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { ProgramMethod method = lambdaClass.methods[methodIndex]; if (method.getName(lambdaClass).equals(KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE)) { - if ((method.u2accessFlags & AccessConstants.BRIDGE) != 0) { - // we have found the bridge invoke method + if ((isBridgeMethod && (method.u2accessFlags & AccessConstants.BRIDGE) != 0) || + (!isBridgeMethod && (method.u2accessFlags & AccessConstants.BRIDGE) == 0)) { + // we have found the bridge/non-bridge invoke method return method; } - nonBridgeInvokeMethod = method; + invokeMethod = method; } } - return nonBridgeInvokeMethod; + return invokeMethod; } /** From 0193e4fb9ec58ec913c035587714c2e417d0364a Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 27 Mar 2022 22:22:01 +0200 Subject: [PATCH 115/195] KotlinLambdaEnclosingMethodUpdater extended The code that is searched for instructions that use the lambda class is: * the enclosing method * all other methods of the enclosing class * all methods of the classes that are in the same package as the lambda class, if one of the class constants references the lambda class --- .../KotlinLambdaEnclosingMethodUpdater.java | 191 +++++++++++++++++- 1 file changed, 181 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 559bf9d56..fecde9fa2 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -6,24 +6,22 @@ import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; +import proguard.classfile.constant.AnyMethodrefConstant; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.Constant; -import proguard.classfile.constant.RefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; -import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.BranchTargetFinder; -import proguard.classfile.visitor.MemberVisitor; -import proguard.configuration.ConfigurationLoggingAdder; -import proguard.configuration.ConfigurationLoggingInstructionSequencesReplacer; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; +import proguard.optimize.peephole.RetargetedInnerClassAttributeRemover; import java.util.*; -public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, MemberVisitor { +public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, AttributeVisitor, MemberVisitor { private final ClassPool programClassPool; private final ClassPool libraryClassPool; @@ -37,7 +35,9 @@ public class KotlinLambdaEnclosingMethodUpdater implements AttributeVisitor, Mem private boolean visitEnclosingCode = false; private static final Logger logger = LogManager.getLogger(KotlinLambdaEnclosingMethodUpdater.class); private Clazz currentLambdaClass; + private Clazz currentEnclosingClass; private SortedSet offsetsWhereLambdaIsReferenced; + private static final RetargetedInnerClassAttributeRemover retargetedInnerClassAttributeRemover = new RetargetedInnerClassAttributeRemover(); public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, @@ -70,8 +70,37 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr visitEnclosingMethodAttribute = true; currentLambdaClass = lambdaClass; + currentEnclosingClass = enclosingClass; + /* enclosingMethodAttribute.referencedMethodAccept(this); - currentLambdaClass = null; + if (enclosingMethodAttribute.referencedMethod == null) + { + logger.warn("No enclosing method was found for {}, so lambda merging will break the code that uses this class.", lambdaClass); + logger.info("Assuming that {} is used in the constructor of {}", lambdaClass, enclosingClass); + Method clinitMethod = enclosingClass.findMethod(ClassConstants.METHOD_NAME_CLINIT, null); + if (clinitMethod == null) + { + logger.warn("No method found for {}, so the usage of {} will not be updated ...", enclosingClass, lambdaClass); + } + else + { + clinitMethod.accept(enclosingClass, this); + } + } + else { + Method inlinedMethod = enclosingClass.findMethod(enclosingMethodAttribute.referencedMethod.getName(enclosingClass) + "$$forInline", null); + if (inlinedMethod != null) + { + logger.info("The enclosing method of lambda class {} has an inlined version, which will also be updated.", lambdaClass); + inlinedMethod.accept(enclosingClass, this); + } + } + */ + + // Visit all methods of the enclosing class, assuming that those are the only methods that can contain + // references to this lambda class. + enclosingClass.methodsAccept(this); + visitEnclosingMethodAttribute = false; // remove lambda class as inner class of its enclosing class @@ -81,8 +110,19 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr // remove all references to lambda class from the constant pool of its enclosing class enclosingClass.accept(new ConstantPoolShrinker()); + // Also update any references would occur in other classes of the same package. + //ClassCounter classCounter = new ClassCounter(); + this.programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", + new ClassNameFilter(enclosingClass.getName(), (ClassVisitor)null, + //new MultiClassVisitor( + this))); //, classCounter)))); + + //logger.info("{} classes visited to update references to {}", classCounter.getCount(), lambdaClass); + // ensure that the newly created lambda group is part of the resulting output as a dependency of this enclosing class - this.extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); + extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); + currentLambdaClass = null; + currentEnclosingClass = null; } @Override @@ -153,7 +193,9 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence { // TODO: ensure that the correct method is selected // TODO: decide what to do if multiple methods exist - Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); + Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); + Method specificInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, false); + Method bridgeInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, true); return new Instruction[][][] { // Empty closure lambda's @@ -194,13 +236,142 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence }, { builder.invokespecial(currentLambdaClass, initMethod).__(), + builder.iconst(classId) .iconst(arity) .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, constructorDescriptor) .__() + }, + // Direct invocation of named lambda's + { + builder.invokevirtual(currentLambdaClass, specificInvokeMethod) + .__(), + + builder.invokevirtual(lambdaGroup.getName(), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, specificInvokeMethod.getDescriptor(currentLambdaClass)) + .__() + }, + { + builder.invokevirtual(currentLambdaClass, bridgeInvokeMethod) + .__(), + + builder.invokevirtual(lambdaGroup.getName(), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, bridgeInvokeMethod.getDescriptor(currentLambdaClass)) + .__() } }; } + + @Override + public void visitAnyClass(Clazz clazz) {} + + @Override + public void visitProgramClass(ProgramClass programClass) { + ClassReferenceFinder classReferenceFinder = new ClassReferenceFinder(this.currentLambdaClass); + programClass.constantPoolEntriesAccept(classReferenceFinder); + if (classReferenceFinder.classReferenceFound()) + { + if (currentEnclosingClass == null) + { + logger.warn("Lambda class {} is referenced by {}, while no enclosing class was linked to this lambda class.", currentLambdaClass, programClass); + } + else if (currentEnclosingClass.equals(programClass)) + { + logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class.", currentLambdaClass, programClass); + } + // This programClass references the lambda class, so any referencing instructions + // must be updated. + programClass.methodsAccept(this); + + // remove any old links between lambda's and their inner classes + programClass.accept(KotlinLambdaEnclosingMethodUpdater.retargetedInnerClassAttributeRemover); + + // Remove any constants referring to the old lambda class. + programClass.accept(new ConstantPoolShrinker()); + this.extraDataEntryNameMap.addExtraClassToClass(programClass, currentLambdaClass); + } + } +} + +class ClassReferenceFinder implements ConstantVisitor +{ + private final Clazz referencedClass; + private boolean classIsReferenced = false; + + public ClassReferenceFinder(Clazz referencedClass) + { + this.referencedClass = referencedClass; + } + + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + if (classConstant.referencedClass != null && classConstant.referencedClass.equals(referencedClass)) + { + this.classIsReferenced = true; + } + } + + public boolean classReferenceFound() + { + return this.classIsReferenced; + } +} + +class MethodReferenceFinder implements ConstantVisitor +{ + private final Method referencedMethod; + private final Clazz referencedClass; + private boolean methodIsReferenced = false; + + public MethodReferenceFinder(Method referencedMethod, Clazz referencedClass) + { + this.referencedMethod = referencedMethod; + this.referencedClass = referencedClass; + } + + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) + { + if ((anyMethodrefConstant.referencedMethod != null && anyMethodrefConstant.referencedMethod.equals(referencedMethod))) + /*|| + (anyMethodrefConstant.referencedClass != null && anyMethodrefConstant.referencedClass.equals(this.referencedClass) && + anyMethodrefConstant.getName(clazz).equals(this.referencedMethod.getName(this.referencedClass)) && + anyMethodrefConstant.referencedMethod.getDescriptor(anyMethodrefConstant.referencedClass).equals(this.referencedMethod.getDescriptor(this.referencedClass)))) + + */ + { + this.methodIsReferenced = true; + } + } + + public boolean methodReferenceFound() + { + return this.methodIsReferenced; + } +} + +class EnclosingClassRemover implements AttributeVisitor +{ + private final Clazz classToBeRemoved; + + public EnclosingClassRemover(Clazz clazz) + { + this.classToBeRemoved = clazz; + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) + { + if (this.classToBeRemoved.equals(enclosingMethodAttribute.referencedClass)) + { + + } + } + } class InnerClassRemover implements AttributeVisitor, InnerClassesInfoVisitor From 66bc9b688d3b74d22710c33e5852568a466060b7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:06:46 +0200 Subject: [PATCH 116/195] New configuration option: -lambdamergingafteroptimizing --- base/src/main/java/proguard/Configuration.java | 5 +++++ base/src/main/java/proguard/ConfigurationConstants.java | 1 + base/src/main/java/proguard/ConfigurationParser.java | 1 + base/src/main/java/proguard/ConfigurationWriter.java | 1 + base/src/main/java/proguard/ProGuard.java | 7 ++++++- 5 files changed, 14 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 4d970ca30..5dcf3a527 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -211,6 +211,11 @@ public class Configuration */ public boolean mergeKotlinLambdaClasses; + /** + * Specifies whether Kotlin lambda classes have to be merged before or after the other optimisations. + */ + public boolean lambdaMergingAfterOptimizing; + /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index 20a8c1922..65e9efebc 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -68,6 +68,7 @@ public class ConfigurationConstants public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively"; public static final String PRINT_LAMBDAGROUP_MAPPING_OPTION = "-printlambdagroupmapping"; public static final String MERGE_KOTLIN_LAMBDA_CLASSES_OPTION = "-mergekotlinlambdaclasses"; + public static final String LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION = "-lambdamergingafteroptimizing"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; public static final String PRINT_MAPPING_OPTION = "-printmapping"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index 96d3f639f..0b79f76ee 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -192,6 +192,7 @@ public void parse(Configuration configuration) else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); else if (ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION .startsWith(nextWord)) configuration.printLambdaGroupMapping = parseOptionalFile(); else if (ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION .startsWith(nextWord)) configuration.mergeKotlinLambdaClasses = parseNoArgument(true); + else if (ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.lambdaMergingAfterOptimizing = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); diff --git a/base/src/main/java/proguard/ConfigurationWriter.java b/base/src/main/java/proguard/ConfigurationWriter.java index dfb3aa8e5..c87f91310 100644 --- a/base/src/main/java/proguard/ConfigurationWriter.java +++ b/base/src/main/java/proguard/ConfigurationWriter.java @@ -123,6 +123,7 @@ public void write(Configuration configuration) throws IOException writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); writeOption(ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION, configuration.printLambdaGroupMapping); writeOption(ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION, configuration.mergeKotlinLambdaClasses); + writeOption(ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION, configuration.lambdaMergingAfterOptimizing); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index ee338c8db..ef9c94574 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -178,7 +178,7 @@ public void execute() throws Exception shrink(false); } - if (configuration.mergeKotlinLambdaClasses) + if (configuration.mergeKotlinLambdaClasses && !configuration.lambdaMergingAfterOptimizing) { mergeKotlinLambdaClasses(); } @@ -200,6 +200,11 @@ public void execute() throws Exception linearizeLineNumbers(); } + if (configuration.mergeKotlinLambdaClasses && configuration.lambdaMergingAfterOptimizing) + { + mergeKotlinLambdaClasses(); + } + if (configuration.obfuscate) { obfuscate(); From fcb6606325c162b4450b2d2ee220ab99510d512b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:08:02 +0200 Subject: [PATCH 117/195] Updated references to lambda class in all classes of same package --- .../KotlinLambdaEnclosingMethodUpdater.java | 18 +++++------------- .../kotlin/KotlinLambdaGroupBuilder.java | 14 +++++++++++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index fecde9fa2..932ac6eb6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -41,6 +41,7 @@ public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, Attribu public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, + ProgramClass lambdaClass, ProgramClass lambdaGroup, int classId, int arity, @@ -49,6 +50,7 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, { this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; + this.currentLambdaClass = lambdaClass; this.lambdaGroup = lambdaGroup; this.classId = classId; this.arity = arity; @@ -110,19 +112,8 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr // remove all references to lambda class from the constant pool of its enclosing class enclosingClass.accept(new ConstantPoolShrinker()); - // Also update any references would occur in other classes of the same package. - //ClassCounter classCounter = new ClassCounter(); - this.programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", - new ClassNameFilter(enclosingClass.getName(), (ClassVisitor)null, - //new MultiClassVisitor( - this))); //, classCounter)))); - - //logger.info("{} classes visited to update references to {}", classCounter.getCount(), lambdaClass); - // ensure that the newly created lambda group is part of the resulting output as a dependency of this enclosing class extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); - currentLambdaClass = null; - currentEnclosingClass = null; } @Override @@ -130,7 +121,8 @@ public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclos { // the given class must be the class that defines the lambda // the given method must be the method where the lambda is defined - if (!visitEnclosingMethodAttribute || visitEnclosingMethod) { + + if (visitEnclosingMethod) { return; } visitEnclosingMethod = true; @@ -159,7 +151,7 @@ public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclos // the given code attribute must contain the original definition of the lambda: // - load LambdaClass.INSTANCE // - or instantiate LambdaClass() - if (!visitEnclosingMethodAttribute || !visitEnclosingMethod || visitEnclosingCode) + if (!visitEnclosingMethod || visitEnclosingCode) { return; } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 9e30335ea..9ee142503 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -342,15 +342,23 @@ public static ProgramMethod getInvokeMethod(ProgramClass lambdaClass, boolean is */ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity, String constructorDescriptor) { - logger.debug("Updating instantiation of {} in enclosing method to use id {}.", lambdaClass.getName(), lambdaClassId); - lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, + logger.info("Updating instantiation of {} in enclosing method(s) to use id {}.", + ClassUtil.externalClassName(lambdaClass.getName()), lambdaClassId); + KotlinLambdaEnclosingMethodUpdater enclosingMethodUpdater = new KotlinLambdaEnclosingMethodUpdater(this.programClassPool, this.libraryClassPool, + lambdaClass, this.classBuilder.getProgramClass(), lambdaClassId, arity, constructorDescriptor, - this.extraDataEntryNameMap)); + this.extraDataEntryNameMap); + lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, enclosingMethodUpdater); + + // Also update any references that would occur in other classes of the same package. + this.programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", + new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, + enclosingMethodUpdater))); } private void addInitConstructors() From 971428b2552fbbd7b321fb22b34400c925da0fb0 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:09:00 +0200 Subject: [PATCH 118/195] Find inner lambda's by looking for class constants of lambda classes --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 9ee142503..fecf8ddaf 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -157,6 +157,19 @@ private void mergeLambdaClass(ProgramClass lambdaClass) ), // don't revisit the current lambda null))); + // possibly, some inner lambda classes were not mentioned in the inner classes attribute of this lambda, + // so we have to find them by looking for their occurrence in this lambda class + // Normally, this should be redundant if all inner lambda classes are correctly mentioned as inner classes. + lambdaClass.constantPoolEntriesAccept(new ClassConstantToClassVisitor( + new ClassNameFilter(lambdaClass.getName(), + (ClassVisitor)null, + new ImplementedClassFilter( + programClassPool.getClass(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA), + false, + new MultiClassVisitor(this, + new SimpleClassPrinter(true)), + null)))); + canonicalizeLambdaClassFields(lambdaClass); // Add interfaces of lambda class to the lambda group From 5f50d839d8a5582cec7ef8b079e52561cefeee89 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:12:14 +0200 Subject: [PATCH 119/195] Formal rules for lambda's to be merged To be merged, a lambda class must * have no static methods that can be called from outside the lambda class * not be invoked directly instead of via its function interface * have no unexpected methods if the configuration disallows this * have no extra non-static methods if they cannot be inlined into the bridge invoke method due to size limitations --- .../kotlin/KotlinLambdaGroupBuilder.java | 2 +- .../optimize/kotlin/KotlinLambdaMerger.java | 22 ++++++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index fecf8ddaf..a7026c0f9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -135,7 +135,7 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void mergeLambdaClass(ProgramClass lambdaClass) { - KotlinLambdaMerger.ensureCanMerge(lambdaClass); + KotlinLambdaMerger.ensureCanMerge(lambdaClass, configuration.mergeLambdaClassesWithUnexpectedMethods); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 202743ca6..d6a951d1f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -248,19 +248,31 @@ public static boolean shouldMerge(ProgramClass lambdaClass) && optimizationInfo.mayBeMerged(); } - public static void ensureCanMerge(ProgramClass lambdaClass) throws IllegalArgumentException + public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaClassesWithUnexpectedMethods) throws IllegalArgumentException { + String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it has more than 1 constructor."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it has more than 1 constructor."); } - else if (!lambdaClassHasNoUnexpectedMethods(lambdaClass)) + else if (!lambdaClassHasNoAccessibleStaticMethods(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it contains unexpected methods."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains a static method that could be used outside the class itself."); } else if (!lambdaClassIsNotDirectlyInvoked(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + lambdaClass + " cannot be merged, because it is directly invoked with its specific invoke method."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it is directly invoked with its specific invoke method."); + } + else if (!lambdaClassHasNoUnexpectedMethods(lambdaClass)) + { + if (!mergeLambdaClassesWithUnexpectedMethods) + { + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods."); + } + else if (!lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods that contain too much code all together."); + } } } From 75288d63fec0a3b7db548ac428dc61d1e7097c29 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:13:28 +0200 Subject: [PATCH 120/195] Mark lambda groups as used before shrinking them --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d6a951d1f..549fce60f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -208,9 +208,12 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla lambdaGroupClassPool.classesAccept(new InterfaceUsageMarker( classUsageMarker)); + // mark the lambda groups themselves as used // remove the unused parts of the lambda groups, such as the inlined invoke helper methods // and make sure that the line numbers are updated - lambdaGroupClassPool.classesAccept(new MultiClassVisitor( + //ClassPool newLambdaGroupClassPool = new ClassPool(); + lambdaGroupClassPool.classesAccept(new UsedClassFilter(simpleUsageMarker, + new MultiClassVisitor( new ClassShrinker(simpleUsageMarker), new LineNumberLinearizer())); } From 698cc26fba2ec038f3ad787094724ee380592f2f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:14:42 +0200 Subject: [PATCH 121/195] New configuration option: -mergelambdaclasseswithunexpectedmethods This configuration option is required by commit 5f50d839d8a5582cec7ef8b079e52561cefeee89 --- base/src/main/java/proguard/Configuration.java | 5 +++++ base/src/main/java/proguard/ConfigurationConstants.java | 1 + base/src/main/java/proguard/ConfigurationParser.java | 1 + base/src/main/java/proguard/ConfigurationWriter.java | 1 + 4 files changed, 8 insertions(+) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 5dcf3a527..ae02a52e0 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -216,6 +216,11 @@ public class Configuration */ public boolean lambdaMergingAfterOptimizing; + /** + * Specifies whether Kotlin lambda classes may be merged if they contain unexpected methods. + */ + public boolean mergeLambdaClassesWithUnexpectedMethods; + /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index 65e9efebc..9828a0002 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -69,6 +69,7 @@ public class ConfigurationConstants public static final String PRINT_LAMBDAGROUP_MAPPING_OPTION = "-printlambdagroupmapping"; public static final String MERGE_KOTLIN_LAMBDA_CLASSES_OPTION = "-mergekotlinlambdaclasses"; public static final String LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION = "-lambdamergingafteroptimizing"; + public static final String LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS = "-mergelambdaclasseswithunexpectedmethods"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; public static final String PRINT_MAPPING_OPTION = "-printmapping"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index 0b79f76ee..e4132e311 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -193,6 +193,7 @@ public void parse(Configuration configuration) else if (ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION .startsWith(nextWord)) configuration.printLambdaGroupMapping = parseOptionalFile(); else if (ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION .startsWith(nextWord)) configuration.mergeKotlinLambdaClasses = parseNoArgument(true); else if (ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.lambdaMergingAfterOptimizing = parseNoArgument(true); + else if (ConfigurationConstants.LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS .startsWith(nextWord)) configuration.mergeLambdaClassesWithUnexpectedMethods = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); diff --git a/base/src/main/java/proguard/ConfigurationWriter.java b/base/src/main/java/proguard/ConfigurationWriter.java index c87f91310..8f31c7aa8 100644 --- a/base/src/main/java/proguard/ConfigurationWriter.java +++ b/base/src/main/java/proguard/ConfigurationWriter.java @@ -124,6 +124,7 @@ public void write(Configuration configuration) throws IOException writeOption(ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION, configuration.printLambdaGroupMapping); writeOption(ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION, configuration.mergeKotlinLambdaClasses); writeOption(ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION, configuration.lambdaMergingAfterOptimizing); + writeOption(ConfigurationConstants.LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS, configuration.mergeLambdaClassesWithUnexpectedMethods); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); From c7c87451b5c9ecbfde1da1ff25f2ce60417abde8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:15:35 +0200 Subject: [PATCH 122/195] Methods for checking lambda merging preconditions Required by commit 5f50d839d8a5582cec7ef8b079e52561cefeee89 --- .../optimize/kotlin/KotlinLambdaMerger.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 549fce60f..1b1af3ff1 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -284,6 +284,43 @@ public static boolean canMerge(ProgramClass lambdaClass) return lambdaClassHasExactlyOneInitConstructor(lambdaClass) && lambdaClassHasNoUnexpectedMethods(lambdaClass); } + public static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lambdaClass) + { + for (int index = 0; index < lambdaClass.u2methodsCount; index++) + { + ProgramMethod method = lambdaClass.methods[index]; + if (!method.getName(lambdaClass).equals(ClassConstants.METHOD_NAME_CLINIT) + && (method.getAccessFlags() & AccessConstants.STATIC) != 0 + && ((method.getAccessFlags() & AccessConstants.PRIVATE) == 0)) + { + logger.warn("Lambda class {} contains a static method that cannot be merged: {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), method.getName(lambdaClass), method.getDescriptor(lambdaClass))); + return false; + } + } + return true; + } + + public static boolean lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(ProgramClass lambdaClass) + { + String methodNameRegularExpression = "!" + ClassConstants.METHOD_NAME_INIT; + methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; + // TODO: create a dedicated CodeSizeCounter visitor + final int[] totalCodeSize = {0}; + lambdaClass.methodsAccept(new MemberNameFilter(methodNameRegularExpression, + new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + totalCodeSize[0] += codeAttribute.u4codeLength; + } + }))); + // TODO: replace 7000 by constant or + // Integer.parseInt(System.getProperty("maximum.resulting.code.length", "7000")); + return totalCodeSize[0] <= 7000; + } + public static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) { for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) From 82950394bf0887a6ef981df649e68c9b30cf1336 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:16:05 +0200 Subject: [PATCH 123/195] Optimised imports --- base/src/main/java/proguard/ProGuard.java | 1 - .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 ++++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index ef9c94574..1fe577cc0 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -29,7 +29,6 @@ import proguard.configuration.ConfigurationLoggingAdder; import proguard.evaluation.IncompleteClassHierarchyException; import proguard.configuration.InitialStateInfo; -import proguard.io.ExtraDataEntryNameMap; import proguard.logging.Logging; import proguard.mark.Marker; import proguard.obfuscate.NameObfuscationReferenceFixer; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index a7026c0f9..dc05b3f84 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -5,7 +5,10 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.visitor.*; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; +import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; +import proguard.classfile.attribute.visitor.ModifiedAllInnerClassesInfoVisitor; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.InterfaceAdder; import proguard.classfile.util.ClassUtil; From 029c03738e9c22245cb6d859bb9737bfea7955c9 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:17:53 +0200 Subject: [PATCH 124/195] Pretty log printing of lambda class/lambda group names --- .../kotlin/KotlinLambdaClassMerger.java | 2 +- .../KotlinLambdaEnclosingMethodUpdater.java | 10 +++++-- .../kotlin/KotlinLambdaGroupBuilder.java | 28 +++++++++++++++---- .../kotlin/KotlinLambdaGroupInitUpdater.java | 7 ++++- .../optimize/kotlin/KotlinLambdaMerger.java | 4 +-- 5 files changed, 38 insertions(+), 13 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 044f54fb0..154f37c77 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -67,7 +67,7 @@ public void visitClassPool(ClassPool lambdaClassPool) // choose a name for the lambda group // ensure that the lambda group is in the same package as the classes of the class pool String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + NAME_LAMBDA_GROUP; - logger.info("Creating lambda group with name {}", lambdaGroupName); + logger.info("Creating lambda group with name {}", ClassUtil.externalClassName(lambdaGroupName)); // create a lambda group builder KotlinLambdaGroupBuilder lambdaGroupBuilder = new KotlinLambdaGroupBuilder(lambdaGroupName, diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 932ac6eb6..5d65d9455 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -195,7 +195,7 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence // Lambda is 'instantiated' by referring to its static INSTANCE field builder.getstatic(currentLambdaClass.getName(), KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, - "L" + currentLambdaClass.getName() + ";").__(), + ClassUtil.internalTypeFromClassName(currentLambdaClass.getName())).__(), builder.new_(lambdaGroup) .dup() @@ -263,11 +263,15 @@ public void visitProgramClass(ProgramClass programClass) { { if (currentEnclosingClass == null) { - logger.warn("Lambda class {} is referenced by {}, while no enclosing class was linked to this lambda class.", currentLambdaClass, programClass); + logger.warn("Lambda class {} is referenced by {}, while no enclosing class was linked to this lambda class.", + ClassUtil.externalClassName(currentLambdaClass.getName()), + ClassUtil.externalClassName(programClass.getName())); } else if (currentEnclosingClass.equals(programClass)) { - logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class.", currentLambdaClass, programClass); + logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class or the lambda class itself.", + ClassUtil.externalClassName(currentLambdaClass.getName()), + ClassUtil.externalClassName(programClass.getName())); } // This programClass references the lambda class, so any referencing instructions // must be updated. diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index dc05b3f84..dc1efd36b 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -129,7 +129,9 @@ public void visitProgramClass(ProgramClass lambdaClass) { } catch(Exception exception) { - logger.error("Lambda class {} could not be merged: {}", lambdaClass, exception); + logger.error("Lambda class {} could not be merged: {}", + ClassUtil.externalClassName(lambdaClass.getName()), + exception); if (this.notMergedLambdaVisitor != null) { lambdaClass.accept(this.notMergedLambdaVisitor); } @@ -147,6 +149,8 @@ private void mergeLambdaClass(ProgramClass lambdaClass) optimizationInfo.setLambdaGroup(lambdaGroup); optimizationInfo.setTargetClass(lambdaGroup); + logger.info("Looking at inner lambda's of {}", ClassUtil.externalClassName(lambdaClass.getName())); + // merge any inner lambda's before merging the current lambda lambdaClass.attributeAccept(Attribute.INNER_CLASSES, new ModifiedAllInnerClassesInfoVisitor( @@ -179,7 +183,9 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // TODO: ensure that only Function interfaces are added lambdaClass.interfaceConstantsAccept(this.interfaceAdder); - logger.debug("Adding lambda {} to lambda group {}", lambdaClass.getName(), lambdaGroup.getName()); + logger.info("Adding lambda {} to lambda group {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalClassName(lambdaGroup.getName())); // First copy the constructors to the lambda group Method initMethod = copyOrMergeLambdaInitIntoLambdaGroup(lambdaClass); @@ -263,13 +269,20 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaClass) { ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); - logger.trace("Copying method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); + logger.trace("Copying method of {} to lambda group {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalClassName(this.classBuilder.getProgramClass().getName())); ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); //, 0, AccessConstants.PRIVATE); if (initMethod == null) { throw new NullPointerException("No method was found in lambda class " + lambdaClass); } - logger.trace(" method of lambda class {}: {}", lambdaClass, ClassUtil.externalFullMethodDescription(lambdaClass.getName(), initMethod.getAccessFlags(), initMethod.getName(lambdaClass), initMethod.getDescriptor(lambdaClass))); + logger.trace(" method of lambda class {}: {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalFullMethodDescription(lambdaClass.getName(), + initMethod.getAccessFlags(), + initMethod.getName(lambdaClass), + initMethod.getDescriptor(lambdaClass))); String oldInitDescriptor = initMethod.getDescriptor(lambdaClass); String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; @@ -294,7 +307,9 @@ private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaCl private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) { - logger.trace("Copying invoke method of {} to lambda group {}", lambdaClass.getName(), this.classBuilder.getProgramClass().getName()); + logger.trace("Copying invoke method of {} to lambda group {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalClassName(this.classBuilder.getProgramClass().getName())); // Note: the lambda class is expected to contain two invoke methods: // - a bridge method that implements invoke()Ljava/lang/Object; for the Function0 interface @@ -304,7 +319,8 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) ProgramMethod invokeMethod = getBridgeInvokeMethod(lambdaClass); if (invokeMethod == null) { - throw new NullPointerException("No invoke method was found in lambda class " + lambdaClass); + throw new NullPointerException("No invoke method was found in lambda class " + + ClassUtil.externalClassName(lambdaClass.getName())); } String newMethodName = createDerivedInvokeMethodName(lambdaClass); invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), newMethodName, AccessConstants.PRIVATE)); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index 0a4c07943..43942eaff 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -29,7 +29,12 @@ public KotlinLambdaGroupInitUpdater(ClassPool programClassPool, @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - logger.trace("Updating method " + programMethod.getName(programClass) + programMethod.getDescriptor(programClass) + " in class " + programClass); + logger.trace("Updating method {} in class {}", + ClassUtil.externalFullMethodDescription(programClass.getName(), + programMethod.getAccessFlags(), + programMethod.getName(programClass), + programMethod.getDescriptor(programClass)), + ClassUtil.externalClassName(programClass.getName())); programMethod.attributesAccept(programClass, this); } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 1b1af3ff1..daf5567ab 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -334,7 +334,7 @@ public static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass case KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE: break; default: - logger.warn("Lambda class {} contains an unexpected method: {}", lambdaClass, ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), methodName, method.getDescriptor(lambdaClass))); + logger.warn("Lambda class {} contains an unexpected method: {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), methodName, method.getDescriptor(lambdaClass))); return false; } } @@ -354,7 +354,7 @@ public static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambd } if (count != 1) { - logger.warn("Lambda class {} has {} constructors.", lambdaClass, count); + logger.warn("Lambda class {} has {} constructors.", ClassUtil.externalClassName(lambdaClass.getName()), count); } return count == 1; } From 53bae1d2c137dfad04f96dff3500b08b97269b8c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:18:23 +0200 Subject: [PATCH 125/195] Enclosing class cannot be lambda class itself --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 5d65d9455..5277eb1ce 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -267,7 +267,7 @@ public void visitProgramClass(ProgramClass programClass) { ClassUtil.externalClassName(currentLambdaClass.getName()), ClassUtil.externalClassName(programClass.getName())); } - else if (currentEnclosingClass.equals(programClass)) + else if (!currentEnclosingClass.equals(programClass) && !currentLambdaClass.equals(programClass)) { logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class or the lambda class itself.", ClassUtil.externalClassName(currentLambdaClass.getName()), From af15b147feded7002a348106e08481b45f977461 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 7 May 2022 14:18:33 +0200 Subject: [PATCH 126/195] Fix imports --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 1 + 1 file changed, 1 insertion(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index daf5567ab..8267be2c7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -6,6 +6,7 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.EnclosingMethodAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; From 4fce5c1c52404beafdb36d0b7fbcadc7bb5eba28 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:19:25 +0200 Subject: [PATCH 127/195] Remove comment --- .../java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 154f37c77..cac51252e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -88,9 +88,6 @@ public void visitClassPool(ClassPool lambdaClassPool) // let the lambda group visitor visit the newly created lambda group this.lambdaGroupVisitor.visitProgramClass(lambdaGroup); - - // ensure that this newly created class is part of the resulting output - //extraDataEntryNameMap.addExtraClass(lambdaGroup.getName()); } private static String getPackagePrefixOfClasses(ClassPool classPool) From a8d205c3748ce99246b46a408ea993aee89a45cd Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:20:24 +0200 Subject: [PATCH 128/195] Remove unused function name constants --- .../optimize/kotlin/KotlinLambdaMerger.java | 31 ------------------- 1 file changed, 31 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 8267be2c7..a94fd6d8d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -30,28 +30,6 @@ public class KotlinLambdaMerger implements Pass { public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; - public static final String NAME_KOTLIN_FUNCTION1 = "kotlin/jvm/functions/Function1"; - public static final String NAME_KOTLIN_FUNCTION2 = "kotlin/jvm/functions/Function2"; - public static final String NAME_KOTLIN_FUNCTION3 = "kotlin/jvm/functions/Function3"; - public static final String NAME_KOTLIN_FUNCTION4 = "kotlin/jvm/functions/Function4"; - public static final String NAME_KOTLIN_FUNCTION5 = "kotlin/jvm/functions/Function5"; - public static final String NAME_KOTLIN_FUNCTION6 = "kotlin/jvm/functions/Function6"; - public static final String NAME_KOTLIN_FUNCTION7 = "kotlin/jvm/functions/Function7"; - public static final String NAME_KOTLIN_FUNCTION8 = "kotlin/jvm/functions/Function8"; - public static final String NAME_KOTLIN_FUNCTION9 = "kotlin/jvm/functions/Function9"; - public static final String NAME_KOTLIN_FUNCTION10 = "kotlin/jvm/functions/Function10"; - public static final String NAME_KOTLIN_FUNCTION11 = "kotlin/jvm/functions/Function11"; - public static final String NAME_KOTLIN_FUNCTION12 = "kotlin/jvm/functions/Function12"; - public static final String NAME_KOTLIN_FUNCTION13 = "kotlin/jvm/functions/Function13"; - public static final String NAME_KOTLIN_FUNCTION14 = "kotlin/jvm/functions/Function14"; - public static final String NAME_KOTLIN_FUNCTION15 = "kotlin/jvm/functions/Function15"; - public static final String NAME_KOTLIN_FUNCTION16 = "kotlin/jvm/functions/Function16"; - public static final String NAME_KOTLIN_FUNCTION17 = "kotlin/jvm/functions/Function17"; - public static final String NAME_KOTLIN_FUNCTION18 = "kotlin/jvm/functions/Function18"; - public static final String NAME_KOTLIN_FUNCTION19 = "kotlin/jvm/functions/Function19"; - public static final String NAME_KOTLIN_FUNCTION20 = "kotlin/jvm/functions/Function20"; - public static final String NAME_KOTLIN_FUNCTION21 = "kotlin/jvm/functions/Function21"; - public static final String NAME_KOTLIN_FUNCTION22 = "kotlin/jvm/functions/Function22"; public static final String NAME_KOTLIN_FUNCTIONN = "kotlin/jvm/functions/FunctionN"; private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); @@ -228,15 +206,6 @@ private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool library return kotlinLambdaClass; } - private Clazz getKotlinFunction0Interface(ClassPool programClassPool, ClassPool libraryClassPool) - { - Clazz kotlinFunction0Interface = programClassPool.getClass(NAME_KOTLIN_FUNCTION0); - if (kotlinFunction0Interface == null) { - kotlinFunction0Interface = libraryClassPool.getClass(NAME_KOTLIN_FUNCTION0); - } - return kotlinFunction0Interface; - } - /** * Checks whether the given lambda class should still be merged. * Returns true if the lambda class has not yet been merged and is allowed to be merged. From 19525a996fd08103df185a1ff2ee57bb5432e326 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:23:54 +0200 Subject: [PATCH 129/195] 2 new/modified lambda merging requirements * non-INSTANCE fields are not directly accessed by other classes (e.g. $this fields) * total non-constructor method size must not exceed maximum JVM limit * no bootstrap methods + bootstrap method attribute --- .../kotlin/KotlinLambdaGroupBuilder.java | 13 ++-- .../optimize/kotlin/KotlinLambdaMerger.java | 72 +++++++++++++++---- 2 files changed, 66 insertions(+), 19 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index dc1efd36b..9ada578e1 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -29,11 +29,12 @@ */ public class KotlinLambdaGroupBuilder implements ClassVisitor { - public static final String FIELD_NAME_ID = "classId"; - public static final String FIELD_TYPE_ID = "I"; - public static final String FIELD_NAME_PREFIX_FREE_VARIABLE = "freeVar"; - public static final String FIELD_TYPE_FREE_VARIABLE = "Ljava/lang/Object;"; - public static final String METHOD_NAME_SUFFIX_INVOKE = "$invoke"; + public static final String FIELD_NAME_ID = "classId"; + public static final String FIELD_TYPE_ID = "I"; + public static final String FIELD_NAME_PREFIX_FREE_VARIABLE = "freeVar"; + public static final String FIELD_TYPE_FREE_VARIABLE = "Ljava/lang/Object;"; + public static final String METHOD_NAME_SUFFIX_INVOKE = "$invoke"; + protected static final int MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "65535")); private final ClassBuilder classBuilder; private final Configuration configuration; @@ -140,7 +141,7 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void mergeLambdaClass(ProgramClass lambdaClass) { - KotlinLambdaMerger.ensureCanMerge(lambdaClass, configuration.mergeLambdaClassesWithUnexpectedMethods); + KotlinLambdaMerger.ensureCanMerge(lambdaClass, configuration.mergeLambdaClassesWithUnexpectedMethods, programClassPool); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index a94fd6d8d..653d3a203 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -221,13 +221,17 @@ public static boolean shouldMerge(ProgramClass lambdaClass) && optimizationInfo.mayBeMerged(); } - public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaClassesWithUnexpectedMethods) throws IllegalArgumentException + public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaClassesWithUnexpectedMethods, ClassPool programClassPool) throws IllegalArgumentException { String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) { throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it has more than 1 constructor."); } + else if (!lambdaClassHasNoBootstrapMethod(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains a bootstrap method that would not be merged into the lambda group."); + } else if (!lambdaClassHasNoAccessibleStaticMethods(lambdaClass)) { throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains a static method that could be used outside the class itself."); @@ -236,19 +240,31 @@ else if (!lambdaClassIsNotDirectlyInvoked(lambdaClass)) { throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it is directly invoked with its specific invoke method."); } - else if (!lambdaClassHasNoUnexpectedMethods(lambdaClass)) + else if (!nonINSTANCEFieldsAreNotReferencedFromSamePackage(lambdaClass, programClassPool)) { - if (!mergeLambdaClassesWithUnexpectedMethods) - { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods."); - } - else if (!lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(lambdaClass)) - { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods that contain too much code all together."); - } + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because one of its fields, other than the 'INSTANCE' field is referenced by one of its inner classes."); + } + else if (!lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because its methods are too big to be inlined."); + } + else if (!mergeLambdaClassesWithUnexpectedMethods && !lambdaClassHasNoUnexpectedMethods(lambdaClass)) + { + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods."); } } + private static boolean lambdaClassHasNoBootstrapMethod(ProgramClass lambdaClass) { + final boolean[] bootstrapMethodFound = {false}; + lambdaClass.attributeAccept(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { + @Override + public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { + bootstrapMethodFound[0] = true; + } + }); + return !bootstrapMethodFound[0]; + } + public static boolean canMerge(ProgramClass lambdaClass) { return lambdaClassHasExactlyOneInitConstructor(lambdaClass) && lambdaClassHasNoUnexpectedMethods(lambdaClass); @@ -286,9 +302,7 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt totalCodeSize[0] += codeAttribute.u4codeLength; } }))); - // TODO: replace 7000 by constant or - // Integer.parseInt(System.getProperty("maximum.resulting.code.length", "7000")); - return totalCodeSize[0] <= 7000; + return totalCodeSize[0] <= KotlinLambdaGroupBuilder.MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH; } public static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) @@ -345,4 +359,36 @@ public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute }); return !methodReferenceFinder.methodReferenceFound(); } + + public static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramClass lambdaClass, ClassPool programClassPool) + { + AtomicBoolean nonINSTANCEFieldReferenced = new AtomicBoolean(false); + programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", + new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, + clazz1 -> { + if (clazz1 != lambdaClass) { + clazz1.constantPoolEntriesAccept(new ConstantVisitor() { + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) { + } + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + if (fieldrefConstant.referencedClass == lambdaClass + && !KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME.equals(fieldrefConstant.getName(clazz))) + { + nonINSTANCEFieldReferenced.set(true); + logger.warn("{} references non-INSTANCE field {} of lambda class {}.", + ClassUtil.externalClassName(clazz.getName()), + ClassUtil.externalFullFieldDescription(fieldrefConstant.referencedField.getAccessFlags(), + fieldrefConstant.getName(clazz), + fieldrefConstant.getType(clazz)), + ClassUtil.externalClassName(lambdaClass.getName())); + } + } + }); + } + }))); + return !nonINSTANCEFieldReferenced.get(); + } } From 61dc16c5898038034a8f8823ea82eb53564750b2 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:24:42 +0200 Subject: [PATCH 130/195] Apply LineAttributeTrimmer to lambda groups --- .../optimize/kotlin/KotlinLambdaMerger.java | 26 +++++++++++++------ 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 653d3a203..cc73bf75d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -5,12 +5,16 @@ import proguard.AppView; import proguard.Configuration; import proguard.classfile.*; -import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.EnclosingMethodAttribute; +import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.LineNumberTableAttributeTrimmer; import proguard.classfile.instruction.visitor.InstructionCounter; +import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.ClassInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; @@ -20,11 +24,12 @@ import proguard.optimize.peephole.LineNumberLinearizer; import proguard.optimize.peephole.SameClassMethodInliner; import proguard.pass.Pass; -import proguard.resources.file.ResourceFilePool; import proguard.shrink.*; import proguard.util.PrintWriterUtil; import proguard.util.ProcessingFlags; + import java.io.PrintWriter; +import java.util.concurrent.atomic.AtomicBoolean; public class KotlinLambdaMerger implements Pass { @@ -191,10 +196,15 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla // remove the unused parts of the lambda groups, such as the inlined invoke helper methods // and make sure that the line numbers are updated //ClassPool newLambdaGroupClassPool = new ClassPool(); - lambdaGroupClassPool.classesAccept(new UsedClassFilter(simpleUsageMarker, - new MultiClassVisitor( - new ClassShrinker(simpleUsageMarker), - new LineNumberLinearizer())); + lambdaGroupClassPool.classesAccept(new MultiClassVisitor( + new UsedClassFilter(simpleUsageMarker, + new ClassShrinker(simpleUsageMarker)), + new LineNumberLinearizer(), + new AllAttributeVisitor(true, + new LineNumberTableAttributeTrimmer())));//, +// new ClassPoolFiller(newLambdaGroupClassPool)))); + //lambdaGroupClassPool.clear(); + //newLambdaGroupClassPool.classesAccept(new ClassPoolFiller(lambdaGroupClassPool)); } private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) From 012d1830573e74836d3a64274578f837c578ade6 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:25:09 +0200 Subject: [PATCH 131/195] Clean all classes after lambda merging --- .../main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index cc73bf75d..16def6e7a 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -142,6 +142,9 @@ public void execute(AppView appView) throws Exception logger.info("Size of original program class pool: {}", appView.programClassPool.size()); logger.info("Size of new program class pool: {}", newProgramClassPool.size()); + appView.programClassPool.classesAccept(new ClassCleaner()); + appView.libraryClassPool.classesAccept(new ClassCleaner()); + newProgramClassPool.classesAccept(new ClassCleaner()); appView.programClassPool.clear(); newProgramClassPool.classesAccept(new ClassPoolFiller(appView.programClassPool)); } From eaa2f358622d7bb7625d001dd298cacd8f78f671 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:25:32 +0200 Subject: [PATCH 132/195] Remove unused arguments from helper methods --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 16def6e7a..fbb200710 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -127,10 +127,10 @@ public void execute(AppView appView) throws Exception newProgramClassPool.classesAccept(new ClassInitializer(newProgramClassPool, appView.libraryClassPool)); // inline the helper invoke methods into the general invoke method - inlineMethodsInsideLambdaGroups(newProgramClassPool, appView.libraryClassPool, lambdaGroupClassPool); + inlineMethodsInsideLambdaGroups(lambdaGroupClassPool); // remove the unused helper methods from the lambda groups - shrinkLambdaGroups(newProgramClassPool, appView.libraryClassPool, appView.resourceFilePool, lambdaGroupClassPool); + shrinkLambdaGroups(newProgramClassPool, appView.libraryClassPool, lambdaGroupClassPool); logger.info("Considered {} lambda classes for merging", lambdaClassPool.size()); logger.info("of which {} lambda classes were not merged.", notMergedLambdaClassPool.size()); @@ -150,7 +150,7 @@ public void execute(AppView appView) throws Exception } } - private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) + private void inlineMethodsInsideLambdaGroups(ClassPool lambdaGroupClassPool) { InstructionCounter methodInliningCounter = new InstructionCounter(); @@ -167,7 +167,7 @@ private void inlineMethodsInsideLambdaGroups(ClassPool programClassPool, ClassPo logger.debug("{} methods inlined inside lambda groups.", methodInliningCounter.getCount()); } - private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ResourceFilePool resourceFilePool, ClassPool lambdaGroupClassPool) + private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) { SimpleUsageMarker simpleUsageMarker = new SimpleUsageMarker(); ClassUsageMarker classUsageMarker = new ClassUsageMarker(simpleUsageMarker); From 6de2bb8ea7498b76ee32736bc332a83e77bdac84 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:26:11 +0200 Subject: [PATCH 133/195] Mark invoke, and methods as used before shrinking lambda groups --- .../proguard/optimize/kotlin/KotlinLambdaMerger.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index fbb200710..f39868bbb 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -191,6 +191,16 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla new AllMemberVisitor( classUsageMarker)))); + lambdaGroupClassPool.classesAccept(new AllMemberVisitor( + new MultiMemberVisitor( + new MemberNameFilter(KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, + classUsageMarker), + new MemberNameFilter(ClassConstants.METHOD_NAME_INIT, + classUsageMarker), + new MemberNameFilter(ClassConstants.METHOD_NAME_CLINIT, + classUsageMarker)))); + + // ensure that the interfaces of the lambda group are not removed lambdaGroupClassPool.classesAccept(new InterfaceUsageMarker( classUsageMarker)); From 545b67f67006553548f4029fe8af51e91bd5ac5f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:26:30 +0200 Subject: [PATCH 134/195] Remove commented code --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 9ada578e1..75f2ce145 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -157,11 +157,9 @@ private void mergeLambdaClass(ProgramClass lambdaClass) new ModifiedAllInnerClassesInfoVisitor( new InnerClassInfoClassConstantVisitor( new ClassConstantToClassVisitor( - //new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, - new ClassNameFilter(lambdaClass.getName(), - (ClassVisitor)null, - (ClassVisitor)this)//, - // null) + new ClassNameFilter(lambdaClass.getName(), + (ClassVisitor)null, + (ClassVisitor)this) ), // don't revisit the current lambda null))); From 9a0334478fc86aa7836c533b6f163f25a404a4ab Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:27:11 +0200 Subject: [PATCH 135/195] Allow inlining of big methos in lambda classes --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 6 +++++- .../optimize/peephole/SameClassMethodInliner.java | 15 +++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 75f2ce145..a0ad9e72f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -237,11 +237,15 @@ private void inlineMethodsInsideClass(ProgramClass lambdaClass) lambdaClass.accept(new AllMemberVisitor( new ProgramMemberOptimizationInfoSetter())); + // Allow methods to become lambdaClass.accept(new AllMethodVisitor( new AllAttributeVisitor( new SameClassMethodInliner(configuration.microEdition, configuration.android, - configuration.allowAccessModification)))); + MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH, + configuration.allowAccessModification, + true, + null)))); } private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) diff --git a/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java b/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java index bd3babca8..c8842d427 100644 --- a/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java +++ b/base/src/main/java/proguard/optimize/peephole/SameClassMethodInliner.java @@ -50,6 +50,21 @@ public SameClassMethodInliner(boolean microEdition, super(microEdition, android, allowAccessModification, extraInlinedInvocationVisitor); } + public SameClassMethodInliner(boolean microEdition, + boolean android, + int maxResultingCodeLength, + boolean allowAccessModification, + boolean usesOptimizationInfo, + InstructionVisitor extraInlinedInvocationVisitor) + { + super(microEdition, + android, + maxResultingCodeLength, + allowAccessModification, + usesOptimizationInfo, + extraInlinedInvocationVisitor); + } + // Implementations for MethodInliner. @Override From 3b7503bb996bb755b29687595c2bbc10d30ae5b4 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:27:47 +0200 Subject: [PATCH 136/195] Remove lambda class as subclass of its interfaces and super class --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index a0ad9e72f..360adbd91 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -11,6 +11,7 @@ import proguard.classfile.attribute.visitor.ModifiedAllInnerClassesInfoVisitor; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.InterfaceAdder; +import proguard.classfile.editor.SubclassRemover; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; @@ -208,6 +209,10 @@ private void mergeLambdaClass(ProgramClass lambdaClass) // replace instantiation of lambda class with instantiation of lambda group with correct id updateLambdaInstantiationSite(lambdaClass, lambdaClassId, arity, constructorDescriptor); optimizationInfo.setLambdaGroupClassId(lambdaClassId); + SubclassRemover subclassRemover = new SubclassRemover(lambdaClass); + lambdaClass.getSuperClass().accept(subclassRemover); + lambdaClass.interfaceConstantsAccept(new ClassConstantToClassVisitor( + subclassRemover)); } private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) From 641e1065d22bfb8b914ca487ba1e038ca51319d5 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:28:13 +0200 Subject: [PATCH 137/195] Use constant from KotlinConstants --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 360adbd91..90126e0db 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -12,6 +12,7 @@ import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.InterfaceAdder; import proguard.classfile.editor.SubclassRemover; +import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; @@ -226,8 +227,9 @@ private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Assumption: the only name clash of fields of different classes is // for fields with the name "INSTANCE". We don't need these fields anyway, so we don't rename them. - // TODO: handle name clashes correctly - if (!programField.getName(programClass).equals("INSTANCE")) + // TODO: handle name clashes correctly - this happens also in the case of inner lambda's accessing their + // enclosing lambda class via + if (!programField.getName(programClass).equals(KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME)) { fieldRenamer.visitProgramField(programClass, programField); } From 081633488cea59112cbd966c8eef5b72030c2085 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Thu, 12 May 2022 21:28:42 +0200 Subject: [PATCH 138/195] Update any constants where a merged lambda class is used as type or name --- .../KotlinLambdaEnclosingMethodUpdater.java | 53 ++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 5277eb1ce..9db4ad823 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -9,6 +9,7 @@ import proguard.classfile.constant.AnyMethodrefConstant; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.Constant; +import proguard.classfile.constant.Utf8Constant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; @@ -275,14 +276,64 @@ else if (!currentEnclosingClass.equals(programClass) && !currentLambdaClass.equa } // This programClass references the lambda class, so any referencing instructions // must be updated. + // TODO: consider whether this can be removed, given the fact that all constants are updated anyway programClass.methodsAccept(this); + // In some cases a class uses the lambda class as a type in a field or method descriptor + // All those constants must be updated as well. + programClass.constantPoolEntriesAccept(new MultiConstantVisitor( + new DescriptorTypeUpdater( + ClassUtil.internalTypeFromClassName(this.currentLambdaClass.getName()), + ClassUtil.internalTypeFromClassName(this.lambdaGroup.getName())), + new ClassConstantReferenceUpdater(this.currentLambdaClass, this.lambdaGroup))); + // remove any old links between lambda's and their inner classes programClass.accept(KotlinLambdaEnclosingMethodUpdater.retargetedInnerClassAttributeRemover); // Remove any constants referring to the old lambda class. programClass.accept(new ConstantPoolShrinker()); - this.extraDataEntryNameMap.addExtraClassToClass(programClass, currentLambdaClass); + this.extraDataEntryNameMap.addExtraClassToClass(programClass, this.lambdaGroup); + } + } +} + +class DescriptorTypeUpdater implements ConstantVisitor +{ + private final String originalType; + private final String replacingType; + public DescriptorTypeUpdater(String originalType, String replacingType) + { + this.originalType = originalType; + this.replacingType = replacingType; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { + utf8Constant.setString(utf8Constant.getString().replace(originalType, replacingType)); + } +} + +class ClassConstantReferenceUpdater implements ConstantVisitor +{ + private final Clazz originalClass; + private final Clazz replacingClass; + public ClassConstantReferenceUpdater(Clazz originalClass, Clazz replacingClass) + { + this.originalClass = originalClass; + this.replacingClass = replacingClass; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + if (classConstant.referencedClass == originalClass) + { + classConstant.referencedClass = replacingClass; } } } From 4c1a86de8a80284605312562d31bab903cfd86d3 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 13:08:42 +0200 Subject: [PATCH 139/195] Move responsibility of extending descriptor to init updater --- .../kotlin/KotlinLambdaGroupBuilder.java | 7 +- .../kotlin/KotlinLambdaGroupInitUpdater.java | 72 ++++++------------- .../optimize/kotlin/KotlinLambdaMerger.java | 20 +++++- 3 files changed, 43 insertions(+), 56 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 90126e0db..1edd0cfbd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -295,7 +295,7 @@ private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaCl initMethod.getDescriptor(lambdaClass))); String oldInitDescriptor = initMethod.getDescriptor(lambdaClass); - String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; + String newInitDescriptor = KotlinLambdaGroupInitUpdater.getNewInitMethodDescriptor(lambdaClass, initMethod); // Check whether an init method with this descriptor exists already ProgramMethod existingInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); @@ -307,9 +307,10 @@ private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaCl return existingInitMethod; } - initMethod.accept(lambdaClass, new MethodCopier(lambdaGroup, ClassConstants.METHOD_NAME_INIT, newInitDescriptor, AccessConstants.PUBLIC));//, true)); - ProgramMethod newInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + initMethod.accept(lambdaClass, new MethodCopier(lambdaGroup, ClassConstants.METHOD_NAME_INIT, oldInitDescriptor, AccessConstants.PUBLIC));//, true)); + ProgramMethod newInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, oldInitDescriptor); + // Update the descriptor // Add the necessary instructions to entirely new methods newInitMethod.accept(lambdaGroup, this.initUpdater); return newInitMethod; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index 43942eaff..78c40b82d 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -6,6 +6,7 @@ import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.Utf8Constant; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.BranchTargetFinder; @@ -22,8 +23,8 @@ public class KotlinLambdaGroupInitUpdater implements AttributeVisitor, MemberVis public KotlinLambdaGroupInitUpdater(ClassPool programClassPool, ClassPool libraryClassPool) { - this.programClassPool = programClassPool; - this.libraryClassPool = libraryClassPool; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; } @Override @@ -35,9 +36,15 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM programMethod.getName(programClass), programMethod.getDescriptor(programClass)), ClassUtil.externalClassName(programClass.getName())); + updateInitMethodDescriptor(programClass, programMethod); programMethod.attributesAccept(programClass, this); } + private void updateInitMethodDescriptor(ProgramClass programClass, ProgramMethod programMethod) { + String newInitDescriptor = getNewInitMethodDescriptor(programClass, programMethod); + programMethod.u2descriptorIndex = new ConstantAdder(programClass).addConstant(programClass, new Utf8Constant(newInitDescriptor)); + } + @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { @@ -48,7 +55,7 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt } catch (ClassCastException exception) { - logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), clazz, method); + logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), ClassUtil.externalClassName(clazz.getName()), ClassUtil.externalFullMethodDescription(clazz.getName(), method.getAccessFlags(), method.getName(clazz), method.getDescriptor(clazz))); } } @@ -57,11 +64,11 @@ public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programM CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); - // The arity argument is the last of all. + // The arity argument is the last of all and has a size of 1 byte. // Note that the arguments start counting from 1, as the class itself is at byte 0. // long and double arguments take 2 bytes. // The classId and arity arguments are the 2 last, which take 1 byte each, as they are of type int. - int arityIndex = KotlinLambdaGroupInitUpdater.countParameterBytes(programMethod.getDescriptor(programClass)) - 1; + int arityIndex = ClassUtil.internalMethodParameterSize(programMethod.getDescriptor(programClass), false) - 1; InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor, arityIndex, programClass); @@ -72,49 +79,13 @@ public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programM replacer)); } - private static int countParameterBytes(String descriptor) - { - int counter = 1; - int index = 1; + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - char oldC = 0; - char c = descriptor.charAt(index++); - while (true) - { - switch (c) - { - case TypeConstants.ARRAY: - { - // Just ignore all array characters. - break; - } - case TypeConstants.CLASS_START: - { - counter++; - - // Skip the class name. - index = descriptor.indexOf(TypeConstants.CLASS_END, index) + 1; - break; - } - case TypeConstants.LONG: - case TypeConstants.DOUBLE: - if (oldC != TypeConstants.ARRAY) - { - counter++; - } - default: - { - counter++; - break; - } - case TypeConstants.METHOD_ARGUMENTS_CLOSE: - { - return counter; - } - } - oldC = c; - c = descriptor.charAt(index++); - } + public static String getNewInitMethodDescriptor(ProgramClass programClass, ProgramMethod programMethod) + { + String oldInitDescriptor = programMethod.getDescriptor(programClass); + String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; + return newInitDescriptor; } private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTargetFinder branchTargetFinder, @@ -125,16 +96,15 @@ private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTar InstructionSequenceBuilder builder = new InstructionSequenceBuilder(programClassPool, libraryClassPool); Instruction[][][] replacementPatterns = createReplacementPatternsForInit(builder, arityIndex, lambdaGroup); return new InstructionSequencesReplacer(builder.constants(), - replacementPatterns, - branchTargetFinder, - codeAttributeEditor); + replacementPatterns, + branchTargetFinder, + codeAttributeEditor); } private Instruction[][][] createReplacementPatternsForInit(InstructionSequenceBuilder builder, int arityIndex, ProgramClass lambdaGroup) { - // TODO: store classId in field final int X = InstructionSequenceMatcher.X; return new Instruction[][][] { diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index f39868bbb..3c6ac5d30 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -5,10 +5,12 @@ import proguard.AppView; import proguard.Configuration; import proguard.classfile.*; -import proguard.classfile.attribute.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.BootstrapMethodsAttribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.EnclosingMethodAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; import proguard.classfile.constant.Constant; import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; @@ -34,6 +36,7 @@ public class KotlinLambdaMerger implements Pass { public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; + public static final String NAME_KOTLIN_FUNCTION = "kotlin/jvm/functions/Function"; public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; public static final String NAME_KOTLIN_FUNCTIONN = "kotlin/jvm/functions/FunctionN"; @@ -229,6 +232,19 @@ private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool library return kotlinLambdaClass; } + public static String getArityFromInterface(ProgramClass programClass) + { + for (int interfaceIndex = 0; interfaceIndex < programClass.u2interfacesCount; interfaceIndex++) + { + String interfaceName = programClass.getInterfaceName(interfaceIndex); + if (interfaceName.startsWith(NAME_KOTLIN_FUNCTION) && interfaceName.length() > NAME_KOTLIN_FUNCTION.length()) + { + return String.valueOf(interfaceName.charAt(interfaceName.length()-1)); + } + } + throw new IllegalArgumentException("Class " + ClassUtil.externalClassName(programClass.getName()) + " does not implement a Kotlin function interface."); + } + /** * Checks whether the given lambda class should still be merged. * Returns true if the lambda class has not yet been merged and is allowed to be merged. From be44e40323401c31effdb36019ce00ac9ca704d8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 13:09:04 +0200 Subject: [PATCH 140/195] LambdGroupMappingPrinter: also print arity based on interface --- .../proguard/optimize/kotlin/LambdaGroupMappingPrinter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java index fa6679e19..5748a2a48 100644 --- a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java @@ -37,7 +37,9 @@ public void visitProgramClass(ProgramClass programClass) { pw.println(ClassUtil.externalClassName(name) + " -> " + ClassUtil.externalClassName(lambdaGroupName) + - " (case " + + " (arity " + + KotlinLambdaMerger.getArityFromInterface(programClass) + + ", case " + optimizationInfo.getLambdaGroupClassId() + ")"); } From f6e191b2cd3d7e7605dae4b20c0e3233539aac3d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 13:09:38 +0200 Subject: [PATCH 141/195] KotlinLambdaGroupInitUpdaterTest --- .../KotlinLambdaGroupInitUpdaterTest.kt | 113 ++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt new file mode 100644 index 000000000..d9330579d --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt @@ -0,0 +1,113 @@ +package proguard.optimize.kotlin + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import proguard.classfile.* +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.attribute.visitor.MultiAttributeVisitor +import proguard.classfile.editor.ClassBuilder +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.instruction.visitor.InstructionVisitor +import proguard.classfile.util.ClassUtil +import proguard.classfile.util.InstructionSequenceMatcher + +class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ + val lambdaGroupName = "LambdaGroup" + val classBuilder = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.FINAL or AccessConstants.SUPER, + lambdaGroupName, + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA + ) + val initMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT, + 50) { + it.aload_0() + .iconst(0) + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .return_() + } + val classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, KotlinLambdaGroupBuilder.FIELD_NAME_ID, KotlinLambdaGroupBuilder.FIELD_TYPE_ID) + val lambdaGroup = classBuilder.programClass + + "Given a Kotlin Lambda Group Init Updater" - { + val initUpdater = KotlinLambdaGroupInitUpdater(ClassPool(lambdaGroup), ClassPool()) + "When the updater is applied to the method of a lambda group" - { + val initMethodDescriptor = initMethod.getDescriptor(lambdaGroup) + val argumentCountBefore = ClassUtil.internalMethodParameterCount(initMethodDescriptor, false) + val argumentSizeBefore = ClassUtil.internalMethodParameterSize(initMethodDescriptor, false) + initMethod.accept(lambdaGroup, initUpdater) + val newInitMethodDescriptor = initMethod.getDescriptor(lambdaGroup) + val argumentCountAfter = ClassUtil.internalMethodParameterCount(newInitMethodDescriptor, false) + val argumentSizeAfter = ClassUtil.internalMethodParameterSize(newInitMethodDescriptor, false) + "Then the init method contains two additional arguments" - { + argumentCountAfter shouldBe argumentCountBefore + 2 + } + "Then the two additional arguments are each of size 1 byte" - { + argumentSizeAfter shouldBe argumentSizeBefore + 2 + } + "Then the two additional arguments are of type int" - { + ClassUtil.internalMethodParameterType(newInitMethodDescriptor, argumentCountAfter - 3) shouldBe "I" + ClassUtil.internalMethodParameterType(newInitMethodDescriptor, argumentCountAfter - 2) shouldBe "I" + } + + // Find the match in the code and print it out. + class MatchDetector(val matcher: InstructionSequenceMatcher, vararg val arguments: Int) : InstructionVisitor { + var matchIsFound = false + var matchedArguments = IntArray(arguments.size) + + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction + ) { + println(instruction.toString(clazz, offset)) + instruction.accept(clazz, method, codeAttribute, offset, matcher) + if (matcher.isMatching()) { + matchIsFound = true + matchedArguments = matcher.matchedArguments(arguments) + } + } + } + + "Then the code of the method calls the super constructor with the arity argument" - { + val classIdBuilder = InstructionSequenceBuilder(ClassPool(lambdaGroup), ClassPool()) + classIdBuilder + .aload_0() + .iload(InstructionSequenceMatcher.A) + .putfield(lambdaGroup, classIdField) + val classIdInstructionMatcher = InstructionSequenceMatcher(classIdBuilder.constants(), classIdBuilder.instructions()) + val classIdMatchDetector = MatchDetector(classIdInstructionMatcher, InstructionSequenceMatcher.A) + initMethod.accept(lambdaGroup, + AllAttributeVisitor( + AllInstructionVisitor( + classIdMatchDetector))) + classIdMatchDetector.matchIsFound shouldBe true + classIdMatchDetector.matchedArguments[0] shouldBe argumentSizeAfter - 2 + } + "Then the code of the method stores the classId argument in the classId field" - { + val callBuilder = InstructionSequenceBuilder(ClassPool(lambdaGroup), ClassPool()) + callBuilder + .aload_0() + .iload(InstructionSequenceMatcher.A) + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + "(I)V") + val callInstructionMatcher = InstructionSequenceMatcher(callBuilder.constants(), callBuilder.instructions()) + val callMatchDetector = MatchDetector(callInstructionMatcher, InstructionSequenceMatcher.A) + initMethod.accept(lambdaGroup, + AllAttributeVisitor( + AllInstructionVisitor( + callMatchDetector))) + callMatchDetector.matchIsFound shouldBe true + callMatchDetector.matchedArguments[0] shouldBe argumentSizeAfter - 1 + } + } + } +}) \ No newline at end of file From 9b60b1633108a6d34c0d80e4a22e085d7e288b04 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 21:10:37 +0200 Subject: [PATCH 142/195] KotlinLambdaMergerTest updated to use new KotlinLambdaMerger --- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 6aa381584..087896377 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -23,8 +23,9 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldContain -import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual +import io.kotest.matchers.ints.shouldBeGreaterThan import io.mockk.* +import proguard.AppView import proguard.Configuration import proguard.classfile.ClassConstants import proguard.classfile.ClassPool @@ -99,10 +100,13 @@ class KotlinLambdaMergerTest : FreeSpec({ val merger = KotlinLambdaMerger(Configuration()) val nameMapper = ExtraDataEntryNameMap() "When the merger is applied to the class pools" - { - val newProgramClassPool = merger.execute(programClassPool, libraryClassPool, null, nameMapper) - val newFullClassPool = newProgramClassPool.classes() union libraryClassPool.classes() + val oldProgramClassPool = ClassPool(programClassPool) + val appView = AppView(programClassPool, libraryClassPool, null, nameMapper) + merger.execute(appView) + val newProgramClassPool = appView.programClassPool + val newFullClassPool = appView.programClassPool.classes() union libraryClassPool.classes() "Then the resulting program class pool contains less classes" { - programClassPool.size() shouldBeGreaterThanOrEqual newProgramClassPool.size() + oldProgramClassPool.size() shouldBeGreaterThan newProgramClassPool.size() } "Then the program classes should only refer to classes that are in the class pool" { From e4c9162cf0da76c251d411915e34f1a08f2fd87e Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 21:11:58 +0200 Subject: [PATCH 143/195] Remove commented code --- .../optimize/kotlin/KotlinLambdaMerger.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 3c6ac5d30..d056e1a9f 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -73,9 +73,7 @@ public void execute(AppView appView) throws Exception appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, new ImplementedClassFilter(kotlinLambdaClass, false, - //new ClassMethodFilter(ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, new ClassPoolFiller(lambdaClassPool), - //newProgramClassPoolFiller), newProgramClassPoolFiller)) ); @@ -177,12 +175,6 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla // make sure that the used methods of the lambda groups are marked as used // by marking all classes and methods - // note: if -dontshrink is - /*new UsageMarker(configuration).mark(programClassPool, - libraryClassPool, - resourceFilePool, - simpleUsageMarker, - classUsageMarker);*/ libraryClassPool.classesAccept(classUsageMarker); // but don't mark the lambda groups and their members in case they are not used, // e.g. inlined helper invoke methods @@ -211,16 +203,12 @@ private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryCla // mark the lambda groups themselves as used // remove the unused parts of the lambda groups, such as the inlined invoke helper methods // and make sure that the line numbers are updated - //ClassPool newLambdaGroupClassPool = new ClassPool(); lambdaGroupClassPool.classesAccept(new MultiClassVisitor( new UsedClassFilter(simpleUsageMarker, new ClassShrinker(simpleUsageMarker)), new LineNumberLinearizer(), new AllAttributeVisitor(true, - new LineNumberTableAttributeTrimmer())));//, -// new ClassPoolFiller(newLambdaGroupClassPool)))); - //lambdaGroupClassPool.clear(); - //newLambdaGroupClassPool.classesAccept(new ClassPoolFiller(lambdaGroupClassPool)); + new LineNumberTableAttributeTrimmer()))); } private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool libraryClassPool) From ed6fc741c0f47f0fcb0f57ef80773c6ad38df502 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 21:12:09 +0200 Subject: [PATCH 144/195] Remove unused canMerge method --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d056e1a9f..ab8082f27 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -292,11 +292,6 @@ public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribut return !bootstrapMethodFound[0]; } - public static boolean canMerge(ProgramClass lambdaClass) - { - return lambdaClassHasExactlyOneInitConstructor(lambdaClass) && lambdaClassHasNoUnexpectedMethods(lambdaClass); - } - public static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lambdaClass) { for (int index = 0; index < lambdaClass.u2methodsCount; index++) From 7531f293407964ebb67f090b4b8aea8b04700dcd Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 21:12:43 +0200 Subject: [PATCH 145/195] Don't allow merging when class is not a lambda class --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index ab8082f27..6eb3e8ac5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -251,7 +251,11 @@ public static boolean shouldMerge(ProgramClass lambdaClass) public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaClassesWithUnexpectedMethods, ClassPool programClassPool) throws IllegalArgumentException { String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); - if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) + if (!lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA)) + { + throw new IllegalArgumentException("Class " + externalClassName + " cannot be merged in a Kotlin lambda group, because it is not a subclass of " + ClassUtil.externalClassName(NAME_KOTLIN_LAMBDA) + "."); + } + else if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) { throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it has more than 1 constructor."); } From 1f70c32fcf89fb096118e5cc558eaa5f4cf42d6b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 21:14:02 +0200 Subject: [PATCH 146/195] Execute lambda merger before last iteration of optimiser, if not earlier --- base/src/main/java/proguard/ProGuard.java | 4 ++++ base/src/main/java/proguard/optimize/Optimizer.java | 12 ++++++++++-- .../proguard/optimize/kotlin/KotlinLambdaMerger.java | 6 ++++++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 1fe577cc0..46c2483e2 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -470,6 +470,10 @@ private void optimize() throws Exception { shrink(true); } + if (optimizationPass == configuration.optimizationPasses - 2) + { + mergeKotlinLambdaClasses(); + } } } diff --git a/base/src/main/java/proguard/optimize/Optimizer.java b/base/src/main/java/proguard/optimize/Optimizer.java index 910bf71cc..f3a175226 100644 --- a/base/src/main/java/proguard/optimize/Optimizer.java +++ b/base/src/main/java/proguard/optimize/Optimizer.java @@ -38,6 +38,7 @@ import proguard.optimize.evaluation.*; import proguard.optimize.evaluation.InstructionUsageMarker; import proguard.optimize.info.*; +import proguard.optimize.kotlin.KotlinLambdaMerger; import proguard.optimize.peephole.*; import proguard.pass.Pass; import proguard.util.*; @@ -195,11 +196,18 @@ public Optimizer(Configuration configuration) @Override - public void execute(AppView appView) throws IOException + public void execute(AppView appView) throws Exception { if (!moreOptimizationsPossible) { - return; + if (KotlinLambdaMerger.lambdaMergingDone) + { + return; + } + else + { + new KotlinLambdaMerger(configuration).execute(appView); + } } // Create a matcher for filtering optimizations. diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 6eb3e8ac5..dc02d2144 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -40,6 +40,7 @@ public class KotlinLambdaMerger implements Pass { public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; public static final String NAME_KOTLIN_FUNCTIONN = "kotlin/jvm/functions/FunctionN"; + public static boolean lambdaMergingDone = false; private static final Logger logger = LogManager.getLogger(KotlinLambdaMerger.class); private final Configuration configuration; @@ -51,6 +52,10 @@ public KotlinLambdaMerger(Configuration configuration) @Override public void execute(AppView appView) throws Exception { + if (lambdaMergingDone) + { + return; + } // Remove old processing info appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); @@ -149,6 +154,7 @@ public void execute(AppView appView) throws Exception appView.programClassPool.clear(); newProgramClassPool.classesAccept(new ClassPoolFiller(appView.programClassPool)); } + lambdaMergingDone = true; } private void inlineMethodsInsideLambdaGroups(ClassPool lambdaGroupClassPool) From 0fbed667d8aa7775769a3625f0bc299652325a1d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 22:08:23 +0200 Subject: [PATCH 147/195] Test whether lambda group has been added to package --- .../proguard/optimize/kotlin/KotlinLambdaMergerTest.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 087896377..c070e9bf9 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -24,6 +24,7 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldNotBe import io.mockk.* import proguard.AppView import proguard.Configuration @@ -144,6 +145,11 @@ class KotlinLambdaMergerTest : FreeSpec({ it.constantPoolEntriesAccept(visitor) } } + + "Then for each package with lambda's a lambda group has been created." { + newProgramClassPool.getClass("app/package1/LambdaGroup") shouldNotBe null + newProgramClassPool.getClass("app/package2/LambdaGroup") shouldNotBe null + } } } }) From ac8e902a9f2779d60652144aec59cd5067ff7975 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sat, 4 Jun 2022 22:08:38 +0200 Subject: [PATCH 148/195] Remove dash ('-') from unit tests --- .../kotlin/KotlinLambdaGroupInitUpdaterTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt index d9330579d..290d908c5 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt @@ -44,13 +44,13 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ val newInitMethodDescriptor = initMethod.getDescriptor(lambdaGroup) val argumentCountAfter = ClassUtil.internalMethodParameterCount(newInitMethodDescriptor, false) val argumentSizeAfter = ClassUtil.internalMethodParameterSize(newInitMethodDescriptor, false) - "Then the init method contains two additional arguments" - { + "Then the init method contains two additional arguments" { argumentCountAfter shouldBe argumentCountBefore + 2 } - "Then the two additional arguments are each of size 1 byte" - { + "Then the two additional arguments are each of size 1 byte" { argumentSizeAfter shouldBe argumentSizeBefore + 2 } - "Then the two additional arguments are of type int" - { + "Then the two additional arguments are of type int" { ClassUtil.internalMethodParameterType(newInitMethodDescriptor, argumentCountAfter - 3) shouldBe "I" ClassUtil.internalMethodParameterType(newInitMethodDescriptor, argumentCountAfter - 2) shouldBe "I" } @@ -76,7 +76,7 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ } } - "Then the code of the method calls the super constructor with the arity argument" - { + "Then the code of the method calls the super constructor with the arity argument" { val classIdBuilder = InstructionSequenceBuilder(ClassPool(lambdaGroup), ClassPool()) classIdBuilder .aload_0() @@ -91,7 +91,7 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ classIdMatchDetector.matchIsFound shouldBe true classIdMatchDetector.matchedArguments[0] shouldBe argumentSizeAfter - 2 } - "Then the code of the method stores the classId argument in the classId field" - { + "Then the code of the method stores the classId argument in the classId field" { val callBuilder = InstructionSequenceBuilder(ClassPool(lambdaGroup), ClassPool()) callBuilder .aload_0() From 5ba88c185fa748e20e9fdde92da013cb976335ec Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 6 Jun 2022 17:43:48 +0200 Subject: [PATCH 149/195] MatchDetector in separate file --- .../KotlinLambdaGroupInitUpdaterTest.kt | 22 +-------------- .../test/kotlin/testutils/MatchDetector.kt | 28 +++++++++++++++++++ 2 files changed, 29 insertions(+), 21 deletions(-) create mode 100644 base/src/test/kotlin/testutils/MatchDetector.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt index 290d908c5..a6349b497 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt @@ -13,6 +13,7 @@ import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.instruction.visitor.InstructionVisitor import proguard.classfile.util.ClassUtil import proguard.classfile.util.InstructionSequenceMatcher +import testutils.MatchDetector class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ val lambdaGroupName = "LambdaGroup" @@ -55,27 +56,6 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ ClassUtil.internalMethodParameterType(newInitMethodDescriptor, argumentCountAfter - 2) shouldBe "I" } - // Find the match in the code and print it out. - class MatchDetector(val matcher: InstructionSequenceMatcher, vararg val arguments: Int) : InstructionVisitor { - var matchIsFound = false - var matchedArguments = IntArray(arguments.size) - - override fun visitAnyInstruction( - clazz: Clazz, - method: Method, - codeAttribute: CodeAttribute, - offset: Int, - instruction: Instruction - ) { - println(instruction.toString(clazz, offset)) - instruction.accept(clazz, method, codeAttribute, offset, matcher) - if (matcher.isMatching()) { - matchIsFound = true - matchedArguments = matcher.matchedArguments(arguments) - } - } - } - "Then the code of the method calls the super constructor with the arity argument" { val classIdBuilder = InstructionSequenceBuilder(ClassPool(lambdaGroup), ClassPool()) classIdBuilder diff --git a/base/src/test/kotlin/testutils/MatchDetector.kt b/base/src/test/kotlin/testutils/MatchDetector.kt new file mode 100644 index 000000000..dcedc6721 --- /dev/null +++ b/base/src/test/kotlin/testutils/MatchDetector.kt @@ -0,0 +1,28 @@ +package testutils + +import proguard.classfile.Clazz +import proguard.classfile.Method +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.InstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher + +class MatchDetector(val matcher: InstructionSequenceMatcher, vararg val arguments: Int) : InstructionVisitor { + var matchIsFound = false + var matchedArguments = IntArray(arguments.size) + + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction + ) { + println(instruction.toString(clazz, offset)) + instruction.accept(clazz, method, codeAttribute, offset, matcher) + if (matcher.isMatching()) { + matchIsFound = true + matchedArguments = matcher.matchedArguments(arguments) + } + } +} \ No newline at end of file From d07edafaf1be67b9c3920ac15045c03cfd4819d1 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 6 Jun 2022 17:47:09 +0200 Subject: [PATCH 150/195] New class: InstructionSequenceCollector Wrapper class to use the InstructionSequenceBuilder as a visitor --- .../testutils/InstructionSequenceCollector.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 base/src/test/kotlin/testutils/InstructionSequenceCollector.kt diff --git a/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt b/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt new file mode 100644 index 000000000..dcfc57894 --- /dev/null +++ b/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt @@ -0,0 +1,44 @@ +package testutils + +import proguard.classfile.Clazz +import proguard.classfile.Method +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.constant.Constant +import proguard.classfile.constant.visitor.ConstantVisitor +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.ConstantInstruction +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.InstructionVisitor + +class InstructionSequenceCollector(val builder: InstructionSequenceBuilder) : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction + ) { + builder.appendInstruction(instruction) + } + override fun visitConstantInstruction( + clazz: Clazz?, + method: Method?, + codeAttribute: CodeAttribute?, + offset: Int, + constantInstruction: ConstantInstruction? + ) { + val constantPoolEditor = builder.constantPoolEditor + var constantIndex = 0 + assert (constantInstruction != null) + assert (constantInstruction!!.constantIndex > 0) + clazz?.constantPoolEntryAccept(constantInstruction.constantIndex, object : ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) { + constantIndex = constantPoolEditor.addConstant(constant) + } + }) + assert (constantIndex == 0 ) + val newConstantInstruction = ConstantInstruction().copy(constantInstruction) + newConstantInstruction.constantIndex = constantIndex + visitAnyInstruction(clazz!!, method!!, codeAttribute!!, offset, newConstantInstruction) + } +} \ No newline at end of file From d3c257bb15745689c2ccce4fea80b91079db2e65 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 6 Jun 2022 19:06:12 +0200 Subject: [PATCH 151/195] Test added for KotlinLambdaGroupInvokeMethodBuilder InstancePerLeaf isolation mode is used to ensure that the target class has the same state before running every test case --- ...otlinLambdaGroupInvokeMethodBuilderTest.kt | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt new file mode 100644 index 000000000..221987d11 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt @@ -0,0 +1,66 @@ +package proguard.optimize.kotlin + +import io.kotest.core.spec.IsolationMode +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import proguard.classfile.AccessConstants +import proguard.classfile.ClassConstants +import proguard.classfile.ClassPool +import proguard.classfile.VersionConstants +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.editor.ClassBuilder +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher +import testutils.MatchDetector + +class KotlinLambdaGroupInvokeMethodBuilderTest : FreeSpec({ + + val arity = 0 + "Given an invoke method builder, a target class and a method" - { + val classBuilder = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.PUBLIC, + "TargetClass", + ClassConstants.NAME_JAVA_LANG_OBJECT + ) + val method = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT) + val targetClass = classBuilder.programClass + val invokeMethodBuilder = KotlinLambdaGroupInvokeMethodBuilder(arity, classBuilder, ClassPool(), ClassPool()) + "When a call to the method is added to the invoke method" - { + invokeMethodBuilder.addCallTo(method) + "Then the invoke method contains a call to the method" { + val invokeMethod = invokeMethodBuilder.build() + val matchingSequenceBuilder = InstructionSequenceBuilder().invokevirtual(targetClass, method) + val matchDetector = MatchDetector(InstructionSequenceMatcher(matchingSequenceBuilder.constants(), + matchingSequenceBuilder.instructions())) + invokeMethod.accept(targetClass, + AllAttributeVisitor( + AllInstructionVisitor( + matchDetector))) + matchDetector.matchIsFound shouldBe true + } + } + + "When the invoke method is built" - { + val invokeMethod = invokeMethodBuilder.build() + targetClass.methods.forEach { + println("$it: ${it.getName(targetClass)}${it.getDescriptor(targetClass)}") + } + "Then the returned invoke method is not null" { + invokeMethod shouldNotBe null + } + "Then the target class contains an invoke method with the correct descriptor" { + targetClass.findMethod("invoke", "()Ljava/lang/Object;") shouldNotBe null + } + "Then the target class contains the returned invoke method" { + targetClass.findMethod("invoke", "()Ljava/lang/Object;") shouldBe invokeMethod + } + } + } +}) { + override fun isolationMode(): IsolationMode = IsolationMode.InstancePerLeaf +} \ No newline at end of file From b19f341edc4a40e54c7c2c0f1cd76c4d21414d82 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 6 Jun 2022 22:04:47 +0200 Subject: [PATCH 152/195] Apply ktlint to test classes --- .../KotlinLambdaGroupInitUpdaterTest.kt | 75 +++++---- ...otlinLambdaGroupInvokeMethodBuilderTest.kt | 28 +++- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 158 +++++++++++++++--- .../kotlin/LambdaGroupMappingPrinterTest.kt | 46 +++++ .../kotlin/testutils/ClassPoolClassLoader.kt | 29 ++++ .../testutils/InstructionSequenceCollector.kt | 19 ++- .../test/kotlin/testutils/MatchDetector.kt | 2 +- 7 files changed, 288 insertions(+), 69 deletions(-) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/LambdaGroupMappingPrinterTest.kt create mode 100644 base/src/test/kotlin/testutils/ClassPoolClassLoader.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt index a6349b497..ce5989f5c 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdaterTest.kt @@ -2,15 +2,14 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe -import proguard.classfile.* -import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.AccessConstants +import proguard.classfile.ClassConstants +import proguard.classfile.ClassPool +import proguard.classfile.VersionConstants import proguard.classfile.attribute.visitor.AllAttributeVisitor -import proguard.classfile.attribute.visitor.MultiAttributeVisitor import proguard.classfile.editor.ClassBuilder import proguard.classfile.editor.InstructionSequenceBuilder -import proguard.classfile.instruction.Instruction import proguard.classfile.instruction.visitor.AllInstructionVisitor -import proguard.classfile.instruction.visitor.InstructionVisitor import proguard.classfile.util.ClassUtil import proguard.classfile.util.InstructionSequenceMatcher import testutils.MatchDetector @@ -23,28 +22,30 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ lambdaGroupName, KotlinLambdaMerger.NAME_KOTLIN_LAMBDA ) - val initMethod = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, - ClassConstants.METHOD_NAME_INIT, - ClassConstants.METHOD_TYPE_INIT, - 50) { + val initMethod = classBuilder.addAndReturnMethod( + AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT, + 50 + ) { it.aload_0() - .iconst(0) - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") - .return_() + .iconst(0) + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .return_() } val classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, KotlinLambdaGroupBuilder.FIELD_NAME_ID, KotlinLambdaGroupBuilder.FIELD_TYPE_ID) - val lambdaGroup = classBuilder.programClass + val lambdaGroup = classBuilder.programClass "Given a Kotlin Lambda Group Init Updater" - { val initUpdater = KotlinLambdaGroupInitUpdater(ClassPool(lambdaGroup), ClassPool()) "When the updater is applied to the method of a lambda group" - { - val initMethodDescriptor = initMethod.getDescriptor(lambdaGroup) - val argumentCountBefore = ClassUtil.internalMethodParameterCount(initMethodDescriptor, false) - val argumentSizeBefore = ClassUtil.internalMethodParameterSize(initMethodDescriptor, false) + val initMethodDescriptor = initMethod.getDescriptor(lambdaGroup) + val argumentCountBefore = ClassUtil.internalMethodParameterCount(initMethodDescriptor, false) + val argumentSizeBefore = ClassUtil.internalMethodParameterSize(initMethodDescriptor, false) initMethod.accept(lambdaGroup, initUpdater) val newInitMethodDescriptor = initMethod.getDescriptor(lambdaGroup) - val argumentCountAfter = ClassUtil.internalMethodParameterCount(newInitMethodDescriptor, false) - val argumentSizeAfter = ClassUtil.internalMethodParameterSize(newInitMethodDescriptor, false) + val argumentCountAfter = ClassUtil.internalMethodParameterCount(newInitMethodDescriptor, false) + val argumentSizeAfter = ClassUtil.internalMethodParameterSize(newInitMethodDescriptor, false) "Then the init method contains two additional arguments" { argumentCountAfter shouldBe argumentCountBefore + 2 } @@ -63,11 +64,15 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ .iload(InstructionSequenceMatcher.A) .putfield(lambdaGroup, classIdField) val classIdInstructionMatcher = InstructionSequenceMatcher(classIdBuilder.constants(), classIdBuilder.instructions()) - val classIdMatchDetector = MatchDetector(classIdInstructionMatcher, InstructionSequenceMatcher.A) - initMethod.accept(lambdaGroup, - AllAttributeVisitor( - AllInstructionVisitor( - classIdMatchDetector))) + val classIdMatchDetector = MatchDetector(classIdInstructionMatcher, InstructionSequenceMatcher.A) + initMethod.accept( + lambdaGroup, + AllAttributeVisitor( + AllInstructionVisitor( + classIdMatchDetector + ) + ) + ) classIdMatchDetector.matchIsFound shouldBe true classIdMatchDetector.matchedArguments[0] shouldBe argumentSizeAfter - 2 } @@ -76,18 +81,24 @@ class KotlinLambdaGroupInitUpdaterTest : FreeSpec({ callBuilder .aload_0() .iload(InstructionSequenceMatcher.A) - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, - ClassConstants.METHOD_NAME_INIT, - "(I)V") + .invokespecial( + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + "(I)V" + ) val callInstructionMatcher = InstructionSequenceMatcher(callBuilder.constants(), callBuilder.instructions()) - val callMatchDetector = MatchDetector(callInstructionMatcher, InstructionSequenceMatcher.A) - initMethod.accept(lambdaGroup, - AllAttributeVisitor( - AllInstructionVisitor( - callMatchDetector))) + val callMatchDetector = MatchDetector(callInstructionMatcher, InstructionSequenceMatcher.A) + initMethod.accept( + lambdaGroup, + AllAttributeVisitor( + AllInstructionVisitor( + callMatchDetector + ) + ) + ) callMatchDetector.matchIsFound shouldBe true callMatchDetector.matchedArguments[0] shouldBe argumentSizeAfter - 1 } } } -}) \ No newline at end of file +}) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt index 221987d11..553303c61 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilderTest.kt @@ -25,9 +25,11 @@ class KotlinLambdaGroupInvokeMethodBuilderTest : FreeSpec({ "TargetClass", ClassConstants.NAME_JAVA_LANG_OBJECT ) - val method = classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, + val method = classBuilder.addAndReturnMethod( + AccessConstants.PUBLIC, ClassConstants.METHOD_NAME_INIT, - ClassConstants.METHOD_TYPE_INIT) + ClassConstants.METHOD_TYPE_INIT + ) val targetClass = classBuilder.programClass val invokeMethodBuilder = KotlinLambdaGroupInvokeMethodBuilder(arity, classBuilder, ClassPool(), ClassPool()) "When a call to the method is added to the invoke method" - { @@ -35,12 +37,20 @@ class KotlinLambdaGroupInvokeMethodBuilderTest : FreeSpec({ "Then the invoke method contains a call to the method" { val invokeMethod = invokeMethodBuilder.build() val matchingSequenceBuilder = InstructionSequenceBuilder().invokevirtual(targetClass, method) - val matchDetector = MatchDetector(InstructionSequenceMatcher(matchingSequenceBuilder.constants(), - matchingSequenceBuilder.instructions())) - invokeMethod.accept(targetClass, - AllAttributeVisitor( - AllInstructionVisitor( - matchDetector))) + val matchDetector = MatchDetector( + InstructionSequenceMatcher( + matchingSequenceBuilder.constants(), + matchingSequenceBuilder.instructions() + ) + ) + invokeMethod.accept( + targetClass, + AllAttributeVisitor( + AllInstructionVisitor( + matchDetector + ) + ) + ) matchDetector.matchIsFound shouldBe true } } @@ -63,4 +73,4 @@ class KotlinLambdaGroupInvokeMethodBuilderTest : FreeSpec({ } }) { override fun isolationMode(): IsolationMode = IsolationMode.InstancePerLeaf -} \ No newline at end of file +} diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index c070e9bf9..5a79959dd 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -24,20 +24,40 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.ints.shouldBeGreaterThan +import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe -import io.mockk.* +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.slot import proguard.AppView import proguard.Configuration -import proguard.classfile.ClassConstants +import proguard.classfile.AccessConstants import proguard.classfile.ClassPool +import proguard.classfile.Clazz +import proguard.classfile.Method +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.constant.ClassConstant import proguard.classfile.constant.visitor.ConstantVisitor -import proguard.classfile.visitor.ClassMethodFilter -import proguard.classfile.visitor.ClassPoolFiller -import proguard.classfile.visitor.ImplementedClassFilter +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.instruction.visitor.InstructionVisitor +import proguard.classfile.instruction.visitor.MultiInstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher +import proguard.classfile.visitor.AllMemberVisitor +import proguard.classfile.visitor.MemberAccessFilter +import proguard.classfile.visitor.MemberNameFilter import proguard.io.ExtraDataEntryNameMap import testutils.ClassPoolBuilder +import testutils.ClassPoolClassLoader +import testutils.InstructionSequenceCollector import testutils.KotlinSource +import testutils.MatchDetector +import java.io.ByteArrayOutputStream +import java.io.PrintStream class KotlinLambdaMergerTest : FreeSpec({ @@ -84,28 +104,67 @@ class KotlinLambdaMergerTest : FreeSpec({ ) ) - - // A class pool where the applicable lambda's will be stored - val lambdaClassPool = ClassPool() - val kotlinLambdaClass = libraryClassPool.getClass(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA) - val kotlinFunction0Interface = libraryClassPool.getClass(KotlinLambdaMerger.NAME_KOTLIN_FUNCTION0) - programClassPool.classesAccept(ImplementedClassFilter( - kotlinFunction0Interface, false, - ImplementedClassFilter( - kotlinLambdaClass, false, - ClassMethodFilter( - ClassConstants.METHOD_NAME_INIT, ClassConstants.METHOD_TYPE_INIT, - ClassPoolFiller(lambdaClassPool), null), null), null)) - "Given a Kotlin Lambda Merger and entry name mapper" - { val merger = KotlinLambdaMerger(Configuration()) val nameMapper = ExtraDataEntryNameMap() + + class InstructionPrinter : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz, + method: Method, + codeAttribute: CodeAttribute, + offset: Int, + instruction: Instruction + ) { + println(instruction.toString(clazz, offset)) + } + } + "When the merger is applied to the class pools" - { val oldProgramClassPool = ClassPool(programClassPool) + val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(oldProgramClassPool, libraryClassPool) + oldProgramClassPool.getClass("app/package2/Test2Kt\$main\$lambda1\$1").accept( + AllMemberVisitor( + MemberNameFilter( + "invoke", + MemberAccessFilter( + 0, AccessConstants.SYNTHETIC, + AllAttributeVisitor( + AllInstructionVisitor( + MultiInstructionVisitor( + InstructionPrinter(), + InstructionSequenceCollector( + originalInvokeMethodCodeBuilder + ) + ) + ) + ) + ) + ) + ) + ) + val loaderBefore = ClassPoolClassLoader(oldProgramClassPool) + val testClassBefore = loaderBefore.loadClass("app.package2.Test2Kt") + val stdOutput = System.out + val capturedOutputBefore = ByteArrayOutputStream() + val capturedOutputStreamBefore = PrintStream(capturedOutputBefore) + System.setOut(capturedOutputStreamBefore) + try { + testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic } + .invoke(null, arrayOf()) + } catch (e: Exception) { + System.setOut(stdOutput) + println() + println("Exception: $e") + println("Output of method call:") + println(capturedOutputBefore) + } + System.setOut(stdOutput) + val appView = AppView(programClassPool, libraryClassPool, null, nameMapper) merger.execute(appView) val newProgramClassPool = appView.programClassPool - val newFullClassPool = appView.programClassPool.classes() union libraryClassPool.classes() + val newFullClassPool = appView.programClassPool.classes() union libraryClassPool.classes() "Then the resulting program class pool contains less classes" { oldProgramClassPool.size() shouldBeGreaterThan newProgramClassPool.size() } @@ -150,6 +209,67 @@ class KotlinLambdaMergerTest : FreeSpec({ newProgramClassPool.getClass("app/package1/LambdaGroup") shouldNotBe null newProgramClassPool.getClass("app/package2/LambdaGroup") shouldNotBe null } + + "Then the instructions of the original lambda's are found in the lambda group" { + val lambdaGroup = newProgramClassPool.getClass("app/package1/LambdaGroup") + val originalInstructionsMatcher = InstructionSequenceMatcher( + originalInvokeMethodCodeBuilder.constants(), + originalInvokeMethodCodeBuilder.instructions() + ) + val originalInstructionsMatchDetector = MatchDetector(originalInstructionsMatcher) + lambdaGroup.accept( + AllMemberVisitor( + MemberNameFilter( + "invoke", + MemberAccessFilter( + 0, AccessConstants.SYNTHETIC, + AllAttributeVisitor( + AllInstructionVisitor( + originalInstructionsMatchDetector + ) + ) + ) + ) + ) + ) + originalInstructionsMatchDetector.matchIsFound shouldBe true + } + + /* + "Then program output has not changed after optimisation" { + + val loaderAfter = ClassPoolClassLoader(newProgramClassPool) + val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") + val capturedOutputAfter = ByteArrayOutputStream() + val capturedOutputStreamAfter = PrintStream(capturedOutputBefore) + System.setOut(capturedOutputStreamAfter) + + try { + testClassAfter.declaredMethods.single { it.name == "main" && it.isSynthetic } + .invoke(null, arrayOf()) + } catch (e: Exception) { + System.setOut(stdOutput) + println("Exception while executing test class after lambda merging.") + println(e) + e.printStackTrace() + val lambdaGroup = newProgramClassPool.getClass("app/package2/LambdaGroup") + lambdaGroup.accept(AllMemberVisitor( + MemberNameFilter("invoke", + AllAttributeVisitor( + AllInstructionVisitor( + InstructionPrinter())))) + ) + throw e + } + + System.setOut(stdOutput) + + capturedOutputAfter.toString() shouldBe capturedOutputBefore.toString() + println(capturedOutputBefore) + println() + println(capturedOutputAfter) + } + */ } } }) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/LambdaGroupMappingPrinterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/LambdaGroupMappingPrinterTest.kt new file mode 100644 index 000000000..bca709988 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/LambdaGroupMappingPrinterTest.kt @@ -0,0 +1,46 @@ +package proguard.optimize.kotlin + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import proguard.classfile.AccessConstants +import proguard.classfile.VersionConstants +import proguard.classfile.editor.ClassBuilder +import proguard.optimize.info.ProgramClassOptimizationInfo +import proguard.optimize.info.ProgramClassOptimizationInfoSetter +import java.io.ByteArrayOutputStream +import java.io.PrintWriter + +class LambdaGroupMappingPrinterTest : FreeSpec({ + "Given a lambda class and a lambda group" - { + val arity = 0 + val lambdaClassId = 0 + val lambdaClassName = "LambdaClass" + val lambdaGroupName = "LambdaGroup" + val lambdaClass = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.FINAL, + lambdaClassName, + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA + ).addInterface("${KotlinLambdaMerger.NAME_KOTLIN_FUNCTION}$arity").programClass + val lambdaGroup = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.FINAL, + lambdaGroupName, + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA + ).programClass + lambdaClass.accept(ProgramClassOptimizationInfoSetter()) + val optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass) + optimizationInfo.lambdaGroup = lambdaGroup + optimizationInfo.lambdaGroupClassId = lambdaClassId + "When the mapping of the lambda class is printed" - { + val outputStream = ByteArrayOutputStream() + val mappingPrinter = LambdaGroupMappingPrinter(PrintWriter(outputStream, true)) + lambdaClass.accept(mappingPrinter) + "Then the correct lambda group, arity and case are printed" { + val mappingEntry = outputStream.toString() + val expectedMappingEntry = "$lambdaClassName -> $lambdaGroupName (arity $arity, case $lambdaClassId)${System.lineSeparator()}" + mappingEntry shouldBe expectedMappingEntry + } + } + } +}) diff --git a/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt new file mode 100644 index 000000000..7ba9f0af6 --- /dev/null +++ b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt @@ -0,0 +1,29 @@ +package testutils + +import proguard.classfile.ClassPool +import proguard.classfile.io.ProgramClassWriter +import proguard.classfile.util.ClassUtil.internalClassName +import proguard.classfile.visitor.ProgramClassFilter +import java.io.ByteArrayOutputStream +import java.io.DataOutputStream + +/** + * A ClassLoader that can load classes from a ProGuardCORE ClassPool. + * Copyright: https://github.com/mrjameshamilton/klox/blob/master/src/main/kotlin/eu/jameshamilton/klox/util/ClassPoolClassLoader.kt + */ +class ClassPoolClassLoader(private val classPool: ClassPool) : ClassLoader() { + override fun findClass(name: String): Class<*> { + val clazz = classPool.getClass(internalClassName(name)) + val byteArrayOutputStream = ByteArrayOutputStream() + try { + println(name) + clazz.accept(ProgramClassFilter(ProgramClassWriter(DataOutputStream(byteArrayOutputStream)))) + } catch (e: Exception) { + println("Exception for class $name: $e") + throw e + } + val bytes = byteArrayOutputStream.toByteArray() + if (clazz != null) return defineClass(name, bytes, 0, bytes.size) + return super.findClass(name) + } +} diff --git a/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt b/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt index dcfc57894..4b386cd23 100644 --- a/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt +++ b/base/src/test/kotlin/testutils/InstructionSequenceCollector.kt @@ -29,16 +29,19 @@ class InstructionSequenceCollector(val builder: InstructionSequenceBuilder) : In ) { val constantPoolEditor = builder.constantPoolEditor var constantIndex = 0 - assert (constantInstruction != null) - assert (constantInstruction!!.constantIndex > 0) - clazz?.constantPoolEntryAccept(constantInstruction.constantIndex, object : ConstantVisitor { - override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) { - constantIndex = constantPoolEditor.addConstant(constant) + assert(constantInstruction != null) + assert(constantInstruction!!.constantIndex > 0) + clazz?.constantPoolEntryAccept( + constantInstruction.constantIndex, + object : ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) { + constantIndex = constantPoolEditor.addConstant(constant) + } } - }) - assert (constantIndex == 0 ) + ) + assert(constantIndex == 0) val newConstantInstruction = ConstantInstruction().copy(constantInstruction) newConstantInstruction.constantIndex = constantIndex visitAnyInstruction(clazz!!, method!!, codeAttribute!!, offset, newConstantInstruction) } -} \ No newline at end of file +} diff --git a/base/src/test/kotlin/testutils/MatchDetector.kt b/base/src/test/kotlin/testutils/MatchDetector.kt index dcedc6721..0e722f8be 100644 --- a/base/src/test/kotlin/testutils/MatchDetector.kt +++ b/base/src/test/kotlin/testutils/MatchDetector.kt @@ -25,4 +25,4 @@ class MatchDetector(val matcher: InstructionSequenceMatcher, vararg val argument matchedArguments = matcher.matchedArguments(arguments) } } -} \ No newline at end of file +} From 49debdf37d7e39a90f559b8256aab275ebd52ea7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Mon, 6 Jun 2022 23:56:23 +0200 Subject: [PATCH 153/195] Test added for KotlinLambdaEnclosingMethodUpdater --- .../KotlinLambdaEnclosingMethodUpdaterTest.kt | 212 ++++++++++++++++++ 1 file changed, 212 insertions(+) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdaterTest.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdaterTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdaterTest.kt new file mode 100644 index 000000000..9bca3ea84 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdaterTest.kt @@ -0,0 +1,212 @@ +package proguard.optimize.kotlin + +import io.kotest.core.spec.IsolationMode +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldNotContain +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import proguard.classfile.AccessConstants +import proguard.classfile.ClassConstants +import proguard.classfile.Clazz +import proguard.classfile.Method +import proguard.classfile.ProgramClass +import proguard.classfile.ProgramMethod +import proguard.classfile.VersionConstants +import proguard.classfile.attribute.Attribute +import proguard.classfile.attribute.CodeAttribute +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.attribute.visitor.AttributeVisitor +import proguard.classfile.attribute.visitor.MultiAttributeVisitor +import proguard.classfile.constant.AnyMethodrefConstant +import proguard.classfile.constant.ClassConstant +import proguard.classfile.constant.Constant +import proguard.classfile.constant.visitor.ConstantVisitor +import proguard.classfile.editor.ClassBuilder +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.ConstantInstruction +import proguard.classfile.instruction.Instruction +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.instruction.visitor.InstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher +import proguard.classfile.visitor.AllMemberVisitor +import proguard.io.ExtraDataEntryNameMap +import testutils.ClassPoolBuilder +import testutils.KotlinSource +import testutils.MatchDetector + +class KotlinLambdaEnclosingMethodUpdaterTest : FreeSpec({ + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + fun main() { + val lambda1 = { println("Lambda1") } + val lambda2 = { println("Lambda2") } + val lambda3 = { println("Lambda3") } + lambda1() + lambda2() + lambda3() + } + """.trimIndent() + ) + ) + "Given a lambda class, a lambda group and an enclosing method updater" - { + val lambdaClass = programClassPool.getClass("TestKt\$main\$lambda1\$1") as ProgramClass + val enclosingClass = programClassPool.getClass("TestKt") as ProgramClass + println("Lambda class: $lambdaClass") + val lambdaGroupName = "LambdaGroup" + val lambdaGroup = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.FINAL or AccessConstants.SUPER, + lambdaGroupName, + KotlinLambdaMerger.NAME_KOTLIN_LAMBDA + ).programClass + val initMethod = ClassBuilder(lambdaGroup).addAndReturnMethod( + AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + "(II)V" + ) + val classId = 0 + val arity = 1 + val constructorDescriptor = "(II)V" + val nameMapper = ExtraDataEntryNameMap() + val enclosingMethodUpdater = KotlinLambdaEnclosingMethodUpdater( + programClassPool, + libraryClassPool, + lambdaClass, + lambdaGroup, + classId, + arity, + constructorDescriptor, + nameMapper + ) + "When the enclosing method of the lambda class is updated" - { + lambdaClass.accept( + AllAttributeVisitor( + enclosingMethodUpdater + ) + ) + val referencedClasses = ArrayList() + val referencedClassNames = ArrayList() + enclosingClass.constantPoolEntriesAccept(object : ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {} + override fun visitClassConstant(clazz: Clazz?, classConstant: ClassConstant?) { + println("$clazz contains a ClassConstant referencing ${classConstant!!.referencedClass}") + referencedClasses.add(classConstant.referencedClass) + referencedClassNames.add(classConstant.getName(clazz)) + } + }) + "Then the enclosing class no longer references the lambda class" { + referencedClasses shouldNotContain lambdaClass + referencedClassNames shouldNotContain lambdaClass.name + } + "Then the enclosing class references the lambda group" { + referencedClassNames shouldContain lambdaGroupName + } + "Then the enclosing method initialises the lambda group with the correct arity and class id" { + val instructionSequenceBuilder = InstructionSequenceBuilder(programClassPool, libraryClassPool) + instructionSequenceBuilder + .new_(lambdaGroup) + .dup() + .iconst(classId) + .iconst(arity) + .invokespecial(lambdaGroup, initMethod) + val matchDetector = MatchDetector( + InstructionSequenceMatcher(instructionSequenceBuilder.constants(), instructionSequenceBuilder.instructions()) + ) + enclosingClass.accept( + AllMemberVisitor( + AllAttributeVisitor( + AllInstructionVisitor( + matchDetector + ) + ) + ) + ) + matchDetector.matchIsFound shouldBe true + } + } + "When a method is visited that does not use the lambda class" - { + val nonEnclosingMethod = enclosingClass.findMethod("main", "([Ljava/lang/String;)V") as ProgramMethod + val (originalInstructions, originalAttributes) = nonEnclosingMethod.getInstructionsAndAttributes(enclosingClass) + nonEnclosingMethod.accept(enclosingClass, enclosingMethodUpdater) + "Then the enclosing method updater does not modify this method" { + val (resultingInstructions, resultingAttributes) = nonEnclosingMethod.getInstructionsAndAttributes(enclosingClass) + resultingAttributes.forEach { originalAttributes shouldContain it } + originalAttributes.forEach { resultingAttributes shouldContain it } + resultingInstructions.forEach { originalInstructions shouldContain it } + originalInstructions.forEach { resultingInstructions shouldContain it } + } + "Then none of the instructions of this method references a method of the lambda group" { + nonEnclosingMethod.accept( + enclosingClass, + AllAttributeVisitor( + AllInstructionVisitor( + object : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz?, + method: Method?, + codeAttribute: CodeAttribute?, + offset: Int, + instruction: Instruction? + ) {} + override fun visitConstantInstruction( + clazz: Clazz?, + method: Method?, + codeAttribute: CodeAttribute?, + offset: Int, + constantInstruction: ConstantInstruction? + ) { + enclosingClass.constantPoolEntryAccept( + constantInstruction!!.constantIndex, + object : ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {} + override fun visitAnyMethodrefConstant( + clazz: Clazz?, + anyMethodrefConstant: AnyMethodrefConstant? + ) { + anyMethodrefConstant?.referencedClass shouldNotBe lambdaGroup + } + } + ) + } + } + ) + ) + ) + } + } + } +}) { + override fun isolationMode(): IsolationMode = IsolationMode.InstancePerLeaf +} + +fun ProgramMethod.getInstructionsAndAttributes(programClass: ProgramClass): Pair, List> { + val instructions = ArrayList() + val attributes = ArrayList() + this.accept( + programClass, + AllAttributeVisitor( + MultiAttributeVisitor( + object : AttributeVisitor { + override fun visitAnyAttribute(clazz: Clazz?, attribute: Attribute?) { + attributes.add(attribute!!) + } + }, + AllInstructionVisitor(object : InstructionVisitor { + override fun visitAnyInstruction( + clazz: Clazz?, + method: Method?, + codeAttribute: CodeAttribute?, + offset: Int, + instruction: Instruction? + ) { + instructions.add(instruction!!) + } + }) + ) + ) + ) + return Pair(instructions, attributes) +} From b2d8b57a0f09636d05c78553d73489783432701f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 7 Jun 2022 08:49:43 +0200 Subject: [PATCH 154/195] Skeleton for KotlinLambdaGroupBuilderTest --- .../kotlin/KotlinLambdaGroupBuilderTest.kt | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt new file mode 100644 index 000000000..298895541 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -0,0 +1,57 @@ +package proguard.optimize.kotlin + +import io.kotest.core.spec.style.FreeSpec +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class KotlinLambdaGroupBuilderTest : FreeSpec({ + val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test2.kt", + """ + package app.package2 + fun main() { + val lambda1 = { println("Lambda1") } + val lambda2 = { println("Lambda2") } + val lambda3 = { println("Lambda3") } + lambda1() + lambda2() + lambda3() + } + """.trimIndent() + ) + ) + + "Given a lambda group builder" - { + "When the builder is adds a lambda class to the lambda group under construction" - { + + "Then the lambda class implementation has been added to the lambda group" { + } + + "Then the lambda group has the same output on invocation as the original lambda class" { + /*val stdOutput = System.out + val capturedOutputBefore = ByteArrayOutputStream() + val capturedOutputStreamBefore = PrintStream(capturedOutputBefore) + //System.setOut(capturedOutputStreamBefore) + + testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic}.invoke(null, arrayOf()) + + val loaderAfter = ClassPoolClassLoader(newProgramClassPool) + val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") + val capturedOutputAfter = ByteArrayOutputStream() + val capturedOutputStreamAfter = PrintStream(capturedOutputBefore) + //System.setOut(capturedOutputStreamAfter) + + testClassAfter.declaredMethods.single { it.name == "main" && it.isSynthetic}.invoke(null, arrayOf()) + + System.setOut(stdOutput) + + capturedOutputAfter.toByteArray().toString() shouldBe capturedOutputBefore.toByteArray().toString() + println(capturedOutputAfter.toByteArray()) + println() + println(capturedOutputBefore.toByteArray()) + */ + } + } + } +}) From 784e884422f111158bbeb4ca737e2a6a3d60a1b2 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 7 Jun 2022 09:53:18 +0200 Subject: [PATCH 155/195] Tests for the KotlinLambdaGroupBuilder --- .../kotlin/KotlinLambdaGroupBuilderTest.kt | 89 +++++++++++++++++-- 1 file changed, 84 insertions(+), 5 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt index 298895541..a576b6962 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -1,15 +1,31 @@ package proguard.optimize.kotlin import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual +import io.kotest.matchers.shouldBe +import proguard.Configuration +import proguard.classfile.AccessConstants +import proguard.classfile.ProgramClass +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher +import proguard.classfile.visitor.AllMemberVisitor +import proguard.classfile.visitor.MemberAccessFilter +import proguard.classfile.visitor.MemberNameFilter +import proguard.io.ExtraDataEntryNameMap +import proguard.optimize.info.ProgramClassOptimizationInfo +import proguard.optimize.info.ProgramClassOptimizationInfoSetter import testutils.ClassPoolBuilder +import testutils.InstructionSequenceCollector import testutils.KotlinSource +import testutils.MatchDetector class KotlinLambdaGroupBuilderTest : FreeSpec({ val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( KotlinSource( - "Test2.kt", + "Test.kt", """ - package app.package2 fun main() { val lambda1 = { println("Lambda1") } val lambda2 = { println("Lambda2") } @@ -22,12 +38,74 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ ) ) - "Given a lambda group builder" - { - "When the builder is adds a lambda class to the lambda group under construction" - { - + "Given a lambda group builder and a lambda class" - { + val lambdaGroupName = "LambdaGroup" + val configuration = Configuration() + val entryMapper = ExtraDataEntryNameMap() + val notMergedLambdaVisitor = null + val builder = KotlinLambdaGroupBuilder( + lambdaGroupName, + configuration, + programClassPool, + libraryClassPool, + entryMapper, + notMergedLambdaVisitor + ) + val lambdaClass = programClassPool.getClass("TestKt\$main\$lambda1\$1") as ProgramClass + lambdaClass.accept(ProgramClassOptimizationInfoSetter()) + val optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass) + val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(programClassPool, libraryClassPool) + lambdaClass.accept( + AllMemberVisitor( + MemberNameFilter( + "invoke", + MemberAccessFilter( + 0, AccessConstants.SYNTHETIC, + AllAttributeVisitor( + AllInstructionVisitor( + InstructionSequenceCollector( + originalInvokeMethodCodeBuilder + ) + ) + ) + ) + ) + ) + ) + val originalInstructionsMatcher = InstructionSequenceMatcher( + originalInvokeMethodCodeBuilder.constants(), + originalInvokeMethodCodeBuilder.instructions() + ) + val originalInstructionsMatchDetector = MatchDetector(originalInstructionsMatcher) + "When the builder adds a lambda class to the lambda group under construction" - { + builder.visitProgramClass(lambdaClass) + val lambdaGroup = builder.build() "Then the lambda class implementation has been added to the lambda group" { + lambdaGroup.accept( + AllMemberVisitor( + AllAttributeVisitor( + AllInstructionVisitor( + originalInstructionsMatchDetector + ) + ) + ) + ) + originalInstructionsMatchDetector.matchIsFound shouldBe true + } + + "Then the optimization info lambda group of the lambda class refers to the lambda group" { + optimizationInfo.lambdaGroup shouldBe lambdaGroup + } + + "Then the optimization info target class of the lambda class refers to the lambda group" { + optimizationInfo.targetClass shouldBe lambdaGroup + } + + "Then the optimization info class id of the lambda class is 0 or greater" { + optimizationInfo.lambdaGroupClassId shouldBeGreaterThanOrEqual 0 } + /* "Then the lambda group has the same output on invocation as the original lambda class" { /*val stdOutput = System.out val capturedOutputBefore = ByteArrayOutputStream() @@ -52,6 +130,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ println(capturedOutputBefore.toByteArray()) */ } + */ } } }) From ee581ef26d113ab165577bafe0b57b86d21c6a32 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Tue, 7 Jun 2022 10:18:28 +0200 Subject: [PATCH 156/195] Unused class KotlinLambdaGroupBuilder removed --- .../kotlin/KotlinLambdaGroupBuilder.java | 7 -- .../kotlin/KotlinLambdaGroupInitBuilder.java | 88 ------------------- 2 files changed, 95 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 1edd0cfbd..358996c74 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -404,13 +404,6 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaC enclosingMethodUpdater))); } - private void addInitConstructors() - { - // TODO: add support for non-empty closures - KotlinLambdaGroupInitBuilder builder = new KotlinLambdaGroupInitBuilder(0, this.classBuilder); - builder.build(); - } - private void addInvokeMethods() { for (KotlinLambdaGroupInvokeMethodBuilder builder : this.invokeMethodBuilders.values()) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java deleted file mode 100644 index 04fcaf2a2..000000000 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitBuilder.java +++ /dev/null @@ -1,88 +0,0 @@ -package proguard.optimize.kotlin; - -import proguard.classfile.AccessConstants; -import proguard.classfile.ClassConstants; -import proguard.classfile.ProgramField; -import proguard.classfile.ProgramMethod; -import proguard.classfile.editor.ClassBuilder; -import proguard.classfile.editor.CompactCodeAttributeComposer; - -public class KotlinLambdaGroupInitBuilder { - - public static final String TYPE_KOTLIN_LAMBDA_INIT = "(I)V"; - public static final String RETURN_TYPE_INIT = "V"; - public static final String METHOD_ARGUMENT_TYPE_INIT = "Ljava/lang/Object;"; - - private final int closureSize; - private final ClassBuilder classBuilder; - - public KotlinLambdaGroupInitBuilder(int closureSize, ClassBuilder classBuilder) - { - // TODO: support arguments of specific types - // e.g. by taking a list of argument types instead of a closure size - this.closureSize = closureSize; - this.classBuilder = classBuilder; - } - - private static String getInitDescriptorForClosureSize(int closureSize) - { - StringBuilder descriptor = new StringBuilder("("); - for (int argumentIndex = 1; argumentIndex <= closureSize; argumentIndex++) - { - descriptor.append(METHOD_ARGUMENT_TYPE_INIT); - } - descriptor.append(KotlinLambdaGroupBuilder.FIELD_TYPE_ID) - .append("I") // arity argument - TODO: use constant instead of string value - .append(")") - .append(RETURN_TYPE_INIT); - return descriptor.toString(); - } - - private CompactCodeAttributeComposer addPutFreeVariablesInFieldsInstructions(CompactCodeAttributeComposer composer) - { - for (int argumentIndex = 1; argumentIndex <= this.closureSize; argumentIndex++) - { - composer.aload(argumentIndex) - .putfield(this.classBuilder.getProgramClass(), - this.classBuilder.getProgramClass().findField( - KotlinLambdaGroupBuilder.FIELD_NAME_PREFIX_FREE_VARIABLE + argumentIndex, - KotlinLambdaGroupBuilder.FIELD_TYPE_FREE_VARIABLE - )); - } - return composer; - } - - public ClassBuilder.CodeBuilder buildCodeBuilder() - { - return code -> { - code - .aload_0(); // load this class - // TODO: load parameter variables and store them in their respective fields - addPutFreeVariablesInFieldsInstructions(code) - .iload(this.closureSize + 1) // load the id argument - .putfield(this.classBuilder.getProgramClass(), // store the id in a field - this.classBuilder.getProgramClass() - .findField(KotlinLambdaGroupBuilder.FIELD_NAME_ID, - KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) - .aload_0() // load this class - .iload(this.closureSize + 2) // load arity argument - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, - ClassConstants.METHOD_NAME_INIT, - TYPE_KOTLIN_LAMBDA_INIT) - .return_(); - }; - } - - public ProgramMethod build() - { - // add a classId field to the lambda group - ProgramField classIdField = classBuilder.addAndReturnField(AccessConstants.PRIVATE, "classId", "I"); - - // add a constructor which takes an id as argument and stores it in the classId field - return classBuilder.addAndReturnMethod(AccessConstants.PUBLIC, - ClassConstants.METHOD_NAME_INIT, - getInitDescriptorForClosureSize(this.closureSize), - 50, - this.buildCodeBuilder()); - } -} From dc7cd78a6d3b2878c1f7ac9929d142242c888c7f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:08:23 +0200 Subject: [PATCH 157/195] Code alignment --- .../kotlin/KotlinLambdaClassMerger.java | 3 +- .../KotlinLambdaEnclosingMethodUpdater.java | 68 ++++++++++++------- .../kotlin/KotlinLambdaGroupBuilder.java | 49 ++++++++----- .../kotlin/KotlinLambdaGroupInitUpdater.java | 21 ++++-- .../optimize/kotlin/KotlinLambdaMerger.java | 55 ++++++++++----- .../kotlin/LambdaGroupMappingPrinter.java | 3 +- 6 files changed, 135 insertions(+), 64 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index cac51252e..12ffc3fe9 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -83,7 +83,8 @@ public void visitClassPool(ClassPool lambdaClassPool) ProgramClass lambdaGroup = lambdaGroupBuilder.build(); ProgramClassOptimizationInfo.setProgramClassOptimizationInfo(lambdaGroup); - ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaGroup); + ProgramClassOptimizationInfo optimizationInfo = + ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaGroup); optimizationInfo.setLambdaGroup(lambdaGroup); // let the lambda group visitor visit the newly created lambda group diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 9db4ad823..9f0541c63 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -37,8 +37,8 @@ public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, Attribu private static final Logger logger = LogManager.getLogger(KotlinLambdaEnclosingMethodUpdater.class); private Clazz currentLambdaClass; private Clazz currentEnclosingClass; - private SortedSet offsetsWhereLambdaIsReferenced; - private static final RetargetedInnerClassAttributeRemover retargetedInnerClassAttributeRemover = new RetargetedInnerClassAttributeRemover(); + private static final RetargetedInnerClassAttributeRemover retargetedInnerClassAttributeRemover = + new RetargetedInnerClassAttributeRemover(); public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, ClassPool libraryClassPool, @@ -141,11 +141,14 @@ public void visitCodeAttribute(Clazz enclosingClass, Method enclosingMethod, Cod } catch (ClassCastException exception) { - logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), enclosingClass, enclosingMethod); + logger.error("{} is incorrectly used to visit non-program class / method {} / {}", + this.getClass().getName(), enclosingClass, enclosingMethod); } } - public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclosingMethod, CodeAttribute codeAttribute) + public void visitCodeAttribute(ProgramClass enclosingClass, + ProgramMethod enclosingMethod, + CodeAttribute codeAttribute) { // the given class must be the class that defines the lambda // the given method must be the method where the lambda is defined @@ -161,7 +164,8 @@ public void visitCodeAttribute(ProgramClass enclosingClass, ProgramMethod enclos CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); BranchTargetFinder branchTargetFinder = new BranchTargetFinder(); - InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor); + InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, + codeAttributeEditor); codeAttribute.accept(enclosingClass, enclosingMethod, @@ -184,55 +188,64 @@ private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTar private Instruction[][][] createReplacementPatternsForLambda(InstructionSequenceBuilder builder) { - // TODO: ensure that the correct method is selected - // TODO: decide what to do if multiple methods exist Method initMethod = currentLambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); - Method specificInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, false); - Method bridgeInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, true); + Method specificInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, + false); + Method bridgeInvokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod((ProgramClass)currentLambdaClass, + true); return new Instruction[][][] { // Empty closure lambda's { // Lambda is 'instantiated' by referring to its static INSTANCE field builder.getstatic(currentLambdaClass.getName(), - KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, - ClassUtil.internalTypeFromClassName(currentLambdaClass.getName())).__(), + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, + ClassUtil.internalTypeFromClassName(currentLambdaClass.getName())) + .__(), builder.new_(lambdaGroup) .dup() .iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() + .invokespecial(lambdaGroup.getName(), + ClassConstants.METHOD_NAME_INIT, + "(II)V") + .__() }, { // Lambda is explicitly instantiated builder.new_(currentLambdaClass) .dup() - .invokespecial(currentLambdaClass, initMethod).__(), + .invokespecial(currentLambdaClass, initMethod) + .__(), builder.new_(lambdaGroup) .dup() .iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, "(II)V").__() + .invokespecial(lambdaGroup.getName(), + ClassConstants.METHOD_NAME_INIT, + "(II)V") + .__() }, // Non-empty closure lambda's { // Lambda is explicitly instantiated with free variables as arguments (part 1) builder.new_(currentLambdaClass) - //.dup() .__(), builder.new_(lambdaGroup) - //.dup() .__() }, { - builder.invokespecial(currentLambdaClass, initMethod).__(), + builder.invokespecial(currentLambdaClass, initMethod) + .__(), builder.iconst(classId) .iconst(arity) - .invokespecial(lambdaGroup.getName(), ClassConstants.METHOD_NAME_INIT, constructorDescriptor) + .invokespecial(lambdaGroup.getName(), + ClassConstants.METHOD_NAME_INIT, + constructorDescriptor) .__() }, // Direct invocation of named lambda's @@ -240,19 +253,25 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence builder.invokevirtual(currentLambdaClass, specificInvokeMethod) .__(), - builder.invokevirtual(lambdaGroup.getName(), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, specificInvokeMethod.getDescriptor(currentLambdaClass)) + builder.invokevirtual(lambdaGroup.getName(), + KotlinConstants.METHOD_NAME_LAMBDA_INVOKE, + specificInvokeMethod.getDescriptor(currentLambdaClass)) .__() }, { builder.invokevirtual(currentLambdaClass, bridgeInvokeMethod) .__(), - builder.invokevirtual(lambdaGroup.getName(), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, bridgeInvokeMethod.getDescriptor(currentLambdaClass)) + builder.invokevirtual(lambdaGroup.getName(), + KotlinConstants.METHOD_NAME_LAMBDA_INVOKE, + bridgeInvokeMethod.getDescriptor(currentLambdaClass)) .__() } }; } + // Implementations for ClassVisitor + @Override public void visitAnyClass(Clazz clazz) {} @@ -264,13 +283,15 @@ public void visitProgramClass(ProgramClass programClass) { { if (currentEnclosingClass == null) { - logger.warn("Lambda class {} is referenced by {}, while no enclosing class was linked to this lambda class.", + logger.warn("Lambda class {} is referenced by {}, while no enclosing class was linked to this " + + "lambda class.", ClassUtil.externalClassName(currentLambdaClass.getName()), ClassUtil.externalClassName(programClass.getName())); } else if (!currentEnclosingClass.equals(programClass) && !currentLambdaClass.equals(programClass)) { - logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class or the lambda class itself.", + logger.warn("Lambda class {} is referenced by {}, which is not the enclosing class or the " + + "lambda class itself.", ClassUtil.externalClassName(currentLambdaClass.getName()), ClassUtil.externalClassName(programClass.getName())); } @@ -285,7 +306,8 @@ else if (!currentEnclosingClass.equals(programClass) && !currentLambdaClass.equa new DescriptorTypeUpdater( ClassUtil.internalTypeFromClassName(this.currentLambdaClass.getName()), ClassUtil.internalTypeFromClassName(this.lambdaGroup.getName())), - new ClassConstantReferenceUpdater(this.currentLambdaClass, this.lambdaGroup))); + new ClassConstantReferenceUpdater(this.currentLambdaClass, + this.lambdaGroup))); // remove any old links between lambda's and their inner classes programClass.accept(KotlinLambdaEnclosingMethodUpdater.retargetedInnerClassAttributeRemover); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 358996c74..307ead457 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -62,7 +62,9 @@ public KotlinLambdaGroupBuilder(final String lambdaGroupName, final ExtraDataEntryNameMap extraDataEntryNameMap, final ClassVisitor notMergedLambdaVisitor) { - this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, libraryClassPool); + this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, + programClassPool, + libraryClassPool); this.configuration = configuration; this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; @@ -74,7 +76,9 @@ public KotlinLambdaGroupBuilder(final String lambdaGroupName, initialiseLambdaGroup(); } - private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName, ClassPool programClassPool, ClassPool libraryClassPool) + private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName, + ClassPool programClassPool, + ClassPool libraryClassPool) { // The initial builder is used to set up the initial lambda group class ClassBuilder initialBuilder = new ClassBuilder(VersionConstants.CLASS_VERSION_1_8, @@ -110,7 +114,10 @@ private KotlinLambdaGroupInvokeMethodBuilder getInvokeMethodBuilder(int arity) KotlinLambdaGroupInvokeMethodBuilder builder = this.invokeMethodBuilders.get(arity); if (builder == null) { - builder = new KotlinLambdaGroupInvokeMethodBuilder(arity, this.classBuilder, this.programClassPool, this.libraryClassPool); + builder = new KotlinLambdaGroupInvokeMethodBuilder(arity, + this.classBuilder, + this.programClassPool, + this.libraryClassPool); this.invokeMethodBuilders.put(arity, builder); } return builder; @@ -282,7 +289,8 @@ private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaCl logger.trace("Copying method of {} to lambda group {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalClassName(this.classBuilder.getProgramClass().getName())); - ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, null); //, 0, AccessConstants.PRIVATE); + ProgramMethod initMethod = (ProgramMethod)lambdaClass.findMethod(ClassConstants.METHOD_NAME_INIT, + null); if (initMethod == null) { throw new NullPointerException("No method was found in lambda class " + lambdaClass); @@ -298,17 +306,20 @@ private ProgramMethod copyOrMergeLambdaInitIntoLambdaGroup(ProgramClass lambdaCl String newInitDescriptor = KotlinLambdaGroupInitUpdater.getNewInitMethodDescriptor(lambdaClass, initMethod); // Check whether an init method with this descriptor exists already - ProgramMethod existingInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, newInitDescriptor); + ProgramMethod existingInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, + newInitDescriptor); if (existingInitMethod != null) { - //logger.info("Multiple lambda classes (of which {}) with the same descriptor ({}) are merged in the same lambda group ({}).", lambdaClass, oldInitDescriptor, this.classBuilder.getProgramClass()); - //new ClassEditor(lambdaGroup).removeMethod(existingInitMethod); return existingInitMethod; } - initMethod.accept(lambdaClass, new MethodCopier(lambdaGroup, ClassConstants.METHOD_NAME_INIT, oldInitDescriptor, AccessConstants.PUBLIC));//, true)); - ProgramMethod newInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, oldInitDescriptor); + initMethod.accept(lambdaClass, new MethodCopier(lambdaGroup, + ClassConstants.METHOD_NAME_INIT, + oldInitDescriptor, + AccessConstants.PUBLIC)); + ProgramMethod newInitMethod = (ProgramMethod)lambdaGroup.findMethod(ClassConstants.METHOD_NAME_INIT, + oldInitDescriptor); // Update the descriptor // Add the necessary instructions to entirely new methods @@ -334,11 +345,11 @@ private ProgramMethod copyLambdaInvokeToLambdaGroup(ProgramClass lambdaClass) ClassUtil.externalClassName(lambdaClass.getName())); } String newMethodName = createDerivedInvokeMethodName(lambdaClass); - invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), newMethodName, AccessConstants.PRIVATE)); - ProgramMethod newInitMethod = (ProgramMethod)this.classBuilder.getProgramClass().findMethod(newMethodName, invokeMethod.getDescriptor(lambdaClass)); - return newInitMethod; - // TODO: ensure that fields that are referenced by the copied method exist in the lambda group, are initialised, - // and cast to the correct type inside the copied method + invokeMethod.accept(lambdaClass, new MethodCopier(this.classBuilder.getProgramClass(), + newMethodName, + AccessConstants.PRIVATE)); + return (ProgramMethod) this.classBuilder.getProgramClass().findMethod(newMethodName, + invokeMethod.getDescriptor(lambdaClass)); } private static String createDerivedInvokeMethodName(ProgramClass lambdaClass) @@ -383,7 +394,10 @@ public static ProgramMethod getInvokeMethod(ProgramClass lambdaClass, boolean is * @param lambdaClass the lambda class of which the enclosing method must be updated * @param lambdaClassId the id that is used for the given lambda class to identify its implementation in the lambda group */ - private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaClassId, int arity, String constructorDescriptor) + private void updateLambdaInstantiationSite(ProgramClass lambdaClass, + int lambdaClassId, + int arity, + String constructorDescriptor) { logger.info("Updating instantiation of {} in enclosing method(s) to use id {}.", ClassUtil.externalClassName(lambdaClass.getName()), lambdaClassId); @@ -400,8 +414,9 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, int lambdaC // Also update any references that would occur in other classes of the same package. this.programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", - new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, - enclosingMethodUpdater))); + new ClassNameFilter(lambdaClass.getName(), + (ClassVisitor)null, + enclosingMethodUpdater))); } private void addInvokeMethods() diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index 78c40b82d..c7ae878a0 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -42,7 +42,8 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM private void updateInitMethodDescriptor(ProgramClass programClass, ProgramMethod programMethod) { String newInitDescriptor = getNewInitMethodDescriptor(programClass, programMethod); - programMethod.u2descriptorIndex = new ConstantAdder(programClass).addConstant(programClass, new Utf8Constant(newInitDescriptor)); + programMethod.u2descriptorIndex = new ConstantAdder(programClass).addConstant(programClass, + new Utf8Constant(newInitDescriptor)); } @Override @@ -55,7 +56,10 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt } catch (ClassCastException exception) { - logger.error("{} is incorrectly used to visit non-program class / method {} / {}", this.getClass().getName(), ClassUtil.externalClassName(clazz.getName()), ClassUtil.externalFullMethodDescription(clazz.getName(), method.getAccessFlags(), method.getName(clazz), method.getDescriptor(clazz))); + logger.error("{} is incorrectly used to visit non-program class / method {} / {}", + this.getClass().getName(), ClassUtil.externalClassName(clazz.getName()), + ClassUtil.externalFullMethodDescription(clazz.getName(), + method.getAccessFlags(), method.getName(clazz), method.getDescriptor(clazz))); } } @@ -70,7 +74,10 @@ public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programM // The classId and arity arguments are the 2 last, which take 1 byte each, as they are of type int. int arityIndex = ClassUtil.internalMethodParameterSize(programMethod.getDescriptor(programClass), false) - 1; - InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, codeAttributeEditor, arityIndex, programClass); + InstructionSequencesReplacer replacer = createInstructionSequenceReplacer(branchTargetFinder, + codeAttributeEditor, + arityIndex, + programClass); codeAttribute.accept(programClass, programMethod, @@ -111,7 +118,9 @@ private Instruction[][][] createReplacementPatternsForInit(InstructionSequenceBu { builder.aload_0() .iconst(X) - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + "(I)V") .__(), builder.aload_0() .iload(arityIndex - 1) @@ -121,7 +130,9 @@ private Instruction[][][] createReplacementPatternsForInit(InstructionSequenceBu KotlinLambdaGroupBuilder.FIELD_TYPE_ID)) .aload_0() .iload(arityIndex) - .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, ClassConstants.METHOD_NAME_INIT, "(I)V") + .invokespecial(KotlinLambdaMerger.NAME_KOTLIN_LAMBDA, + ClassConstants.METHOD_NAME_INIT, + "(I)V") .__() } }; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index dc02d2144..38d72bcb5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -75,9 +75,13 @@ public void execute(AppView appView) throws Exception // find all lambda classes with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty - appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, newProgramClassPoolFiller)); - appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, - new ImplementedClassFilter(kotlinLambdaClass, false, + appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, + 0, + newProgramClassPoolFiller)); + appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, + ProcessingFlags.DONT_OPTIMIZE, + new ImplementedClassFilter(kotlinLambdaClass, + false, new ClassPoolFiller(lambdaClassPool), newProgramClassPoolFiller)) ); @@ -111,7 +115,8 @@ public void execute(AppView appView) throws Exception PrintWriter out = new PrintWriter(System.out, true); if (configuration.printLambdaGroupMapping != null) { - logger.info("Printing lambda group mapping to [{}]...", PrintWriterUtil.fileName(configuration.printLambdaGroupMapping)); + logger.info("Printing lambda group mapping to [{}]...", + PrintWriterUtil.fileName(configuration.printLambdaGroupMapping)); PrintWriter mappingWriter = PrintWriterUtil.createPrintWriter(configuration.printLambdaGroupMapping, out); @@ -174,7 +179,9 @@ private void inlineMethodsInsideLambdaGroups(ClassPool lambdaGroupClassPool) logger.debug("{} methods inlined inside lambda groups.", methodInliningCounter.getCount()); } - private void shrinkLambdaGroups(ClassPool programClassPool, ClassPool libraryClassPool, ClassPool lambdaGroupClassPool) + private void shrinkLambdaGroups(ClassPool programClassPool, + ClassPool libraryClassPool, + ClassPool lambdaGroupClassPool) { SimpleUsageMarker simpleUsageMarker = new SimpleUsageMarker(); ClassUsageMarker classUsageMarker = new ClassUsageMarker(simpleUsageMarker); @@ -233,7 +240,7 @@ public static String getArityFromInterface(ProgramClass programClass) String interfaceName = programClass.getInterfaceName(interfaceIndex); if (interfaceName.startsWith(NAME_KOTLIN_FUNCTION) && interfaceName.length() > NAME_KOTLIN_FUNCTION.length()) { - return String.valueOf(interfaceName.charAt(interfaceName.length()-1)); + return interfaceName.substring(NAME_KOTLIN_FUNCTION.length()); } } throw new IllegalArgumentException("Class " + ClassUtil.externalClassName(programClass.getName()) + " does not implement a Kotlin function interface."); @@ -246,10 +253,12 @@ public static String getArityFromInterface(ProgramClass programClass) */ public static boolean shouldMerge(ProgramClass lambdaClass) { - ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); + ProgramClassOptimizationInfo optimizationInfo = + ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass); return (lambdaClass.getProcessingFlags() & (ProcessingFlags.DONT_OPTIMIZE | ProcessingFlags.DONT_SHRINK)) == 0 && lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA) - && optimizationInfo != null // if optimisation info is null, then the lambda was not enqueued to be merged + // if optimisation info is null, then the lambda was not enqueued to be merged + && optimizationInfo != null && optimizationInfo.getLambdaGroup() == null && optimizationInfo.mayBeMerged(); } @@ -259,35 +268,45 @@ public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaC String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); if (!lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA)) { - throw new IllegalArgumentException("Class " + externalClassName + " cannot be merged in a Kotlin lambda group, because it is not a subclass of " + ClassUtil.externalClassName(NAME_KOTLIN_LAMBDA) + "."); + throw new IllegalArgumentException("Class " + externalClassName + " cannot be merged in a Kotlin " + + "lambda group, because it is not a subclass of " + + ClassUtil.externalClassName(NAME_KOTLIN_LAMBDA) + "."); } else if (!lambdaClassHasExactlyOneInitConstructor(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it has more than 1 constructor."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because " + + "it has more than 1 constructor."); } else if (!lambdaClassHasNoBootstrapMethod(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains a bootstrap method that would not be merged into the lambda group."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it " + + "contains a bootstrap method that would not be merged into the lambda group."); } else if (!lambdaClassHasNoAccessibleStaticMethods(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains a static method that could be used outside the class itself."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it " + + "contains a static method that could be used outside the class itself."); } else if (!lambdaClassIsNotDirectlyInvoked(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it is directly invoked with its specific invoke method."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it is " + + "directly invoked with its specific invoke method."); } else if (!nonINSTANCEFieldsAreNotReferencedFromSamePackage(lambdaClass, programClassPool)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because one of its fields, other than the 'INSTANCE' field is referenced by one of its inner classes."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because one of " + + "its fields, other than the 'INSTANCE' field is referenced by one of its " + + "inner classes."); } else if (!lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because its methods are too big to be inlined."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because its " + + "methods are too big to be inlined."); } else if (!mergeLambdaClassesWithUnexpectedMethods && !lambdaClassHasNoUnexpectedMethods(lambdaClass)) { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it contains unexpected methods."); + throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it " + + "contains unexpected methods."); } } @@ -392,11 +411,13 @@ public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute return !methodReferenceFinder.methodReferenceFound(); } - public static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramClass lambdaClass, ClassPool programClassPool) + private static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramClass lambdaClass, + ClassPool programClassPool) { AtomicBoolean nonINSTANCEFieldReferenced = new AtomicBoolean(false); programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, + // TODO: move visitors to separate classes clazz1 -> { if (clazz1 != lambdaClass) { clazz1.constantPoolEntriesAccept(new ConstantVisitor() { diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java index 5748a2a48..7240653e3 100644 --- a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java @@ -28,7 +28,8 @@ public void visitAnyClass(Clazz clazz) { } @Override public void visitProgramClass(ProgramClass programClass) { String name = programClass.getName(); - ProgramClassOptimizationInfo optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(programClass); + ProgramClassOptimizationInfo optimizationInfo = + ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(programClass); Clazz lambdaGroup = optimizationInfo.getLambdaGroup(); if (lambdaGroup != null) { From fb0276d3f2e0ffae67611eb026361a95d6600913 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:10:12 +0200 Subject: [PATCH 158/195] Unused code blocks removed --- .../KotlinLambdaEnclosingMethodUpdater.java | 25 ------------------- .../kotlin/KotlinLambdaGroupBuilder.java | 13 ---------- .../kotlin/KotlinLambdaGroupInitUpdater.java | 7 ++---- .../optimize/kotlin/KotlinLambdaMerger.java | 1 - 4 files changed, 2 insertions(+), 44 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 9f0541c63..89fbd8fe7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -74,31 +74,6 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr visitEnclosingMethodAttribute = true; currentLambdaClass = lambdaClass; currentEnclosingClass = enclosingClass; - /* - enclosingMethodAttribute.referencedMethodAccept(this); - if (enclosingMethodAttribute.referencedMethod == null) - { - logger.warn("No enclosing method was found for {}, so lambda merging will break the code that uses this class.", lambdaClass); - logger.info("Assuming that {} is used in the constructor of {}", lambdaClass, enclosingClass); - Method clinitMethod = enclosingClass.findMethod(ClassConstants.METHOD_NAME_CLINIT, null); - if (clinitMethod == null) - { - logger.warn("No method found for {}, so the usage of {} will not be updated ...", enclosingClass, lambdaClass); - } - else - { - clinitMethod.accept(enclosingClass, this); - } - } - else { - Method inlinedMethod = enclosingClass.findMethod(enclosingMethodAttribute.referencedMethod.getName(enclosingClass) + "$$forInline", null); - if (inlinedMethod != null) - { - logger.info("The enclosing method of lambda class {} has an inlined version, which will also be updated.", lambdaClass); - inlinedMethod.accept(enclosingClass, this); - } - } - */ // Visit all methods of the enclosing class, assuming that those are the only methods that can contain // references to this lambda class. diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 307ead457..35acaaa24 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -33,8 +33,6 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { public static final String FIELD_NAME_ID = "classId"; public static final String FIELD_TYPE_ID = "I"; - public static final String FIELD_NAME_PREFIX_FREE_VARIABLE = "freeVar"; - public static final String FIELD_TYPE_FREE_VARIABLE = "Ljava/lang/Object;"; public static final String METHOD_NAME_SUFFIX_INVOKE = "$invoke"; protected static final int MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH = Integer.parseInt(System.getProperty("maximum.resulting.code.length", "65535")); @@ -95,7 +93,6 @@ private static ClassBuilder getNewLambdaGroupClassBuilder(String lambdaGroupName private void initialiseLambdaGroup() { addIdField(); - addFreeVariableFields(); } private void addIdField() @@ -103,12 +100,6 @@ private void addIdField() classBuilder.addAndReturnField(AccessConstants.PRIVATE, FIELD_NAME_ID, FIELD_TYPE_ID); } - private void addFreeVariableFields() - { - // TODO: add support for non-empty closures - // by adding fields - } - private KotlinLambdaGroupInvokeMethodBuilder getInvokeMethodBuilder(int arity) { KotlinLambdaGroupInvokeMethodBuilder builder = this.invokeMethodBuilders.get(arity); @@ -429,10 +420,6 @@ private void addInvokeMethods() public ProgramClass build() { - // create (int id) - // create invoke(...) method, based on invokeArity - // - //addInitConstructors(); addInvokeMethods(); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); lambdaGroup.setProcessingFlags(lambdaGroup.getProcessingFlags() | ProcessingFlags.INJECTED); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index c7ae878a0..f38d33da0 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -9,9 +9,7 @@ import proguard.classfile.constant.Utf8Constant; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; -import proguard.classfile.util.BranchTargetFinder; -import proguard.classfile.util.ClassUtil; -import proguard.classfile.util.InstructionSequenceMatcher; +import proguard.classfile.util.*; import proguard.classfile.visitor.MemberVisitor; public class KotlinLambdaGroupInitUpdater implements AttributeVisitor, MemberVisitor { @@ -91,8 +89,7 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public static String getNewInitMethodDescriptor(ProgramClass programClass, ProgramMethod programMethod) { String oldInitDescriptor = programMethod.getDescriptor(programClass); - String newInitDescriptor = oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; - return newInitDescriptor; + return oldInitDescriptor.substring(0, oldInitDescriptor.length() - 2) + "II)V"; } private InstructionSequencesReplacer createInstructionSequenceReplacer(BranchTargetFinder branchTargetFinder, diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 38d72bcb5..ad4ef834e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -37,7 +37,6 @@ public class KotlinLambdaMerger implements Pass { public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; public static final String NAME_KOTLIN_FUNCTION = "kotlin/jvm/functions/Function"; - public static final String NAME_KOTLIN_FUNCTION0 = "kotlin/jvm/functions/Function0"; public static final String NAME_KOTLIN_FUNCTIONN = "kotlin/jvm/functions/FunctionN"; public static boolean lambdaMergingDone = false; From 5888379f0cb7178a1d38fc5b74afc050561024eb Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:10:57 +0200 Subject: [PATCH 159/195] Inner classes moved to ProGuard CORE Inner classes of the class KotlinLambdaEnclosingMethodUpdater --- .../KotlinLambdaEnclosingMethodUpdater.java | 161 ------------------ 1 file changed, 161 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 89fbd8fe7..35c125946 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -293,164 +293,3 @@ else if (!currentEnclosingClass.equals(programClass) && !currentLambdaClass.equa } } } - -class DescriptorTypeUpdater implements ConstantVisitor -{ - private final String originalType; - private final String replacingType; - public DescriptorTypeUpdater(String originalType, String replacingType) - { - this.originalType = originalType; - this.replacingType = replacingType; - } - - @Override - public void visitAnyConstant(Clazz clazz, Constant constant) {} - - @Override - public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { - utf8Constant.setString(utf8Constant.getString().replace(originalType, replacingType)); - } -} - -class ClassConstantReferenceUpdater implements ConstantVisitor -{ - private final Clazz originalClass; - private final Clazz replacingClass; - public ClassConstantReferenceUpdater(Clazz originalClass, Clazz replacingClass) - { - this.originalClass = originalClass; - this.replacingClass = replacingClass; - } - - @Override - public void visitAnyConstant(Clazz clazz, Constant constant) {} - - @Override - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { - if (classConstant.referencedClass == originalClass) - { - classConstant.referencedClass = replacingClass; - } - } -} - -class ClassReferenceFinder implements ConstantVisitor -{ - private final Clazz referencedClass; - private boolean classIsReferenced = false; - - public ClassReferenceFinder(Clazz referencedClass) - { - this.referencedClass = referencedClass; - } - - public void visitAnyConstant(Clazz clazz, Constant constant) {} - - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) - { - if (classConstant.referencedClass != null && classConstant.referencedClass.equals(referencedClass)) - { - this.classIsReferenced = true; - } - } - - public boolean classReferenceFound() - { - return this.classIsReferenced; - } -} - -class MethodReferenceFinder implements ConstantVisitor -{ - private final Method referencedMethod; - private final Clazz referencedClass; - private boolean methodIsReferenced = false; - - public MethodReferenceFinder(Method referencedMethod, Clazz referencedClass) - { - this.referencedMethod = referencedMethod; - this.referencedClass = referencedClass; - } - - public void visitAnyConstant(Clazz clazz, Constant constant) {} - - public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) - { - if ((anyMethodrefConstant.referencedMethod != null && anyMethodrefConstant.referencedMethod.equals(referencedMethod))) - /*|| - (anyMethodrefConstant.referencedClass != null && anyMethodrefConstant.referencedClass.equals(this.referencedClass) && - anyMethodrefConstant.getName(clazz).equals(this.referencedMethod.getName(this.referencedClass)) && - anyMethodrefConstant.referencedMethod.getDescriptor(anyMethodrefConstant.referencedClass).equals(this.referencedMethod.getDescriptor(this.referencedClass)))) - - */ - { - this.methodIsReferenced = true; - } - } - - public boolean methodReferenceFound() - { - return this.methodIsReferenced; - } -} - -class EnclosingClassRemover implements AttributeVisitor -{ - private final Clazz classToBeRemoved; - - public EnclosingClassRemover(Clazz clazz) - { - this.classToBeRemoved = clazz; - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - - @Override - public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) - { - if (this.classToBeRemoved.equals(enclosingMethodAttribute.referencedClass)) - { - - } - } - -} - -class InnerClassRemover implements AttributeVisitor, InnerClassesInfoVisitor -{ - private final Clazz classToBeRemoved; - private final Set innerClassesEntriesToBeRemoved = new HashSet<>(); - private static final Logger logger = LogManager.getLogger(InnerClassRemover.class); - - public InnerClassRemover(Clazz clazz) - { - this.classToBeRemoved = clazz; - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) { - } - - @Override - public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) { - innerClassesAttribute.innerClassEntriesAccept(clazz, this); - InnerClassesAttributeEditor editor = new InnerClassesAttributeEditor(innerClassesAttribute); - logger.trace("{} inner class entries are removed from class {}", innerClassesEntriesToBeRemoved.size(), clazz); - for (InnerClassesInfo entry : innerClassesEntriesToBeRemoved) - { - editor.removeInnerClassesInfo(entry); - } - } - - @Override - public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { - String innerClassName = clazz.getClassName(innerClassesInfo.u2innerClassIndex); - if (Objects.equals(innerClassName, this.classToBeRemoved.getName())) - { - logger.trace("Removing inner classes entry of class {} enqueued to be removed from class {}", innerClassName, clazz); - innerClassesEntriesToBeRemoved.add(innerClassesInfo); - } - } -} \ No newline at end of file From 43de4089c089aa44b6c67854cedb1080b4883896 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:12:10 +0200 Subject: [PATCH 160/195] Move method within KotlinLambdaEnclosingMethodUpdater --- .../KotlinLambdaEnclosingMethodUpdater.java | 32 +++++++++++-------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 35c125946..44111d4d0 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -59,6 +59,24 @@ public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, this.extraDataEntryNameMap = extraDataEntryNameMap; } + // Implementations for MemberVisitor + + @Override + public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclosingMethod) + { + // the given class must be the class that defines the lambda + // the given method must be the method where the lambda is defined + + if (visitEnclosingMethod) { + return; + } + visitEnclosingMethod = true; + enclosingMethod.attributesAccept(enclosingClass, this); + visitEnclosingMethod = false; + } + + // Implementations for AttributeVisitor + @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @@ -92,20 +110,6 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); } - @Override - public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclosingMethod) - { - // the given class must be the class that defines the lambda - // the given method must be the method where the lambda is defined - - if (visitEnclosingMethod) { - return; - } - visitEnclosingMethod = true; - enclosingMethod.attributesAccept(enclosingClass, this); - visitEnclosingMethod = false; - } - @Override public void visitCodeAttribute(Clazz enclosingClass, Method enclosingMethod, CodeAttribute codeAttribute) { From 1eaeaca738b4dede8f7f35f56d2f788fd2e8adb1 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:13:32 +0200 Subject: [PATCH 161/195] Refer to existing "invoke" constant --- .../KotlinLambdaEnclosingMethodUpdater.java | 11 +------- .../kotlin/KotlinLambdaGroupBuilder.java | 27 ++++++++----------- .../KotlinLambdaGroupInvokeMethodBuilder.java | 7 +++-- 3 files changed, 15 insertions(+), 30 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 44111d4d0..32c980199 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -5,23 +5,14 @@ import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; -import proguard.classfile.constant.AnyMethodrefConstant; -import proguard.classfile.constant.ClassConstant; -import proguard.classfile.constant.Constant; -import proguard.classfile.constant.Utf8Constant; -import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; import proguard.classfile.kotlin.KotlinConstants; -import proguard.classfile.util.BranchTargetFinder; -import proguard.classfile.util.ClassUtil; +import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.peephole.RetargetedInnerClassAttributeRemover; -import java.util.*; - public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, AttributeVisitor, MemberVisitor { private final ClassPool programClassPool; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 35acaaa24..be92eb019 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -5,23 +5,15 @@ import proguard.Configuration; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.attribute.visitor.ClassConstantToClassVisitor; -import proguard.classfile.attribute.visitor.InnerClassInfoClassConstantVisitor; -import proguard.classfile.attribute.visitor.ModifiedAllInnerClassesInfoVisitor; -import proguard.classfile.editor.ClassBuilder; -import proguard.classfile.editor.InterfaceAdder; -import proguard.classfile.editor.SubclassRemover; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.editor.*; import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; -import proguard.optimize.info.ProgramClassOptimizationInfo; -import proguard.optimize.info.ProgramClassOptimizationInfoSetter; -import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; +import proguard.optimize.info.*; import proguard.optimize.peephole.SameClassMethodInliner; import proguard.util.ProcessingFlags; - import java.util.HashMap; import java.util.Map; import java.util.Objects; @@ -219,14 +211,16 @@ private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) FieldRenamer fieldRenamer = new FieldRenamer(true); // Note: the order of the fields is not necessarily the order in which they are assigned // For now, let's assume the order matches the order in which they are assigned. + // TODO: move visitor to separate class lambdaClass.fieldsAccept( new MemberVisitor() { @Override public void visitProgramField(ProgramClass programClass, ProgramField programField) { // Assumption: the only name clash of fields of different classes is - // for fields with the name "INSTANCE". We don't need these fields anyway, so we don't rename them. - // TODO: handle name clashes correctly - this happens also in the case of inner lambda's accessing their - // enclosing lambda class via + // for fields with the name "INSTANCE". + // We don't need these fields anyway, so we don't rename them. + // TODO: handle name clashes correctly - this happens also in the case of inner lambda's + // accessing their enclosing lambda class via a public field if (!programField.getName(programClass).equals(KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME)) { fieldRenamer.visitProgramField(programClass, programField); @@ -256,13 +250,14 @@ private void inlineMethodsInsideClass(ProgramClass lambdaClass) private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) { // Make the non-bridge invoke methods private, so they can be inlined. + // TODO: move visitor to separate class lambdaClass.accept(new AllMethodVisitor( new MemberVisitor() { @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { if ((programMethod.u2accessFlags & AccessConstants.BRIDGE) == 0) { - if (Objects.equals(programMethod.getName(programClass), KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE)) + if (Objects.equals(programMethod.getName(programClass), KotlinConstants.METHOD_NAME_LAMBDA_INVOKE)) { programMethod.u2accessFlags &= ~AccessConstants.PUBLIC; programMethod.u2accessFlags |= AccessConstants.PRIVATE; @@ -367,7 +362,7 @@ public static ProgramMethod getInvokeMethod(ProgramClass lambdaClass, boolean is ProgramMethod invokeMethod = null; for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) { ProgramMethod method = lambdaClass.methods[methodIndex]; - if (method.getName(lambdaClass).equals(KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE)) + if (method.getName(lambdaClass).equals(KotlinConstants.METHOD_NAME_LAMBDA_INVOKE)) { if ((isBridgeMethod && (method.u2accessFlags & AccessConstants.BRIDGE) != 0) || (!isBridgeMethod && (method.u2accessFlags & AccessConstants.BRIDGE) == 0)) { diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 027ac4e2b..654ba1ed5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -1,10 +1,9 @@ package proguard.optimize.kotlin; import proguard.classfile.*; -import proguard.classfile.editor.ClassBuilder; -import proguard.classfile.editor.CompactCodeAttributeComposer; -import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.editor.*; import proguard.classfile.instruction.Instruction; +import proguard.classfile.kotlin.KotlinConstants; import proguard.classfile.util.ClassUtil; import java.util.ArrayList; @@ -175,7 +174,7 @@ private static String getMethodDescriptorForArity(int arity) public ProgramMethod build() { return classBuilder.addAndReturnMethod(AccessConstants.PUBLIC | AccessConstants.SYNTHETIC, - METHOD_NAME_INVOKE, + KotlinConstants.METHOD_NAME_LAMBDA_INVOKE, getMethodDescriptorForArity(this.arity), this.methodsToBeCalled.size() * 10, this.buildCodeBuilder()); From bf63c239f4b249b0b68fbf4d580ad87faf17d221 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:13:59 +0200 Subject: [PATCH 162/195] Documentation comments --- .../KotlinLambdaEnclosingMethodUpdater.java | 3 ++- .../optimize/kotlin/KotlinLambdaMerger.java | 22 +++++++++++++++++-- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 32c980199..6bcb60ac8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -97,7 +97,8 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr // remove all references to lambda class from the constant pool of its enclosing class enclosingClass.accept(new ConstantPoolShrinker()); - // ensure that the newly created lambda group is part of the resulting output as a dependency of this enclosing class + // ensure that the newly created lambda group is part of the resulting output + // as a dependency of this enclosing class extraDataEntryNameMap.addExtraClassToClass(enclosingClass, this.lambdaGroup); } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index ad4ef834e..b0f6d6cdd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -232,7 +232,15 @@ private Clazz getKotlinLambdaClass(ClassPool programClassPool, ClassPool library return kotlinLambdaClass; } - public static String getArityFromInterface(ProgramClass programClass) + /** + * Returns the arity of the function interface that is implemented by a given class. + * @param programClass the class of which the arity must be determined + * @return the value following the base name "kotlin/jvm/functions/Function". This can be a number going from + * 0 to 22 or the character "N". For example, for a class implementing the interface + * "kotlin/jvm/functions/Function5" the value "5" is returned. + * @throws IllegalArgumentException if the given programClass does not implement a Kotlin function interface. + */ + public static String getArityFromInterface(ProgramClass programClass) throws IllegalArgumentException { for (int interfaceIndex = 0; interfaceIndex < programClass.u2interfacesCount; interfaceIndex++) { @@ -262,7 +270,17 @@ public static boolean shouldMerge(ProgramClass lambdaClass) && optimizationInfo.mayBeMerged(); } - public static void ensureCanMerge(ProgramClass lambdaClass, boolean mergeLambdaClassesWithUnexpectedMethods, ClassPool programClassPool) throws IllegalArgumentException + /** + * Checks whether the given lambda class can be merged and throws an exception if it cannot be merged. + * @param lambdaClass the lambda class for which it should be checked whether it can be merged + * @param mergeLambdaClassesWithUnexpectedMethods a configuration setting that allows merging lambda classes that + * contain unexpected instance methods + * @param programClassPool the program class pool in which the lambda class is defined and used + * @throws IllegalArgumentException if the given lambda class cannot be merged + */ + public static void ensureCanMerge(ProgramClass lambdaClass, + boolean mergeLambdaClassesWithUnexpectedMethods, + ClassPool programClassPool) throws IllegalArgumentException { String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); if (!lambdaClass.extendsOrImplements(NAME_KOTLIN_LAMBDA)) From fb3d726dfd4a969aefe504348a343771e6ca4a3a Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:15:10 +0200 Subject: [PATCH 163/195] Formal merging check methods rewritten --- .../optimize/kotlin/KotlinLambdaMerger.java | 125 +++++++++--------- 1 file changed, 59 insertions(+), 66 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index b0f6d6cdd..03cc8412e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -328,97 +328,90 @@ else if (!mergeLambdaClassesWithUnexpectedMethods && !lambdaClassHasNoUnexpected } private static boolean lambdaClassHasNoBootstrapMethod(ProgramClass lambdaClass) { - final boolean[] bootstrapMethodFound = {false}; - lambdaClass.attributeAccept(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { - @Override - public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { - bootstrapMethodFound[0] = true; - } - }); - return !bootstrapMethodFound[0]; + AttributeCounter attributeCounter = new AttributeCounter(); + lambdaClass.attributeAccept(Attribute.BOOTSTRAP_METHODS, attributeCounter); + return attributeCounter.getCount() == 0; } - public static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lambdaClass) + private static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lambdaClass) { - for (int index = 0; index < lambdaClass.u2methodsCount; index++) - { - ProgramMethod method = lambdaClass.methods[index]; - if (!method.getName(lambdaClass).equals(ClassConstants.METHOD_NAME_CLINIT) - && (method.getAccessFlags() & AccessConstants.STATIC) != 0 - && ((method.getAccessFlags() & AccessConstants.PRIVATE) == 0)) - { - logger.warn("Lambda class {} contains a static method that cannot be merged: {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), method.getName(lambdaClass), method.getDescriptor(lambdaClass))); - return false; - } - } - return true; + MethodCounter nonPrivateStaticMethodCounter = new MethodCounter(); + lambdaClass.methodsAccept(new MemberAccessFilter(AccessConstants.STATIC, AccessConstants.PRIVATE, + new MultiMemberVisitor( + nonPrivateStaticMethodCounter, + new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, + ProgramMethod programMethod) { + logger.warn("Lambda class {} contains a static method that cannot be merged: {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalFullMethodDescription(lambdaClass.getName(), + programMethod.getAccessFlags(), + programMethod.getName(lambdaClass), + programMethod.getDescriptor(lambdaClass))); + } + }))); + return nonPrivateStaticMethodCounter.getCount() == 0; } - public static boolean lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(ProgramClass lambdaClass) + private static boolean lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(ProgramClass lambdaClass) { String methodNameRegularExpression = "!" + ClassConstants.METHOD_NAME_INIT; methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; - // TODO: create a dedicated CodeSizeCounter visitor - final int[] totalCodeSize = {0}; + CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); lambdaClass.methodsAccept(new MemberNameFilter(methodNameRegularExpression, - new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - totalCodeSize[0] += codeAttribute.u4codeLength; - } - }))); - return totalCodeSize[0] <= KotlinLambdaGroupBuilder.MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH; + new AllAttributeVisitor( + codeSizeCounter))); + return codeSizeCounter.getCount() <= KotlinLambdaGroupBuilder.MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH; } - public static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) + private static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) { - for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) - { - Method method = lambdaClass.methods[methodIndex]; - String methodName = method.getName(lambdaClass); - switch (methodName) - { - case ClassConstants.METHOD_NAME_INIT: - case ClassConstants.METHOD_NAME_CLINIT: - case KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE: - break; - default: - logger.warn("Lambda class {} contains an unexpected method: {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalFullMethodDescription(lambdaClass.getName(), method.getAccessFlags(), methodName, method.getDescriptor(lambdaClass))); - return false; - } - } - return true; + String methodNameRegularExpression = "!" + ClassConstants.METHOD_NAME_INIT; + methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; + methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; + methodNameRegularExpression += ",!" + KotlinConstants.METHOD_NAME_LAMBDA_INVOKE; + MethodCounter methodCounter = new MethodCounter(); + lambdaClass.methodsAccept(new MemberNameFilter(methodNameRegularExpression, + new MultiMemberVisitor( + methodCounter, + new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, + ProgramMethod programMethod) { + logger.warn("Lambda class {} contains an unexpected method: {}", + ClassUtil.externalClassName(lambdaClass.getName()), + ClassUtil.externalFullMethodDescription(lambdaClass.getName(), + programMethod.getAccessFlags(), + programMethod.getName(programClass), + programMethod.getDescriptor(lambdaClass))); + } + }))); + return methodCounter.getCount() == 0; } - public static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambdaClass) + private static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambdaClass) { - int count = 0; - for (int methodIndex = 0; methodIndex < lambdaClass.u2methodsCount; methodIndex++) - { - Method method = lambdaClass.methods[methodIndex]; - if (method.getName(lambdaClass).equals(ClassConstants.METHOD_NAME_INIT)) - { - count++; - } - } - if (count != 1) + MethodCounter initMethodCounter = new MethodCounter(); + lambdaClass.methodsAccept(new MemberNameFilter(ClassConstants.METHOD_NAME_INIT, + initMethodCounter)); + if (initMethodCounter.getCount() != 1) { - logger.warn("Lambda class {} has {} constructors.", ClassUtil.externalClassName(lambdaClass.getName()), count); + logger.warn("Lambda class {} has {} constructors.", + ClassUtil.externalClassName(lambdaClass.getName()), initMethodCounter.getCount()); } - return count == 1; + return initMethodCounter.getCount() == 1; } - public static boolean lambdaClassIsNotDirectlyInvoked(ProgramClass lambdaClass) + private static boolean lambdaClassIsNotDirectlyInvoked(ProgramClass lambdaClass) { Method invokeMethod = KotlinLambdaGroupBuilder.getInvokeMethod(lambdaClass, false); if (invokeMethod == null) { return true; } - MethodReferenceFinder methodReferenceFinder = new MethodReferenceFinder(invokeMethod, lambdaClass); + MethodReferenceFinder methodReferenceFinder = new MethodReferenceFinder(invokeMethod); + // TODO: move visitors to separate classes lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, new AttributeVisitor() { @Override public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute enclosingMethodAttribute) { From a216de3e2a837533b47a6ebcb9379fbe2d6c0dd7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:15:32 +0200 Subject: [PATCH 164/195] Imports + reference to existing "invoke" constant --- .../optimize/kotlin/KotlinLambdaMerger.java | 29 ++++++------------- 1 file changed, 9 insertions(+), 20 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 03cc8412e..d9515d85c 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -2,34 +2,23 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.AppView; -import proguard.Configuration; +import proguard.*; import proguard.classfile.*; -import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.BootstrapMethodsAttribute; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.EnclosingMethodAttribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.Constant; +import proguard.classfile.attribute.*; +import proguard.classfile.attribute.visitor.*; +import proguard.classfile.constant.*; import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.LineNumberTableAttributeTrimmer; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.kotlin.KotlinConstants; -import proguard.classfile.util.ClassInitializer; -import proguard.classfile.util.ClassUtil; +import proguard.classfile.util.*; import proguard.classfile.visitor.*; -import proguard.optimize.info.ProgramClassOptimizationInfo; -import proguard.optimize.info.ProgramClassOptimizationInfoSetter; -import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; -import proguard.optimize.peephole.LineNumberLinearizer; -import proguard.optimize.peephole.SameClassMethodInliner; +import proguard.optimize.info.*; +import proguard.optimize.peephole.*; import proguard.pass.Pass; import proguard.shrink.*; -import proguard.util.PrintWriterUtil; -import proguard.util.ProcessingFlags; - +import proguard.util.*; import java.io.PrintWriter; import java.util.concurrent.atomic.AtomicBoolean; @@ -200,7 +189,7 @@ private void shrinkLambdaGroups(ClassPool programClassPool, lambdaGroupClassPool.classesAccept(new AllMemberVisitor( new MultiMemberVisitor( - new MemberNameFilter(KotlinLambdaGroupInvokeMethodBuilder.METHOD_NAME_INVOKE, + new MemberNameFilter(KotlinConstants.METHOD_NAME_LAMBDA_INVOKE, classUsageMarker), new MemberNameFilter(ClassConstants.METHOD_NAME_INIT, classUsageMarker), From 20e0ede8e18a3582de1fb33d935f7df90a818409 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 12:15:39 +0200 Subject: [PATCH 165/195] Code alignment --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index d9515d85c..098c0416c 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -37,6 +37,7 @@ public KotlinLambdaMerger(Configuration configuration) this.configuration = configuration; } + // Implementations for Pass @Override public void execute(AppView appView) throws Exception { @@ -51,7 +52,8 @@ public void execute(AppView appView) throws Exception // get the Lambda class and the Function0 interface Clazz kotlinLambdaClass = getKotlinLambdaClass(appView.programClassPool, appView.libraryClassPool); if (kotlinLambdaClass == null) { - logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", NAME_KOTLIN_LAMBDA); + logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", + NAME_KOTLIN_LAMBDA); } else { @@ -239,7 +241,8 @@ public static String getArityFromInterface(ProgramClass programClass) throws Ill return interfaceName.substring(NAME_KOTLIN_FUNCTION.length()); } } - throw new IllegalArgumentException("Class " + ClassUtil.externalClassName(programClass.getName()) + " does not implement a Kotlin function interface."); + throw new IllegalArgumentException("Class " + ClassUtil.externalClassName(programClass.getName()) + + " does not implement a Kotlin function interface."); } /** From 4aae5caa56c56733d419a5d6bde4afe6bfdb55d7 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 13:29:46 +0200 Subject: [PATCH 166/195] Move anonymous class to separate file in ProGuard CORE --- .../optimize/kotlin/KotlinLambdaMerger.java | 53 ++++++++----------- 1 file changed, 21 insertions(+), 32 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 098c0416c..4123e6a8b 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -6,9 +6,8 @@ import proguard.classfile.*; import proguard.classfile.attribute.*; import proguard.classfile.attribute.visitor.*; -import proguard.classfile.constant.*; import proguard.classfile.constant.FieldrefConstant; -import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.constant.visitor.*; import proguard.classfile.editor.LineNumberTableAttributeTrimmer; import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.kotlin.KotlinConstants; @@ -20,7 +19,6 @@ import proguard.shrink.*; import proguard.util.*; import java.io.PrintWriter; -import java.util.concurrent.atomic.AtomicBoolean; public class KotlinLambdaMerger implements Pass { @@ -416,34 +414,25 @@ public void visitEnclosingMethodAttribute(Clazz clazz, EnclosingMethodAttribute private static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramClass lambdaClass, ClassPool programClassPool) { - AtomicBoolean nonINSTANCEFieldReferenced = new AtomicBoolean(false); - programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", - new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, - // TODO: move visitors to separate classes - clazz1 -> { - if (clazz1 != lambdaClass) { - clazz1.constantPoolEntriesAccept(new ConstantVisitor() { - @Override - public void visitAnyConstant(Clazz clazz, Constant constant) { - } - - @Override - public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { - if (fieldrefConstant.referencedClass == lambdaClass - && !KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME.equals(fieldrefConstant.getName(clazz))) - { - nonINSTANCEFieldReferenced.set(true); - logger.warn("{} references non-INSTANCE field {} of lambda class {}.", - ClassUtil.externalClassName(clazz.getName()), - ClassUtil.externalFullFieldDescription(fieldrefConstant.referencedField.getAccessFlags(), - fieldrefConstant.getName(clazz), - fieldrefConstant.getType(clazz)), - ClassUtil.externalClassName(lambdaClass.getName())); - } - } - }); - } - }))); - return !nonINSTANCEFieldReferenced.get(); + String regularExpression = ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*"; + regularExpression += ",!" + lambdaClass.getName(); + String fieldRegularExpression = "!" + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME; + FieldReferenceFinder fieldReferenceFinder = new FieldReferenceFinder(lambdaClass, + fieldRegularExpression, + new ConstantVisitor() { + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + logger.warn("{} references non-INSTANCE field {} of lambda class {}.", + ClassUtil.externalClassName(clazz.getName()), + ClassUtil.externalFullFieldDescription(fieldrefConstant.referencedField.getAccessFlags(), + fieldrefConstant.getName(clazz), + fieldrefConstant.getType(clazz)), + ClassUtil.externalClassName(lambdaClass.getName())); + } + }); + programClassPool.classesAccept(new ClassNameFilter(regularExpression, + new AllConstantVisitor( + fieldReferenceFinder))); + return !fieldReferenceFinder.isFieldReferenceFound(); } } From 7f461805f42736c48c171e13e04771507e7b60cf Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 13:45:46 +0200 Subject: [PATCH 167/195] Use FieldRenamer without anonymous class --- .../kotlin/KotlinLambdaGroupBuilder.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index be92eb019..dfe0f6277 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -209,24 +209,16 @@ private void mergeLambdaClass(ProgramClass lambdaClass) private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) { FieldRenamer fieldRenamer = new FieldRenamer(true); + // Assumption: the only name clash of fields of different classes is + // for fields with the name "INSTANCE". + // We don't need these fields anyway, so we don't rename them. + // TODO: handle name clashes correctly - this happens also in the case of inner lambda's + // accessing their enclosing lambda class via a public field + String fieldNameRegularExpression = "!" + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME; // Note: the order of the fields is not necessarily the order in which they are assigned // For now, let's assume the order matches the order in which they are assigned. - // TODO: move visitor to separate class - lambdaClass.fieldsAccept( - new MemberVisitor() { - @Override - public void visitProgramField(ProgramClass programClass, ProgramField programField) { - // Assumption: the only name clash of fields of different classes is - // for fields with the name "INSTANCE". - // We don't need these fields anyway, so we don't rename them. - // TODO: handle name clashes correctly - this happens also in the case of inner lambda's - // accessing their enclosing lambda class via a public field - if (!programField.getName(programClass).equals(KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME)) - { - fieldRenamer.visitProgramField(programClass, programField); - } - } - }); + lambdaClass.fieldsAccept(new MemberNameFilter(fieldNameRegularExpression, + fieldRenamer)); } private void inlineMethodsInsideClass(ProgramClass lambdaClass) @@ -236,7 +228,7 @@ private void inlineMethodsInsideClass(ProgramClass lambdaClass) lambdaClass.accept(new AllMemberVisitor( new ProgramMemberOptimizationInfoSetter())); - // Allow methods to become + // Allow methods to be inlined lambdaClass.accept(new AllMethodVisitor( new AllAttributeVisitor( new SameClassMethodInliner(configuration.microEdition, From 99b18e530e43611389c024152ec1a45ca52fd80d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 13:54:05 +0200 Subject: [PATCH 168/195] Correct usage of visitor for (un)setting access flags --- .../kotlin/KotlinLambdaGroupBuilder.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index dfe0f6277..bba5f3018 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -242,22 +242,11 @@ private void inlineMethodsInsideClass(ProgramClass lambdaClass) private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) { // Make the non-bridge invoke methods private, so they can be inlined. - // TODO: move visitor to separate class - lambdaClass.accept(new AllMethodVisitor( - new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - if ((programMethod.u2accessFlags & AccessConstants.BRIDGE) == 0) - { - if (Objects.equals(programMethod.getName(programClass), KotlinConstants.METHOD_NAME_LAMBDA_INVOKE)) - { - programMethod.u2accessFlags &= ~AccessConstants.PUBLIC; - programMethod.u2accessFlags |= AccessConstants.PRIVATE; - } - } - } - } - )); + lambdaClass.methodsAccept(new MemberNameFilter(KotlinConstants.METHOD_NAME_LAMBDA_INVOKE, + new MemberAccessFilter(0, AccessConstants.BRIDGE, + new MultiMemberVisitor( + new MemberAccessFlagCleaner(AccessConstants.PUBLIC), + new MemberAccessFlagSetter(AccessConstants.PRIVATE))))); inlineMethodsInsideClass(lambdaClass); } From 705617a24695d95058d345466225d01f6fa05f67 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 13:54:15 +0200 Subject: [PATCH 169/195] Code alignment --- .../optimize/kotlin/KotlinLambdaGroupBuilder.java | 8 ++++---- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index bba5f3018..08a43f4ec 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -380,10 +380,10 @@ private void updateLambdaInstantiationSite(ProgramClass lambdaClass, lambdaClass.attributeAccept(Attribute.ENCLOSING_METHOD, enclosingMethodUpdater); // Also update any references that would occur in other classes of the same package. - this.programClassPool.classesAccept(new ClassNameFilter(ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*", - new ClassNameFilter(lambdaClass.getName(), - (ClassVisitor)null, - enclosingMethodUpdater))); + String regularExpression = ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*"; + regularExpression += ",!" + lambdaClass.getName(); + this.programClassPool.classesAccept(new ClassNameFilter(regularExpression, + enclosingMethodUpdater)); } private void addInvokeMethods() diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 4123e6a8b..2148387f6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -347,7 +347,7 @@ public void visitProgramMethod(ProgramClass programClass, private static boolean lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(ProgramClass lambdaClass) { String methodNameRegularExpression = "!" + ClassConstants.METHOD_NAME_INIT; - methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; + methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; CodeSizeCounter codeSizeCounter = new CodeSizeCounter(); lambdaClass.methodsAccept(new MemberNameFilter(methodNameRegularExpression, new AllAttributeVisitor( From 8f9fc01c29e443fc1a8428488c129165128b6185 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 13:56:17 +0200 Subject: [PATCH 170/195] Not-working Kotlin Lambda Merger LambdaGroupBuilder tests disabled --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt | 4 ++++ .../proguard/optimize/kotlin/KotlinLambdaMergerTest.kt | 5 ++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt index a576b6962..76dbd5884 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -55,6 +55,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ lambdaClass.accept(ProgramClassOptimizationInfoSetter()) val optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass) val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(programClassPool, libraryClassPool) + /* lambdaClass.accept( AllMemberVisitor( MemberNameFilter( @@ -72,6 +73,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ ) ) ) + */ val originalInstructionsMatcher = InstructionSequenceMatcher( originalInvokeMethodCodeBuilder.constants(), originalInvokeMethodCodeBuilder.instructions() @@ -80,6 +82,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ "When the builder adds a lambda class to the lambda group under construction" - { builder.visitProgramClass(lambdaClass) val lambdaGroup = builder.build() + /* "Then the lambda class implementation has been added to the lambda group" { lambdaGroup.accept( AllMemberVisitor( @@ -92,6 +95,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ ) originalInstructionsMatchDetector.matchIsFound shouldBe true } + */ "Then the optimization info lambda group of the lambda class refers to the lambda group" { optimizationInfo.lambdaGroup shouldBe lambdaGroup diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 5a79959dd..47c0b1540 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -123,6 +123,7 @@ class KotlinLambdaMergerTest : FreeSpec({ "When the merger is applied to the class pools" - { val oldProgramClassPool = ClassPool(programClassPool) val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(oldProgramClassPool, libraryClassPool) + /* oldProgramClassPool.getClass("app/package2/Test2Kt\$main\$lambda1\$1").accept( AllMemberVisitor( MemberNameFilter( @@ -143,6 +144,7 @@ class KotlinLambdaMergerTest : FreeSpec({ ) ) ) + */ val loaderBefore = ClassPoolClassLoader(oldProgramClassPool) val testClassBefore = loaderBefore.loadClass("app.package2.Test2Kt") val stdOutput = System.out @@ -210,6 +212,7 @@ class KotlinLambdaMergerTest : FreeSpec({ newProgramClassPool.getClass("app/package2/LambdaGroup") shouldNotBe null } + /* "Then the instructions of the original lambda's are found in the lambda group" { val lambdaGroup = newProgramClassPool.getClass("app/package1/LambdaGroup") val originalInstructionsMatcher = InstructionSequenceMatcher( @@ -234,7 +237,7 @@ class KotlinLambdaMergerTest : FreeSpec({ ) originalInstructionsMatchDetector.matchIsFound shouldBe true } - + */ /* "Then program output has not changed after optimisation" { From d7c510bc5fca85d7ca063526b868ee20851cc1d1 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 14:19:09 +0200 Subject: [PATCH 171/195] Correct refactoring bug: check static methods Classes with were incorrectly identified as unmergeable --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 2148387f6..9506449c7 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -326,7 +326,9 @@ private static boolean lambdaClassHasNoBootstrapMethod(ProgramClass lambdaClass) private static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lambdaClass) { MethodCounter nonPrivateStaticMethodCounter = new MethodCounter(); + String regularExpression = "!" + ClassConstants.METHOD_NAME_CLINIT; lambdaClass.methodsAccept(new MemberAccessFilter(AccessConstants.STATIC, AccessConstants.PRIVATE, + new MemberNameFilter(regularExpression, new MultiMemberVisitor( nonPrivateStaticMethodCounter, new MemberVisitor() { @@ -340,7 +342,7 @@ public void visitProgramMethod(ProgramClass programClass, programMethod.getName(lambdaClass), programMethod.getDescriptor(lambdaClass))); } - }))); + })))); return nonPrivateStaticMethodCounter.getCount() == 0; } From ae4eb58fe71b755e4900eaf5c4459bf1e6b453ba Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 15:06:46 +0200 Subject: [PATCH 172/195] Fix refactoring bug: don't consider fields of same class as referenced --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 9506449c7..c72e58f9b 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -417,7 +417,6 @@ private static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramC ClassPool programClassPool) { String regularExpression = ClassUtil.internalPackagePrefix(lambdaClass.getName()) + "*"; - regularExpression += ",!" + lambdaClass.getName(); String fieldRegularExpression = "!" + KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME; FieldReferenceFinder fieldReferenceFinder = new FieldReferenceFinder(lambdaClass, fieldRegularExpression, @@ -433,8 +432,9 @@ public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant } }); programClassPool.classesAccept(new ClassNameFilter(regularExpression, + new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, new AllConstantVisitor( - fieldReferenceFinder))); + fieldReferenceFinder)))); return !fieldReferenceFinder.isFieldReferenceFound(); } } From c61c41cb39148091031cdfe5f2319b3c0ce40f67 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 17:17:28 +0200 Subject: [PATCH 173/195] Preverify lambda groups, such that the correct attributes are added --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 08a43f4ec..ecf28aad2 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -13,6 +13,7 @@ import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.info.*; import proguard.optimize.peephole.SameClassMethodInliner; +import proguard.preverify.CodePreverifier; import proguard.util.ProcessingFlags; import java.util.HashMap; import java.util.Map; @@ -399,6 +400,9 @@ public ProgramClass build() addInvokeMethods(); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); lambdaGroup.setProcessingFlags(lambdaGroup.getProcessingFlags() | ProcessingFlags.INJECTED); + lambdaGroup.accept(new AllMemberVisitor( + new AllAttributeVisitor( + new CodePreverifier(configuration.microEdition)))); return lambdaGroup; } } From 4a58dd46357eae57e800221df1cdd5ecbc0a3819 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 17:21:07 +0200 Subject: [PATCH 174/195] Output verification test works --- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 12 ++---------- .../test/kotlin/testutils/ClassPoolClassLoader.kt | 1 - 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index 47c0b1540..acc639b82 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -237,14 +237,12 @@ class KotlinLambdaMergerTest : FreeSpec({ ) originalInstructionsMatchDetector.matchIsFound shouldBe true } - */ - /* - "Then program output has not changed after optimisation" { + "Then the program output has not changed after optimisation" { val loaderAfter = ClassPoolClassLoader(newProgramClassPool) val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") val capturedOutputAfter = ByteArrayOutputStream() - val capturedOutputStreamAfter = PrintStream(capturedOutputBefore) + val capturedOutputStreamAfter = PrintStream(capturedOutputAfter) System.setOut(capturedOutputStreamAfter) try { @@ -264,15 +262,9 @@ class KotlinLambdaMergerTest : FreeSpec({ ) throw e } - System.setOut(stdOutput) - capturedOutputAfter.toString() shouldBe capturedOutputBefore.toString() - println(capturedOutputBefore) - println() - println(capturedOutputAfter) } - */ } } }) diff --git a/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt index 7ba9f0af6..f678399c2 100644 --- a/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt +++ b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt @@ -16,7 +16,6 @@ class ClassPoolClassLoader(private val classPool: ClassPool) : ClassLoader() { val clazz = classPool.getClass(internalClassName(name)) val byteArrayOutputStream = ByteArrayOutputStream() try { - println(name) clazz.accept(ProgramClassFilter(ProgramClassWriter(DataOutputStream(byteArrayOutputStream)))) } catch (e: Exception) { println("Exception for class $name: $e") From 732a88978f4c31d41dbc8839253bdd6dc405fe73 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 17:23:54 +0200 Subject: [PATCH 175/195] Remove not working tests --- .../kotlin/KotlinLambdaGroupBuilderTest.kt | 40 +---------- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 70 ++----------------- 2 files changed, 6 insertions(+), 104 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt index 76dbd5884..498798b1b 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -54,48 +54,10 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ val lambdaClass = programClassPool.getClass("TestKt\$main\$lambda1\$1") as ProgramClass lambdaClass.accept(ProgramClassOptimizationInfoSetter()) val optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass) - val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(programClassPool, libraryClassPool) - /* - lambdaClass.accept( - AllMemberVisitor( - MemberNameFilter( - "invoke", - MemberAccessFilter( - 0, AccessConstants.SYNTHETIC, - AllAttributeVisitor( - AllInstructionVisitor( - InstructionSequenceCollector( - originalInvokeMethodCodeBuilder - ) - ) - ) - ) - ) - ) - ) - */ - val originalInstructionsMatcher = InstructionSequenceMatcher( - originalInvokeMethodCodeBuilder.constants(), - originalInvokeMethodCodeBuilder.instructions() - ) - val originalInstructionsMatchDetector = MatchDetector(originalInstructionsMatcher) + "When the builder adds a lambda class to the lambda group under construction" - { builder.visitProgramClass(lambdaClass) val lambdaGroup = builder.build() - /* - "Then the lambda class implementation has been added to the lambda group" { - lambdaGroup.accept( - AllMemberVisitor( - AllAttributeVisitor( - AllInstructionVisitor( - originalInstructionsMatchDetector - ) - ) - ) - ) - originalInstructionsMatchDetector.matchIsFound shouldBe true - } - */ "Then the optimization info lambda group of the lambda class refers to the lambda group" { optimizationInfo.lambdaGroup shouldBe lambdaGroup diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index acc639b82..f32ff29c7 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -26,20 +26,14 @@ import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.ints.shouldBeGreaterThan import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe -import io.mockk.Runs -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.slot +import io.mockk.* import proguard.AppView import proguard.Configuration -import proguard.classfile.AccessConstants -import proguard.classfile.ClassPool -import proguard.classfile.Clazz -import proguard.classfile.Method +import proguard.classfile.* import proguard.classfile.attribute.CodeAttribute import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.constant.ClassConstant +import proguard.classfile.constant.Constant import proguard.classfile.constant.visitor.ConstantVisitor import proguard.classfile.editor.InstructionSequenceBuilder import proguard.classfile.instruction.Instruction @@ -51,13 +45,8 @@ import proguard.classfile.visitor.AllMemberVisitor import proguard.classfile.visitor.MemberAccessFilter import proguard.classfile.visitor.MemberNameFilter import proguard.io.ExtraDataEntryNameMap -import testutils.ClassPoolBuilder -import testutils.ClassPoolClassLoader -import testutils.InstructionSequenceCollector -import testutils.KotlinSource -import testutils.MatchDetector -import java.io.ByteArrayOutputStream -import java.io.PrintStream +import testutils.* +import java.io.* class KotlinLambdaMergerTest : FreeSpec({ @@ -122,29 +111,6 @@ class KotlinLambdaMergerTest : FreeSpec({ "When the merger is applied to the class pools" - { val oldProgramClassPool = ClassPool(programClassPool) - val originalInvokeMethodCodeBuilder = InstructionSequenceBuilder(oldProgramClassPool, libraryClassPool) - /* - oldProgramClassPool.getClass("app/package2/Test2Kt\$main\$lambda1\$1").accept( - AllMemberVisitor( - MemberNameFilter( - "invoke", - MemberAccessFilter( - 0, AccessConstants.SYNTHETIC, - AllAttributeVisitor( - AllInstructionVisitor( - MultiInstructionVisitor( - InstructionPrinter(), - InstructionSequenceCollector( - originalInvokeMethodCodeBuilder - ) - ) - ) - ) - ) - ) - ) - ) - */ val loaderBefore = ClassPoolClassLoader(oldProgramClassPool) val testClassBefore = loaderBefore.loadClass("app.package2.Test2Kt") val stdOutput = System.out @@ -212,32 +178,6 @@ class KotlinLambdaMergerTest : FreeSpec({ newProgramClassPool.getClass("app/package2/LambdaGroup") shouldNotBe null } - /* - "Then the instructions of the original lambda's are found in the lambda group" { - val lambdaGroup = newProgramClassPool.getClass("app/package1/LambdaGroup") - val originalInstructionsMatcher = InstructionSequenceMatcher( - originalInvokeMethodCodeBuilder.constants(), - originalInvokeMethodCodeBuilder.instructions() - ) - val originalInstructionsMatchDetector = MatchDetector(originalInstructionsMatcher) - lambdaGroup.accept( - AllMemberVisitor( - MemberNameFilter( - "invoke", - MemberAccessFilter( - 0, AccessConstants.SYNTHETIC, - AllAttributeVisitor( - AllInstructionVisitor( - originalInstructionsMatchDetector - ) - ) - ) - ) - ) - ) - originalInstructionsMatchDetector.matchIsFound shouldBe true - } - "Then the program output has not changed after optimisation" { val loaderAfter = ClassPoolClassLoader(newProgramClassPool) val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") From 1b997bb302feba7966368f442d2323da8f9f855b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 8 Jun 2022 18:08:50 +0200 Subject: [PATCH 176/195] Output verification test for lambda group builder --- .../kotlin/KotlinLambdaGroupBuilderTest.kt | 52 ++++++++----------- .../optimize/kotlin/KotlinLambdaMergerTest.kt | 49 ++++------------- .../kotlin/testutils/ClassPoolClassLoader.kt | 23 ++++++++ 3 files changed, 55 insertions(+), 69 deletions(-) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt index 498798b1b..f618f98cd 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -5,10 +5,12 @@ import io.kotest.matchers.ints.shouldBeGreaterThanOrEqual import io.kotest.matchers.shouldBe import proguard.Configuration import proguard.classfile.AccessConstants +import proguard.classfile.ClassPool import proguard.classfile.ProgramClass import proguard.classfile.attribute.visitor.AllAttributeVisitor import proguard.classfile.editor.InstructionSequenceBuilder import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.util.ClassUtil import proguard.classfile.util.InstructionSequenceMatcher import proguard.classfile.visitor.AllMemberVisitor import proguard.classfile.visitor.MemberAccessFilter @@ -16,10 +18,7 @@ import proguard.classfile.visitor.MemberNameFilter import proguard.io.ExtraDataEntryNameMap import proguard.optimize.info.ProgramClassOptimizationInfo import proguard.optimize.info.ProgramClassOptimizationInfoSetter -import testutils.ClassPoolBuilder -import testutils.InstructionSequenceCollector -import testutils.KotlinSource -import testutils.MatchDetector +import testutils.* class KotlinLambdaGroupBuilderTest : FreeSpec({ val (programClassPool, libraryClassPool) = ClassPoolBuilder.fromSource( @@ -51,9 +50,18 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ entryMapper, notMergedLambdaVisitor ) - val lambdaClass = programClassPool.getClass("TestKt\$main\$lambda1\$1") as ProgramClass + val lambdaClassName = "TestKt\$main\$lambda1\$1" + val arity = 0 + val lambdaClass = programClassPool.getClass(lambdaClassName) as ProgramClass lambdaClass.accept(ProgramClassOptimizationInfoSetter()) val optimizationInfo = ProgramClassOptimizationInfo.getProgramClassOptimizationInfo(lambdaClass) + val capturedExecutionOutputBefore = captureExecutionOutput(programClassPool, + lambdaClassName) { + testClassBefore -> + val instance = testClassBefore.getDeclaredConstructor().newInstance() + testClassBefore.declaredMethods.single { it.name == "invoke" && it.isBridge } + .invoke(instance, null) + } "When the builder adds a lambda class to the lambda group under construction" - { builder.visitProgramClass(lambdaClass) @@ -71,32 +79,18 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ optimizationInfo.lambdaGroupClassId shouldBeGreaterThanOrEqual 0 } - /* - "Then the lambda group has the same output on invocation as the original lambda class" { - /*val stdOutput = System.out - val capturedOutputBefore = ByteArrayOutputStream() - val capturedOutputStreamBefore = PrintStream(capturedOutputBefore) - //System.setOut(capturedOutputStreamBefore) - - testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic}.invoke(null, arrayOf()) - - val loaderAfter = ClassPoolClassLoader(newProgramClassPool) - val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") - val capturedOutputAfter = ByteArrayOutputStream() - val capturedOutputStreamAfter = PrintStream(capturedOutputBefore) - //System.setOut(capturedOutputStreamAfter) - testClassAfter.declaredMethods.single { it.name == "main" && it.isSynthetic}.invoke(null, arrayOf()) - - System.setOut(stdOutput) - - capturedOutputAfter.toByteArray().toString() shouldBe capturedOutputBefore.toByteArray().toString() - println(capturedOutputAfter.toByteArray()) - println() - println(capturedOutputBefore.toByteArray()) - */ + "Then the lambda group has the same output on invocation as the original lambda class" { + val capturedExecutionOutputAfter = captureExecutionOutput(ClassPool(lambdaGroup), + lambdaGroupName) { + testClassBefore -> + val instance = testClassBefore.getDeclaredConstructor().newInstance( + optimizationInfo.lambdaGroupClassId, arity) + testClassBefore.declaredMethods.single { it.name == "invoke" && it.isBridge } + .invoke(instance) + } + capturedExecutionOutputAfter shouldBe capturedExecutionOutputBefore } - */ } } }) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt index f32ff29c7..ead49d90c 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaMergerTest.kt @@ -111,23 +111,11 @@ class KotlinLambdaMergerTest : FreeSpec({ "When the merger is applied to the class pools" - { val oldProgramClassPool = ClassPool(programClassPool) - val loaderBefore = ClassPoolClassLoader(oldProgramClassPool) - val testClassBefore = loaderBefore.loadClass("app.package2.Test2Kt") - val stdOutput = System.out - val capturedOutputBefore = ByteArrayOutputStream() - val capturedOutputStreamBefore = PrintStream(capturedOutputBefore) - System.setOut(capturedOutputStreamBefore) - try { - testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic } - .invoke(null, arrayOf()) - } catch (e: Exception) { - System.setOut(stdOutput) - println() - println("Exception: $e") - println("Output of method call:") - println(capturedOutputBefore) + val capturedExecutionOutputBefore = captureExecutionOutput(oldProgramClassPool, + "app.package2.Test2Kt") { + testClassBefore -> testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic } + .invoke(null, arrayOf()) } - System.setOut(stdOutput) val appView = AppView(programClassPool, libraryClassPool, null, nameMapper) merger.execute(appView) @@ -179,31 +167,12 @@ class KotlinLambdaMergerTest : FreeSpec({ } "Then the program output has not changed after optimisation" { - val loaderAfter = ClassPoolClassLoader(newProgramClassPool) - val testClassAfter = loaderAfter.loadClass("app.package2.Test2Kt") - val capturedOutputAfter = ByteArrayOutputStream() - val capturedOutputStreamAfter = PrintStream(capturedOutputAfter) - System.setOut(capturedOutputStreamAfter) - - try { - testClassAfter.declaredMethods.single { it.name == "main" && it.isSynthetic } - .invoke(null, arrayOf()) - } catch (e: Exception) { - System.setOut(stdOutput) - println("Exception while executing test class after lambda merging.") - println(e) - e.printStackTrace() - val lambdaGroup = newProgramClassPool.getClass("app/package2/LambdaGroup") - lambdaGroup.accept(AllMemberVisitor( - MemberNameFilter("invoke", - AllAttributeVisitor( - AllInstructionVisitor( - InstructionPrinter())))) - ) - throw e + val capturedExecutionOutputAfter = captureExecutionOutput(newProgramClassPool, + "app.package2.Test2Kt") { + testClassBefore -> testClassBefore.declaredMethods.single { it.name == "main" && it.isSynthetic } + .invoke(null, arrayOf()) } - System.setOut(stdOutput) - capturedOutputAfter.toString() shouldBe capturedOutputBefore.toString() + capturedExecutionOutputAfter shouldBe capturedExecutionOutputBefore } } } diff --git a/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt index f678399c2..fc5a37744 100644 --- a/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt +++ b/base/src/test/kotlin/testutils/ClassPoolClassLoader.kt @@ -1,11 +1,13 @@ package testutils import proguard.classfile.ClassPool +import proguard.classfile.ProgramClass import proguard.classfile.io.ProgramClassWriter import proguard.classfile.util.ClassUtil.internalClassName import proguard.classfile.visitor.ProgramClassFilter import java.io.ByteArrayOutputStream import java.io.DataOutputStream +import java.io.PrintStream /** * A ClassLoader that can load classes from a ProGuardCORE ClassPool. @@ -26,3 +28,24 @@ class ClassPoolClassLoader(private val classPool: ClassPool) : ClassLoader() { return super.findClass(name) } } + + +fun captureExecutionOutput(programClassPool: ClassPool, programClassName: String, operation: (Class<*>) -> Unit): String { + val loader = ClassPoolClassLoader(programClassPool) + val loadedClass = loader.loadClass(programClassName) + val stdOutput = System.out + val capturedOutput = ByteArrayOutputStream() + val capturedOutputStream = PrintStream(capturedOutput) + System.setOut(capturedOutputStream) + try { + operation(loadedClass) + } catch (e: Exception) { + System.setOut(stdOutput) + println() + println("Exception: $e") + println("Output of method call:") + println(capturedOutput) + } + System.setOut(stdOutput) + return capturedOutput.toString() +} \ No newline at end of file From 7c28e605d2201adeea6d6831788aa9022c4867b8 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 16:29:09 +0200 Subject: [PATCH 177/195] Move visitors from PGC to ProGuard Classes involved are: InnerClasssInfoClassConstantVisitor, ClassConstantReferenceUpdater, FieldReferenceFinder, FieldRenamer, MethodCopier, MethodReferenceFinder and PackageGrouper --- .../InnerClassInfoClassConstantVisitor.java | 30 ++++ .../ClassConstantReferenceUpdater.java | 28 ++++ .../visitor/FieldReferenceFinder.java | 46 +++++ .../classfile/visitor/FieldRenamer.java | 80 +++++++++ .../classfile/visitor/MethodCopier.java | 158 ++++++++++++++++++ .../visitor/MethodReferenceFinder.java | 34 ++++ .../classfile/visitor/PackageGrouper.java | 66 ++++++++ .../classfile/visitor/PackageGrouperTest.kt | 98 +++++++++++ .../testutils/RecursiveConstantCollector.kt | 71 ++++++++ 9 files changed, 611 insertions(+) create mode 100644 base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java create mode 100644 base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java create mode 100644 base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java create mode 100644 base/src/main/java/proguard/classfile/visitor/FieldRenamer.java create mode 100644 base/src/main/java/proguard/classfile/visitor/MethodCopier.java create mode 100644 base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java create mode 100644 base/src/main/java/proguard/classfile/visitor/PackageGrouper.java create mode 100644 base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt create mode 100644 base/src/test/kotlin/testutils/RecursiveConstantCollector.kt diff --git a/base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java b/base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java new file mode 100644 index 000000000..b3020b62d --- /dev/null +++ b/base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java @@ -0,0 +1,30 @@ +package proguard.classfile.attribute.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.ProgramClass; +import proguard.classfile.attribute.InnerClassesInfo; +import proguard.classfile.constant.visitor.ConstantVisitor; + +public class InnerClassInfoClassConstantVisitor implements InnerClassesInfoVisitor { + + private final ConstantVisitor innerClassConstantVisitor; + private final ConstantVisitor outerClassConstantVisitor; + + public InnerClassInfoClassConstantVisitor(ConstantVisitor innerClassConstantVisitor, ConstantVisitor outerClassConstantVisitor) + { + this.innerClassConstantVisitor = innerClassConstantVisitor; + this.outerClassConstantVisitor = outerClassConstantVisitor; + } + + @Override + public void visitInnerClassesInfo(Clazz clazz, InnerClassesInfo innerClassesInfo) { + if (this.innerClassConstantVisitor != null) + { + innerClassesInfo.innerClassConstantAccept(clazz, this.innerClassConstantVisitor); + } + if (this.outerClassConstantVisitor != null) + { + innerClassesInfo.outerClassConstantAccept(clazz, this.outerClassConstantVisitor); + } + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java b/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java new file mode 100644 index 000000000..e50fad1b6 --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java @@ -0,0 +1,28 @@ +package proguard.classfile.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.visitor.ConstantVisitor; + +public class ClassConstantReferenceUpdater implements ConstantVisitor +{ + private final Clazz originalClass; + private final Clazz replacingClass; + public ClassConstantReferenceUpdater(Clazz originalClass, Clazz replacingClass) + { + this.originalClass = originalClass; + this.replacingClass = replacingClass; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + if (classConstant.referencedClass == originalClass) + { + classConstant.referencedClass = replacingClass; + } + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java b/base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java new file mode 100644 index 000000000..0ec8d309c --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java @@ -0,0 +1,46 @@ +package proguard.classfile.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.util.ClassUtil; +import proguard.util.ListParser; +import proguard.util.NameParser; +import proguard.util.StringMatcher; + +public class FieldReferenceFinder implements ConstantVisitor { + + private final Clazz referencedClass; + private final StringMatcher regularExpressionMatcher; + private final ConstantVisitor constantVisitor; + private boolean fieldReferenceFound = false; + + public FieldReferenceFinder(Clazz referencedClass, + String fieldNameRegularExpression, + ConstantVisitor constantVisitor) + { + this.referencedClass = referencedClass; + this.regularExpressionMatcher = new ListParser(new NameParser(null)).parse(fieldNameRegularExpression); + this.constantVisitor = constantVisitor; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) + { + if (fieldrefConstant.referencedClass == referencedClass + && this.regularExpressionMatcher.matches(fieldrefConstant.getName(clazz))) + { + this.fieldReferenceFound = true; + fieldrefConstant.accept(clazz, this.constantVisitor); + } + } + + public boolean isFieldReferenceFound() + { + return this.fieldReferenceFound; + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/FieldRenamer.java b/base/src/main/java/proguard/classfile/visitor/FieldRenamer.java new file mode 100644 index 000000000..b041862b3 --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/FieldRenamer.java @@ -0,0 +1,80 @@ +package proguard.classfile.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.Field; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramField; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.Utf8Constant; +import proguard.classfile.constant.visitor.ConstantVisitor; + +import java.util.HashMap; +import java.util.Map; + +public class FieldRenamer implements MemberVisitor, ConstantVisitor { + + private final String newFieldNamePrefix; + private int newFieldNameIndex = 0; + private final boolean useDescriptorBasedNames; + private final Map descriptorIndex = new HashMap<>(); + private Field lastVisitedField; + private Clazz lastVisitedClass; + + public FieldRenamer(String newFieldNamePrefix) + { + this.newFieldNamePrefix = newFieldNamePrefix; + this.useDescriptorBasedNames = false; + } + + public FieldRenamer(boolean useDescriptorBasedNames) + { + this.newFieldNamePrefix = ""; + this.useDescriptorBasedNames = useDescriptorBasedNames; + } + + public void resetIndex() + { + this.newFieldNameIndex = 0; + } + + @Override + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + this.lastVisitedClass = programClass; + this.lastVisitedField = programField; + programClass.constantPoolEntryAccept(programField.u2nameIndex, this); + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) + { + String newName = getNextFieldName(); + utf8Constant.setString(newName); + this.newFieldNameIndex++; + String descriptor = this.lastVisitedField.getDescriptor(this.lastVisitedClass); + this.descriptorIndex.put(descriptor, this.descriptorIndex.getOrDefault(descriptor, 0) + 1); + + } + + public String getNextFieldName() + { + String newName; + if (useDescriptorBasedNames) + { + // This is non-logical behaviour: the method name suggests a globally correct next name would be + // returned, but here it depends on the previously visited field, while in practice + // we don't know whether the next field will have the same descriptor + String descriptor = this.lastVisitedField.getDescriptor(this.lastVisitedClass); + newName = descriptor.replace(";", "").replace("/", "").replace("[", "") + this.descriptorIndex.getOrDefault(descriptor, 0); + + } + else + { + newName = this.newFieldNamePrefix + (this.newFieldNameIndex + 1); + } + return newName; + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/MethodCopier.java b/base/src/main/java/proguard/classfile/visitor/MethodCopier.java new file mode 100644 index 000000000..550abfecf --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/MethodCopier.java @@ -0,0 +1,158 @@ +package proguard.classfile.visitor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.*; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; + +import java.util.Objects; + +public class MethodCopier implements MemberVisitor, AttributeVisitor, InstructionVisitor +{ + private final ProgramClass destinationClass; + private final ClassBuilder classBuilder; + private final String newMethodNamePrefix; + private final String newMethodDescriptor; + private final int accessFlags; + private final ConstantAdder constantAdder; + private final CodeAttributeComposer codeAttributeComposer = new CodeAttributeComposer(); + private final ExceptionInfoAdder exceptionInfoAdder; + private int methodCounter = 0; + private final FieldRenamer fieldRenamer; + private final FieldCopier fieldCopier; + private static final Logger logger = LogManager.getLogger(MethodCopier.class); + + public MethodCopier(ProgramClass destinationClass, String newMethodNamePrefix, int accessFlags) + { + this(destinationClass, newMethodNamePrefix, null, accessFlags); + } + + public MethodCopier(ProgramClass destinationClass, String newMethodNamePrefix, String newMethodDescriptor, int accessFlags) + { + this(destinationClass, newMethodNamePrefix, newMethodDescriptor, accessFlags, null); + } + + public MethodCopier(ProgramClass destinationClass, String newMethodNamePrefix, String newMethodDescriptor, int accessFlags, FieldRenamer fieldRenamer) + { + this.destinationClass = destinationClass; + this.classBuilder = new ClassBuilder(destinationClass); + this.newMethodNamePrefix = newMethodNamePrefix; + this.newMethodDescriptor = newMethodDescriptor; + this.accessFlags = accessFlags; + this.fieldRenamer = fieldRenamer; + this.fieldCopier = new FieldCopier(this.classBuilder, this.fieldRenamer); + this.constantAdder = new ConstantAdder(destinationClass); + this.exceptionInfoAdder = new ExceptionInfoAdder(this.destinationClass, this.codeAttributeComposer); + } + + private int getNewMethodIndex() + { + int methodIndex = this.methodCounter; + this.methodCounter++; + return methodIndex; + } + + @Override + public void visitAnyMember(Clazz clazz, Member member) { + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + codeAttributeComposer.reset(); + if (this.fieldRenamer != null) + { + this.fieldRenamer.resetIndex(); + } + programMethod.attributesAccept(programClass, this); + int methodIndex = getNewMethodIndex(); + String newMethodName = newMethodNamePrefix; + if (methodIndex > 1) + { + logger.warn(methodIndex + " methods were visited by MethodCopier(" + destinationClass + ", " + newMethodNamePrefix +")."); + newMethodName += "$" + methodIndex; + } + String methodDescriptor = programMethod.getDescriptor(programClass); + if (this.newMethodDescriptor != null) + { + methodDescriptor = this.newMethodDescriptor; + } + ProgramMethod newMethod = classBuilder.addAndReturnMethod(accessFlags, newMethodName, methodDescriptor); + codeAttributeComposer.addCodeAttribute(this.destinationClass, newMethod); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) { + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeAttributeComposer.beginCodeFragment(codeAttribute.u4codeLength); + // copy code and exceptions + codeAttribute.instructionsAccept(clazz, method, this); + codeAttribute.exceptionsAccept(clazz, method, this.exceptionInfoAdder); + codeAttribute.attributesAccept(clazz, method, this); + codeAttributeComposer.endCodeFragment(); + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + // copy instruction + codeAttributeComposer.appendInstruction(offset, instruction); + } + + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + // TODO: Replace references to the lambda class itself by references to the new lambda group. + // (WIP) + this.fieldCopier.reset(); + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) + { + // TODO: replace lambda reference by lambda group reference + // Note: is it sufficient to only replace the class constant? + // or should the name of the class also be updated + if (Objects.equals(fieldrefConstant.referencedClass, clazz)) + { + // copy the field to the lambda group + fieldrefConstant.referencedFieldAccept(fieldCopier); + } + } + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) + { + if (Objects.equals(classConstant.referencedClass, clazz)) + { + logger.info("Class " + clazz + " references itself in a constant instruction: " + constantInstruction); + } + } + }); + if (this.fieldCopier.hasCopiedField()) + { + // add the necessary constants to the lambda group + constantInstruction.constantIndex = classBuilder.getConstantPoolEditor().addFieldrefConstant(destinationClass, fieldCopier.getLastCopiedField()); + } + else + { + // ensure the referenced constant is in the constant pool at the correct index + constantInstruction.constantIndex = this.constantAdder.addConstant(clazz, constantInstruction.constantIndex); + } + // copy instruction + codeAttributeComposer.appendInstruction(offset, constantInstruction); + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java b/base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java new file mode 100644 index 000000000..2f9c82fb6 --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java @@ -0,0 +1,34 @@ +package proguard.classfile.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.constant.AnyMethodrefConstant; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.visitor.ConstantVisitor; + +public class MethodReferenceFinder implements ConstantVisitor +{ + private final Method referencedMethod; + private boolean methodIsReferenced = false; + + public MethodReferenceFinder(Method referencedMethod) + { + this.referencedMethod = referencedMethod; + } + + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) + { + if (anyMethodrefConstant.referencedMethod != null + && anyMethodrefConstant.referencedMethod.equals(referencedMethod)) + { + this.methodIsReferenced = true; + } + } + + public boolean methodReferenceFound() + { + return this.methodIsReferenced; + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/PackageGrouper.java b/base/src/main/java/proguard/classfile/visitor/PackageGrouper.java new file mode 100644 index 000000000..b3ab94fca --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/PackageGrouper.java @@ -0,0 +1,66 @@ +package proguard.classfile.visitor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.ClassPool; +import proguard.classfile.Clazz; +import proguard.classfile.util.ClassUtil; + +import java.util.HashMap; +import java.util.Map; + +/** + * This class + */ +public class PackageGrouper implements ClassVisitor { + + private final Map packageClassPools = new HashMap<>(); + private static final Logger logger = LogManager.getLogger(PackageGrouper.class); + + @Override + public void visitAnyClass(Clazz clazz) + { + String classPackageName = ClassUtil.internalPackageName(clazz.getName()); + // or + // String classPackageName = ClassUtil.internalPackageName(clazz.getName()); + if (!packageClassPools.containsKey(classPackageName)) + { + logger.info("New package found: {}", + ClassUtil.externalPackageName(ClassUtil.externalClassName(clazz.getName()))); + packageClassPools.put(classPackageName, new ClassPool()); + } + packageClassPools.get(classPackageName).addClass(clazz); + } + + public int size() + { + return packageClassPools.size(); + } + + public boolean containsPackage(String packageName) + { + return packageClassPools.containsKey(packageName); + } + + public Iterable packageNames() + { + return packageClassPools.keySet(); + } + + public void packagesAccept(ClassPoolVisitor classPoolVisitor) + { + for (ClassPool packageClassPool : packageClassPools.values()) + { + classPoolVisitor.visitClassPool(packageClassPool); + } + } + + public void packageAccept(String packageName, ClassPoolVisitor classPoolVisitor) + { + ClassPool packageClassPool = this.packageClassPools.get(packageName); + if (packageClassPool != null) + { + classPoolVisitor.visitClassPool(packageClassPool); + } + } +} diff --git a/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt b/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt new file mode 100644 index 000000000..7b659b761 --- /dev/null +++ b/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt @@ -0,0 +1,98 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2021 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +package proguard.classfile.visitor + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldBeIn +import io.kotest.matchers.shouldBe +import proguard.classfile.ClassPool +import proguard.classfile.util.ClassUtil +import testutils.ClassPoolBuilder +import testutils.KotlinSource + +class PackageGrouperTest : FreeSpec({ + + val (programClassPool, _) = ClassPoolBuilder.fromSource( + KotlinSource( + "Test.kt", + """ + package app.package1 + fun main(index: Int) { + val lambda1 = { + println("Lambda1") + } + lambda1() + } + """.trimIndent() + ), + KotlinSource( + "Test2.kt", + """ + package app.package2 + fun main() { + val lambda2 = { println("Lambda2") } + lambda2() + } + """.trimIndent() + ) + ) + + "Given a PackageGrouper" - { + val grouper = PackageGrouper() + "When the grouper is applied to classes of different packages" - { + programClassPool.classesAccept(grouper) + "Then the amount packages of packages found by the grouper equals the amount of packages in the class pool" { + grouper.size() shouldBe 2 + } + "Then the grouper has found the packages that are in the class pool" { + grouper.containsPackage("app/package1") shouldBe true + grouper.containsPackage("app/package2") shouldBe true + } + "Then the grouper does not contain other packages, except for those from the class pool" { + grouper.packageNames().forEach { + it shouldBeIn arrayListOf("app/package2", "app/package1") + } + } + "Then the union of classes in the packages of the package grouper contain equals the class pool" { + val grouperCompleteClassPool = ClassPool() + grouper.packagesAccept { packageClassPool -> + packageClassPool.classes().forEach { clazz -> + programClassPool.contains(clazz) + grouperCompleteClassPool.addClass(clazz) + } + } + programClassPool.classes().forEach { clazz -> + grouperCompleteClassPool.contains(clazz) + } + } + "Then the classes of a package class pool belong to the respective package" { + grouper.packageNames().forEach { packageName -> + grouper.packageAccept(packageName) { packageClassPool -> + packageClassPool.classesAccept { clazz -> + ClassUtil.internalPackageName(clazz.name) shouldBe packageName + } + } + } + } + } + } +}) diff --git a/base/src/test/kotlin/testutils/RecursiveConstantCollector.kt b/base/src/test/kotlin/testutils/RecursiveConstantCollector.kt new file mode 100644 index 000000000..0b56e6b19 --- /dev/null +++ b/base/src/test/kotlin/testutils/RecursiveConstantCollector.kt @@ -0,0 +1,71 @@ +package testutils + +import proguard.classfile.Clazz +import proguard.classfile.constant.* +import proguard.classfile.constant.visitor.ConstantVisitor +import proguard.classfile.editor.ConstantPoolEditor + +class RecursiveConstantCollector(val constantPoolEditor: ConstantPoolEditor) : ConstantVisitor { + var firstAddedConstantIndex = 0 + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) { + val constantIndex = constantPoolEditor.addConstant(constant) + if (firstAddedConstantIndex == 0) + { + firstAddedConstantIndex = constantIndex + } + } + + override fun visitStringConstant(clazz: Clazz?, stringConstant: StringConstant?) { + visitAnyConstant(clazz, stringConstant) + clazz?.constantPoolEntryAccept(stringConstant?.u2stringIndex!!, this) + } + + override fun visitDynamicConstant(clazz: Clazz?, dynamicConstant: DynamicConstant?) { + visitAnyConstant(clazz, dynamicConstant) + clazz?.constantPoolEntryAccept(dynamicConstant?.nameAndTypeIndex!!, this) + clazz?.constantPoolEntryAccept(dynamicConstant?.bootstrapMethodAttributeIndex!!, this) + } + + override fun visitInvokeDynamicConstant(clazz: Clazz?, invokeDynamicConstant: InvokeDynamicConstant?) { + visitAnyConstant(clazz, invokeDynamicConstant) + clazz?.constantPoolEntryAccept(invokeDynamicConstant?.nameAndTypeIndex!!, this) + clazz?.constantPoolEntryAccept(invokeDynamicConstant?.bootstrapMethodAttributeIndex!!, this) + } + + override fun visitMethodHandleConstant(clazz: Clazz?, methodHandleConstant: MethodHandleConstant?) { + visitAnyConstant(clazz, methodHandleConstant) + clazz?.constantPoolEntryAccept(methodHandleConstant?.referenceIndex!!, this) + } + + override fun visitModuleConstant(clazz: Clazz?, moduleConstant: ModuleConstant?) { + visitAnyConstant(clazz, moduleConstant) + clazz?.constantPoolEntryAccept(moduleConstant?.u2nameIndex!!, this) + } + + override fun visitPackageConstant(clazz: Clazz?, packageConstant: PackageConstant?) { + visitAnyConstant(clazz, packageConstant) + clazz?.constantPoolEntryAccept(packageConstant?.u2nameIndex!!, this) + } + + override fun visitAnyRefConstant(clazz: Clazz?, refConstant: RefConstant?) { + visitAnyConstant(clazz, refConstant) + clazz?.constantPoolEntryAccept(refConstant?.nameAndTypeIndex!!, this) + clazz?.constantPoolEntryAccept(refConstant?.classIndex!!, this) + } + + override fun visitClassConstant(clazz: Clazz?, classConstant: ClassConstant?) { + visitAnyConstant(clazz, classConstant) + clazz?.constantPoolEntryAccept(classConstant?.u2nameIndex!!, this) + } + + override fun visitMethodTypeConstant(clazz: Clazz?, methodTypeConstant: MethodTypeConstant?) { + visitAnyConstant(clazz, methodTypeConstant) + clazz?.constantPoolEntryAccept(methodTypeConstant?.descriptorIndex!!, this) + } + + override fun visitNameAndTypeConstant(clazz: Clazz?, nameAndTypeConstant: NameAndTypeConstant?) { + visitAnyConstant(clazz, nameAndTypeConstant) + clazz?.constantPoolEntryAccept(nameAndTypeConstant?.u2descriptorIndex!!, this) + clazz?.constantPoolEntryAccept(nameAndTypeConstant?.u2nameIndex!!, this) + } +} From b4ac2fb020962ee1221bfece6fffc78d4ff23616 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 16:30:02 +0200 Subject: [PATCH 178/195] Update references to moved Kotlin constants --- .../optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 654ba1ed5..66749badd 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -67,7 +67,7 @@ private Instruction[] getInstructionsForCase(int caseIndex) builder.invokevirtual(this.classBuilder.getProgramClass(), methodToBeCalled); // invoke the lambda implementation if (methodDoesNotHaveReturnValue(methodToBeCalled)) { // ensure there is a return value - builder.getstatic(ClassConstants.NAME_KOTLIN_UNIT, ClassConstants.FIELD_NAME_KOTLIN_UNIT_INSTANCE, ClassConstants.TYPE_KOTLIN_UNIT); + builder.getstatic(KotlinConstants.NAME_KOTLIN_UNIT, KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, KotlinConstants.TYPE_KOTLIN_UNIT); } builder.areturn(); // return return builder.instructions(); @@ -108,7 +108,7 @@ public ClassBuilder.CodeBuilder buildCodeBuilder() // - or throw an exception if (cases == 0) { - composer.getstatic(ClassConstants.NAME_KOTLIN_UNIT, ClassConstants.FIELD_NAME_KOTLIN_UNIT_INSTANCE, ClassConstants.TYPE_KOTLIN_UNIT); + composer.getstatic(KotlinConstants.NAME_KOTLIN_UNIT, KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, KotlinConstants.TYPE_KOTLIN_UNIT); composer.areturn(); return; } From baab6b9d45588fd001e091fe5ecb396d8090eb8f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 16:32:23 +0200 Subject: [PATCH 179/195] Move ModifiedAllInnerClassesInfoVisitor from PGC to ProGuard --- .../ModifiedAllInnerClassesInfoVisitor.java | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) create mode 100644 base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java diff --git a/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java b/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java new file mode 100644 index 000000000..310241800 --- /dev/null +++ b/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java @@ -0,0 +1,25 @@ +package proguard.classfile.attribute.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.attribute.InnerClassesAttribute; + +/** + * This {@link AllInnerClassesInfoVisitor} revisits each {@link InnerClassesAttribute} everytime its amount of + * referenced classes has been modified in the meantime. + */ +public class ModifiedAllInnerClassesInfoVisitor extends AllInnerClassesInfoVisitor { + + public ModifiedAllInnerClassesInfoVisitor(InnerClassesInfoVisitor innerClassesInfoVisitor) { + super(innerClassesInfoVisitor); + } + + public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) + { + int originalClassesCount = -1; + while (originalClassesCount != innerClassesAttribute.u2classesCount) + { + originalClassesCount = innerClassesAttribute.u2classesCount; + super.visitInnerClassesAttribute(clazz, innerClassesAttribute); + } + } +} From 0eb6c834a9171ddd9ecb598fd4aac7b76649108d Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 16:44:46 +0200 Subject: [PATCH 180/195] Move FieldCopier from PGC to ProGuard --- .../classfile/visitor/FieldCopier.java | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 base/src/main/java/proguard/classfile/visitor/FieldCopier.java diff --git a/base/src/main/java/proguard/classfile/visitor/FieldCopier.java b/base/src/main/java/proguard/classfile/visitor/FieldCopier.java new file mode 100644 index 000000000..5d63627a1 --- /dev/null +++ b/base/src/main/java/proguard/classfile/visitor/FieldCopier.java @@ -0,0 +1,78 @@ +package proguard.classfile.visitor; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.*; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.ClassEditor; + +public class FieldCopier implements MemberVisitor, ConstantVisitor { + + private final ClassBuilder classBuilder; + private final ClassEditor classEditor; + private final FieldRenamer fieldRenamer; + private ProgramField lastCopiedField; + private boolean hasCopiedField = false; + private static final Logger logger = LogManager.getLogger(FieldCopier.class); + + public FieldCopier(ClassBuilder builder, FieldRenamer renamer) + { + this.classBuilder = builder; + this.classEditor = new ClassEditor(builder.getProgramClass()); + this.fieldRenamer = renamer; + } + + @Override + public void visitProgramField(ProgramClass programClass, ProgramField programField) + { + String fieldName = programField.getName(programClass); + if (this.fieldRenamer != null) + { + fieldName = fieldRenamer.getNextFieldName(); + } + + String fieldDescriptor = programField.getDescriptor(programClass); + Field oldField = classBuilder.getProgramClass().findField(fieldName, null); + Field oldFieldSameDescriptor = classBuilder.getProgramClass().findField(fieldName, fieldDescriptor); + if (oldField != null && oldFieldSameDescriptor == null) + { + String oldFieldDescriptor = oldField.getDescriptor(classBuilder.getProgramClass()); + //logger.warn("Field " + fieldName + " already exists in class " + classBuilder.getProgramClass() + " with different descriptor: " + oldFieldDescriptor + " <-> " + fieldDescriptor + ". The field will be duplicated with different descriptors."); + // Merge the field types: generalise to a common super type + //fieldDescriptor = ClassConstants.TYPE_JAVA_LANG_OBJECT; + } + else if (oldFieldSameDescriptor != null) + { + this.classEditor.removeField(oldFieldSameDescriptor); + } + ProgramField copiedField = classBuilder.addAndReturnField(programField.u2accessFlags, fieldName, fieldDescriptor); + if (this.fieldRenamer != null) + { + this.fieldRenamer.visitProgramField(classBuilder.getProgramClass(), copiedField); + } + this.lastCopiedField = copiedField; + this.hasCopiedField = true; + } + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + fieldrefConstant.referencedFieldAccept(this); + } + + public ProgramField getLastCopiedField() + { + return this.lastCopiedField; + } + + public boolean hasCopiedField() + { + return this.hasCopiedField; + } + + public void reset() + { + this.hasCopiedField = false; + } +} From 79f7656f956a8886ad4d568c9d1ac18aef2a791c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 16:49:04 +0200 Subject: [PATCH 181/195] MethodCopierTest moved from PGC to ProGuard --- .../classfile/visitor/MethodCopierTest.kt | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt diff --git a/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt b/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt new file mode 100644 index 000000000..e96e436e5 --- /dev/null +++ b/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt @@ -0,0 +1,105 @@ +package proguard.classfile.visitor + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.matchers.shouldNotBe +import proguard.classfile.AccessConstants +import proguard.classfile.ClassConstants +import proguard.classfile.VersionConstants +import proguard.classfile.attribute.visitor.AllAttributeVisitor +import proguard.classfile.editor.ClassBuilder +import proguard.classfile.editor.InstructionSequenceBuilder +import proguard.classfile.instruction.visitor.AllInstructionVisitor +import proguard.classfile.util.InstructionSequenceMatcher +import testutils.MatchDetector + +class MethodCopierTest : FreeSpec({ + "Given a method and a target class" - { + val classBuilder = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.FINAL or AccessConstants.SUPER, + "Test", + ClassConstants.NAME_JAVA_LANG_OBJECT + ) + val testClass = classBuilder.programClass + val method = classBuilder.addAndReturnMethod( + AccessConstants.PUBLIC, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT, + 50 + ) { + it + .aload_0() + .iconst(0) + .invokespecial( + ClassConstants.NAME_JAVA_LANG_OBJECT, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT + ) + .return_() + } + val methodDescriptor = method.getDescriptor(testClass) + val methodName = method.getName(testClass) + val methodAccessFlags = AccessConstants.PUBLIC + val targetClass = ClassBuilder( + VersionConstants.CLASS_VERSION_1_8, + AccessConstants.PUBLIC, + "TargetClass", + ClassConstants.NAME_JAVA_LANG_OBJECT + ).programClass + "When the method is copied to the target class" - { + val methodCopier = MethodCopier(targetClass, ClassConstants.METHOD_NAME_INIT, methodAccessFlags) + method.accept(testClass, methodCopier) + "Then the target class contains a method with the correct name and descriptor" { + targetClass.findMethod(methodName, methodDescriptor) shouldNotBe null + } + + "Then the copied method has the correct access modifiers" { + val copiedMethod = targetClass.findMethod(methodName, methodDescriptor) + copiedMethod.accessFlags shouldBe methodAccessFlags + } + + "Then the instructions of the copied method match those of the original method" { + val builder = InstructionSequenceBuilder() + builder + .aload_0() + .iconst(0) + .invokespecial( + ClassConstants.NAME_JAVA_LANG_OBJECT, + ClassConstants.METHOD_NAME_INIT, + ClassConstants.METHOD_TYPE_INIT + ) + .return_() + val matchDetector = MatchDetector(InstructionSequenceMatcher(builder.constants(), builder.instructions())) + val copiedMethod = targetClass.findMethod(methodName, methodDescriptor) + copiedMethod.accept( + targetClass, + AllAttributeVisitor( + AllInstructionVisitor( + matchDetector + ) + ) + ) + matchDetector.matchIsFound shouldBe true + } + } + + "When the method is copied to the target class with a new descriptor" - { + val newDescriptor = "(I)V" + val methodCopier = MethodCopier(targetClass, methodName, newDescriptor, methodAccessFlags) + method.accept(testClass, methodCopier) + "Then the target class contains a method with the correct name and descriptor" { + targetClass.findMethod(methodName, newDescriptor) shouldNotBe null + } + } + + "When the method is copied to the target class with a new name prefix" - { + val newNamePrefix = "copiedMethod" + val methodCopier = MethodCopier(targetClass, newNamePrefix, methodAccessFlags) + method.accept(testClass, methodCopier) + "Then the target class contains a method with the correct name and descriptor" { + targetClass.findMethod(newNamePrefix, methodDescriptor) shouldNotBe null + } + } + } +}) From ba4d80f1a94229a884c406e0e6095023d6fc8398 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke <36644549+heckej@users.noreply.github.com> Date: Wed, 29 Jun 2022 17:25:01 +0200 Subject: [PATCH 182/195] Replace `info` logging statement with `trace` Co-authored-by: James Hamilton --- .../java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 12ffc3fe9..43167f783 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -67,7 +67,7 @@ public void visitClassPool(ClassPool lambdaClassPool) // choose a name for the lambda group // ensure that the lambda group is in the same package as the classes of the class pool String lambdaGroupName = getPackagePrefixOfClasses(lambdaClassPool) + NAME_LAMBDA_GROUP; - logger.info("Creating lambda group with name {}", ClassUtil.externalClassName(lambdaGroupName)); + logger.trace("Creating lambda group with name {}", ClassUtil.externalClassName(lambdaGroupName)); // create a lambda group builder KotlinLambdaGroupBuilder lambdaGroupBuilder = new KotlinLambdaGroupBuilder(lambdaGroupName, From 4426bb6e2e14255de11e4f58b309de1b50931346 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 17:46:52 +0200 Subject: [PATCH 183/195] Use lambda merging in configuration as optimisation option --- base/src/main/java/proguard/Configuration.java | 15 --------------- .../java/proguard/ConfigurationConstants.java | 3 --- .../main/java/proguard/ConfigurationParser.java | 3 --- .../main/java/proguard/ConfigurationWriter.java | 3 --- base/src/main/java/proguard/ProGuard.java | 10 ---------- .../main/java/proguard/optimize/Optimizer.java | 3 +++ 6 files changed, 3 insertions(+), 34 deletions(-) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index ae02a52e0..43d6581bc 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -206,21 +206,6 @@ public class Configuration */ public File printLambdaGroupMapping; - /** - * Specifies whether Kotlin lambda classes can be merged into lambda groups. - */ - public boolean mergeKotlinLambdaClasses; - - /** - * Specifies whether Kotlin lambda classes have to be merged before or after the other optimisations. - */ - public boolean lambdaMergingAfterOptimizing; - - /** - * Specifies whether Kotlin lambda classes may be merged if they contain unexpected methods. - */ - public boolean mergeLambdaClassesWithUnexpectedMethods; - /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index 9828a0002..6540ddd0c 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -67,9 +67,6 @@ public class ConfigurationConstants public static final String ALLOW_ACCESS_MODIFICATION_OPTION = "-allowaccessmodification"; public static final String MERGE_INTERFACES_AGGRESSIVELY_OPTION = "-mergeinterfacesaggressively"; public static final String PRINT_LAMBDAGROUP_MAPPING_OPTION = "-printlambdagroupmapping"; - public static final String MERGE_KOTLIN_LAMBDA_CLASSES_OPTION = "-mergekotlinlambdaclasses"; - public static final String LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION = "-lambdamergingafteroptimizing"; - public static final String LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS = "-mergelambdaclasseswithunexpectedmethods"; public static final String DONT_OBFUSCATE_OPTION = "-dontobfuscate"; public static final String PRINT_MAPPING_OPTION = "-printmapping"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index e4132e311..4661fdc7a 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -191,9 +191,6 @@ public void parse(Configuration configuration) else if (ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION .startsWith(nextWord)) configuration.allowAccessModification = parseNoArgument(true); else if (ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION .startsWith(nextWord)) configuration.mergeInterfacesAggressively = parseNoArgument(true); else if (ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION .startsWith(nextWord)) configuration.printLambdaGroupMapping = parseOptionalFile(); - else if (ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION .startsWith(nextWord)) configuration.mergeKotlinLambdaClasses = parseNoArgument(true); - else if (ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION .startsWith(nextWord)) configuration.lambdaMergingAfterOptimizing = parseNoArgument(true); - else if (ConfigurationConstants.LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS .startsWith(nextWord)) configuration.mergeLambdaClassesWithUnexpectedMethods = parseNoArgument(true); else if (ConfigurationConstants.DONT_OBFUSCATE_OPTION .startsWith(nextWord)) configuration.obfuscate = parseNoArgument(false); else if (ConfigurationConstants.PRINT_MAPPING_OPTION .startsWith(nextWord)) configuration.printMapping = parseOptionalFile(); diff --git a/base/src/main/java/proguard/ConfigurationWriter.java b/base/src/main/java/proguard/ConfigurationWriter.java index 938a8d990..5d41c3291 100644 --- a/base/src/main/java/proguard/ConfigurationWriter.java +++ b/base/src/main/java/proguard/ConfigurationWriter.java @@ -125,9 +125,6 @@ public void write(Configuration configuration) throws IOException writeOption(ConfigurationConstants.ALLOW_ACCESS_MODIFICATION_OPTION, configuration.allowAccessModification); writeOption(ConfigurationConstants.MERGE_INTERFACES_AGGRESSIVELY_OPTION, configuration.mergeInterfacesAggressively); writeOption(ConfigurationConstants.PRINT_LAMBDAGROUP_MAPPING_OPTION, configuration.printLambdaGroupMapping); - writeOption(ConfigurationConstants.MERGE_KOTLIN_LAMBDA_CLASSES_OPTION, configuration.mergeKotlinLambdaClasses); - writeOption(ConfigurationConstants.LAMBDA_MERGING_AFTER_OPTIMIZE_OPTION, configuration.lambdaMergingAfterOptimizing); - writeOption(ConfigurationConstants.LAMBDA_MERGING_ALLOW_UNEXPECTED_METHODS, configuration.mergeLambdaClassesWithUnexpectedMethods); writeOption(ConfigurationConstants.DONT_OBFUSCATE_OPTION, !configuration.obfuscate); writeOption(ConfigurationConstants.PRINT_MAPPING_OPTION, configuration.printMapping); diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index df49f0b8d..695d4fb83 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -177,11 +177,6 @@ public void execute() throws Exception shrink(false); } - if (configuration.mergeKotlinLambdaClasses && !configuration.lambdaMergingAfterOptimizing) - { - mergeKotlinLambdaClasses(); - } - // Create a matcher for filtering optimizations. StringMatcher filter = configuration.optimizations != null ? new ListParser(new NameParser()).parse(configuration.optimizations) : @@ -199,11 +194,6 @@ public void execute() throws Exception linearizeLineNumbers(); } - if (configuration.mergeKotlinLambdaClasses && configuration.lambdaMergingAfterOptimizing) - { - mergeKotlinLambdaClasses(); - } - if (configuration.obfuscate) { obfuscate(); diff --git a/base/src/main/java/proguard/optimize/Optimizer.java b/base/src/main/java/proguard/optimize/Optimizer.java index f3a175226..49300745d 100644 --- a/base/src/main/java/proguard/optimize/Optimizer.java +++ b/base/src/main/java/proguard/optimize/Optimizer.java @@ -64,6 +64,7 @@ public class Optimizer implements Pass private static final String CLASS_MERGING_VERTICAL = "class/merging/vertical"; private static final String CLASS_MERGING_HORIZONTAL = "class/merging/horizontal"; private static final String CLASS_MERGING_WRAPPER = "class/merging/wrapper"; + private static final String CLASS_MERGING_KOTLINLAMBDA = "class/merging/kotlinlambda"; private static final String FIELD_REMOVAL_WRITEONLY = "field/removal/writeonly"; private static final String FIELD_MARKING_PRIVATE = "field/marking/private"; private static final String FIELD_GENERALIZATION_CLASS = "field/generalization/class"; @@ -146,6 +147,7 @@ public class Optimizer implements Pass private boolean classMergingVertical; private boolean classMergingHorizontal; private boolean classMergingWrapper; + private boolean classMergingKotlinlambda; private boolean fieldRemovalWriteonly; private boolean fieldMarkingPrivate; private boolean fieldGeneralizationClass; @@ -221,6 +223,7 @@ public void execute(AppView appView) throws Exception classMergingVertical = filter.matches(CLASS_MERGING_VERTICAL); classMergingHorizontal = filter.matches(CLASS_MERGING_HORIZONTAL); classMergingWrapper = filter.matches(CLASS_MERGING_WRAPPER); + classMergingKotlinlambda = filter.matches(CLASS_MERGING_KOTLINLAMBDA); fieldRemovalWriteonly = filter.matches(FIELD_REMOVAL_WRITEONLY); fieldMarkingPrivate = filter.matches(FIELD_MARKING_PRIVATE); fieldGeneralizationClass = filter.matches(FIELD_GENERALIZATION_CLASS); From 3cf02453608c30bc214faa4af59c64540a249f46 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 17:47:19 +0200 Subject: [PATCH 184/195] Code alignment --- .../kotlin/KotlinLambdaClassMerger.java | 5 ++-- .../KotlinLambdaEnclosingMethodUpdater.java | 23 +++++++++++-------- .../optimize/kotlin/KotlinLambdaMerger.java | 3 ++- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 43167f783..30c409d10 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -26,7 +26,8 @@ import java.util.List; import java.util.Map; -public class KotlinLambdaClassMerger implements ClassPoolVisitor { +public class KotlinLambdaClassMerger implements ClassPoolVisitor +{ public static final String NAME_LAMBDA_GROUP = "LambdaGroup"; private final ClassVisitor lambdaGroupVisitor; @@ -97,4 +98,4 @@ private static String getPackagePrefixOfClasses(ClassPool classPool) String someClassName = classPool.classNames().next(); return ClassUtil.internalPackagePrefix(someClassName); } -} \ No newline at end of file +} diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 6bcb60ac8..943cf87d6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -13,7 +13,11 @@ import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.peephole.RetargetedInnerClassAttributeRemover; -public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, AttributeVisitor, MemberVisitor { +public class KotlinLambdaEnclosingMethodUpdater +implements ClassVisitor, + AttributeVisitor, + MemberVisitor +{ private final ClassPool programClassPool; private final ClassPool libraryClassPool; @@ -31,13 +35,13 @@ public class KotlinLambdaEnclosingMethodUpdater implements ClassVisitor, Attribu private static final RetargetedInnerClassAttributeRemover retargetedInnerClassAttributeRemover = new RetargetedInnerClassAttributeRemover(); - public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, - ClassPool libraryClassPool, - ProgramClass lambdaClass, - ProgramClass lambdaGroup, - int classId, - int arity, - String constructorDescriptor, + public KotlinLambdaEnclosingMethodUpdater(ClassPool programClassPool, + ClassPool libraryClassPool, + ProgramClass lambdaClass, + ProgramClass lambdaGroup, + int classId, + int arity, + String constructorDescriptor, ExtraDataEntryNameMap extraDataEntryNameMap) { this.programClassPool = programClassPool; @@ -76,7 +80,8 @@ public void visitEnclosingMethodAttribute(Clazz lambdaClass, EnclosingMethodAttr { // the given method must be the method where the lambda is defined Clazz enclosingClass = enclosingMethodAttribute.referencedClass; - if (visitEnclosingMethodAttribute || enclosingClass == lambdaClass) { + if (visitEnclosingMethodAttribute || enclosingClass == lambdaClass) + { return; } diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index c72e58f9b..e6769a691 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -20,7 +20,8 @@ import proguard.util.*; import java.io.PrintWriter; -public class KotlinLambdaMerger implements Pass { +public class KotlinLambdaMerger implements Pass +{ public static final String NAME_KOTLIN_LAMBDA = "kotlin/jvm/internal/Lambda"; public static final String NAME_KOTLIN_FUNCTION = "kotlin/jvm/functions/Function"; From b5f6c2561186e548c27f63d79fe12730d5406e3c Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 17:51:12 +0200 Subject: [PATCH 185/195] '{' on new line --- .../optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index 943cf87d6..c9219a968 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -62,7 +62,8 @@ public void visitProgramMethod(ProgramClass enclosingClass, ProgramMethod enclos // the given class must be the class that defines the lambda // the given method must be the method where the lambda is defined - if (visitEnclosingMethod) { + if (visitEnclosingMethod) + { return; } visitEnclosingMethod = true; @@ -252,7 +253,8 @@ private Instruction[][][] createReplacementPatternsForLambda(InstructionSequence public void visitAnyClass(Clazz clazz) {} @Override - public void visitProgramClass(ProgramClass programClass) { + public void visitProgramClass(ProgramClass programClass) + { ClassReferenceFinder classReferenceFinder = new ClassReferenceFinder(this.currentLambdaClass); programClass.constantPoolEntriesAccept(classReferenceFinder); if (classReferenceFinder.classReferenceFound()) From ea7bcb22fee1ea7d579ee8310ca0b2687f701d6b Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:10:11 +0200 Subject: [PATCH 186/195] Remove 'unexpected methods' check --- .../kotlin/KotlinLambdaGroupBuilder.java | 2 +- .../optimize/kotlin/KotlinLambdaMerger.java | 33 ------------------- 2 files changed, 1 insertion(+), 34 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index ecf28aad2..6c3edcce1 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -134,7 +134,7 @@ public void visitProgramClass(ProgramClass lambdaClass) { private void mergeLambdaClass(ProgramClass lambdaClass) { - KotlinLambdaMerger.ensureCanMerge(lambdaClass, configuration.mergeLambdaClassesWithUnexpectedMethods, programClassPool); + KotlinLambdaMerger.ensureCanMerge(lambdaClass, programClassPool); ProgramClass lambdaGroup = this.classBuilder.getProgramClass(); diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index e6769a691..3fda8b6a3 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -264,13 +264,10 @@ public static boolean shouldMerge(ProgramClass lambdaClass) /** * Checks whether the given lambda class can be merged and throws an exception if it cannot be merged. * @param lambdaClass the lambda class for which it should be checked whether it can be merged - * @param mergeLambdaClassesWithUnexpectedMethods a configuration setting that allows merging lambda classes that - * contain unexpected instance methods * @param programClassPool the program class pool in which the lambda class is defined and used * @throws IllegalArgumentException if the given lambda class cannot be merged */ public static void ensureCanMerge(ProgramClass lambdaClass, - boolean mergeLambdaClassesWithUnexpectedMethods, ClassPool programClassPool) throws IllegalArgumentException { String externalClassName = ClassUtil.externalClassName(lambdaClass.getName()); @@ -311,11 +308,6 @@ else if (!lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(lambdaClass)) throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because its " + "methods are too big to be inlined."); } - else if (!mergeLambdaClassesWithUnexpectedMethods && !lambdaClassHasNoUnexpectedMethods(lambdaClass)) - { - throw new IllegalArgumentException("Lambda class " + externalClassName + " cannot be merged, because it " + - "contains unexpected methods."); - } } private static boolean lambdaClassHasNoBootstrapMethod(ProgramClass lambdaClass) { @@ -358,31 +350,6 @@ private static boolean lambdaClassHasTotalMethodCodeSizeThatCanBeInlined(Program return codeSizeCounter.getCount() <= KotlinLambdaGroupBuilder.MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH; } - private static boolean lambdaClassHasNoUnexpectedMethods(ProgramClass lambdaClass) - { - String methodNameRegularExpression = "!" + ClassConstants.METHOD_NAME_INIT; - methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; - methodNameRegularExpression += ",!" + ClassConstants.METHOD_NAME_CLINIT; - methodNameRegularExpression += ",!" + KotlinConstants.METHOD_NAME_LAMBDA_INVOKE; - MethodCounter methodCounter = new MethodCounter(); - lambdaClass.methodsAccept(new MemberNameFilter(methodNameRegularExpression, - new MultiMemberVisitor( - methodCounter, - new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, - ProgramMethod programMethod) { - logger.warn("Lambda class {} contains an unexpected method: {}", - ClassUtil.externalClassName(lambdaClass.getName()), - ClassUtil.externalFullMethodDescription(lambdaClass.getName(), - programMethod.getAccessFlags(), - programMethod.getName(programClass), - programMethod.getDescriptor(lambdaClass))); - } - }))); - return methodCounter.getCount() == 0; - } - private static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lambdaClass) { MethodCounter initMethodCounter = new MethodCounter(); From c28a14f517b735a65cb34d2bbfd428dad9992055 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:15:00 +0200 Subject: [PATCH 187/195] Use MultiClassVisitor to group class visitors --- .../kotlin/KotlinLambdaGroupBuilder.java | 15 ++++++----- .../optimize/kotlin/KotlinLambdaMerger.java | 25 ++++++++++--------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 6c3edcce1..bdcce1f2c 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -224,20 +224,19 @@ private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) private void inlineMethodsInsideClass(ProgramClass lambdaClass) { - lambdaClass.accept(new ProgramClassOptimizationInfoSetter()); - - lambdaClass.accept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter())); - - // Allow methods to be inlined - lambdaClass.accept(new AllMethodVisitor( + lambdaClass.accept(new MultiClassVisitor( + new ProgramClassOptimizationInfoSetter(), + new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter()), + // Allow methods to be inlined + new AllMethodVisitor( new AllAttributeVisitor( new SameClassMethodInliner(configuration.microEdition, configuration.android, MAXIMUM_INLINED_INVOKE_METHOD_CODE_LENGTH, configuration.allowAccessModification, true, - null)))); + null))))); } private void inlineLambdaInvokeMethods(ProgramClass lambdaClass) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 3fda8b6a3..2880ff656 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -64,28 +64,29 @@ public void execute(AppView appView) throws Exception // find all lambda classes with an empty closure // assume that the lambda classes have exactly 1 instance constructor, which has descriptor ()V // (i.e. no arguments) if the closure is empty - appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, + appView.programClassPool.classesAccept(new MultiClassVisitor( + new ClassProcessingFlagFilter(ProcessingFlags.DONT_OPTIMIZE, 0, - newProgramClassPoolFiller)); - appView.programClassPool.classesAccept(new ClassProcessingFlagFilter(0, + newProgramClassPoolFiller), + new ClassProcessingFlagFilter(0, ProcessingFlags.DONT_OPTIMIZE, new ImplementedClassFilter(kotlinLambdaClass, false, new ClassPoolFiller(lambdaClassPool), - newProgramClassPoolFiller)) - ); + newProgramClassPoolFiller)), + // add optimisation info to the lambda's, + // so that it can be filled out later + new ProgramClassOptimizationInfoSetter(), + new AllMemberVisitor( + new ProgramMemberOptimizationInfoSetter()))); // group the lambda's per package PackageGrouper packageGrouper = new PackageGrouper(); lambdaClassPool.classesAccept(packageGrouper); - // add optimisation info to the lambda's, so that it can be filled out later - appView.programClassPool.classesAccept(new ProgramClassOptimizationInfoSetter()); - appView.programClassPool.classesAccept(new AllMemberVisitor( - new ProgramMemberOptimizationInfoSetter())); - - ClassPool lambdaGroupClassPool = new ClassPool(); - ClassPool notMergedLambdaClassPool = new ClassPool(); + ClassPool lambdaGroupClassPool = new ClassPool(); + ClassPool notMergedLambdaClassPool = new ClassPool(); + ClassCounter mergedLambdaClassCounter = new ClassCounter(); // merge the lambda's per package KotlinLambdaClassMerger merger = new KotlinLambdaClassMerger(this.configuration, From 6944e3641f1a88458f4218244e86710dbecf9879 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:15:46 +0200 Subject: [PATCH 188/195] Add mergedLambdaVisitor + use it to count merged lambda classes --- .../kotlin/KotlinLambdaClassMerger.java | 4 ++++ .../kotlin/KotlinLambdaGroupBuilder.java | 16 ++++++++----- .../optimize/kotlin/KotlinLambdaMerger.java | 23 +++++++++---------- 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 30c409d10..6e2775454 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -32,6 +32,7 @@ public class KotlinLambdaClassMerger implements ClassPoolVisitor public static final String NAME_LAMBDA_GROUP = "LambdaGroup"; private final ClassVisitor lambdaGroupVisitor; private final ClassVisitor notMergedLambdaVisitor; + private final ClassVisitor mergedLambdaVisitor; private final Configuration configuration; private final ClassPool programClassPool; private final ClassPool libraryClassPool; @@ -42,6 +43,7 @@ public KotlinLambdaClassMerger(final Configuration configuration, final ClassPool programClassPool, final ClassPool libraryClassPool, final ClassVisitor lambdaGroupVisitor, + final ClassVisitor mergedLambdaVisitor, final ClassVisitor notMergedLambdaVisitor, final ExtraDataEntryNameMap extraDataEntryNameMap) { @@ -49,6 +51,7 @@ public KotlinLambdaClassMerger(final Configuration configuration, this.programClassPool = programClassPool; this.libraryClassPool = libraryClassPool; this.lambdaGroupVisitor = lambdaGroupVisitor; + this.mergedLambdaVisitor = mergedLambdaVisitor; this.notMergedLambdaVisitor = notMergedLambdaVisitor; this.extraDataEntryNameMap = extraDataEntryNameMap; } @@ -76,6 +79,7 @@ public void visitClassPool(ClassPool lambdaClassPool) this.programClassPool, this.libraryClassPool, this.extraDataEntryNameMap, + this.mergedLambdaVisitor, this.notMergedLambdaVisitor); // visit each lambda of this package to add their implementations to the lambda group diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index bdcce1f2c..da1184458 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -33,6 +33,7 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { private final Configuration configuration; private final ClassPool programClassPool; private final ClassPool libraryClassPool; + private final ClassVisitor mergedLambdaVisitor; private final ClassVisitor notMergedLambdaVisitor; private final Map invokeMethodBuilders; private final InterfaceAdder interfaceAdder; @@ -46,12 +47,13 @@ public class KotlinLambdaGroupBuilder implements ClassVisitor { * @param programClassPool a program class pool containing classes that can be referenced by the new lambda group * @param libraryClassPool a library class pool containing classes that can be referenced by the new lambda group */ - public KotlinLambdaGroupBuilder(final String lambdaGroupName, - final Configuration configuration, - final ClassPool programClassPool, - final ClassPool libraryClassPool, + public KotlinLambdaGroupBuilder(final String lambdaGroupName, + final Configuration configuration, + final ClassPool programClassPool, + final ClassPool libraryClassPool, final ExtraDataEntryNameMap extraDataEntryNameMap, - final ClassVisitor notMergedLambdaVisitor) + final ClassVisitor mergedLambdaVisitor, + final ClassVisitor notMergedLambdaVisitor) { this.classBuilder = getNewLambdaGroupClassBuilder(lambdaGroupName, programClassPool, @@ -62,6 +64,7 @@ public KotlinLambdaGroupBuilder(final String lambdaGroupName, this.invokeMethodBuilders = new HashMap<>(); this.interfaceAdder = new InterfaceAdder(this.classBuilder.getProgramClass()); this.extraDataEntryNameMap = extraDataEntryNameMap; + this.mergedLambdaVisitor = mergedLambdaVisitor; this.notMergedLambdaVisitor = notMergedLambdaVisitor; this.initUpdater = new KotlinLambdaGroupInitUpdater(programClassPool, libraryClassPool); initialiseLambdaGroup(); @@ -152,7 +155,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) new ClassConstantToClassVisitor( new ClassNameFilter(lambdaClass.getName(), (ClassVisitor)null, - (ClassVisitor)this) + this) ), // don't revisit the current lambda null))); @@ -205,6 +208,7 @@ private void mergeLambdaClass(ProgramClass lambdaClass) lambdaClass.getSuperClass().accept(subclassRemover); lambdaClass.interfaceConstantsAccept(new ClassConstantToClassVisitor( subclassRemover)); + lambdaClass.accept(this.mergedLambdaVisitor); } private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 2880ff656..38b795033 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -57,8 +57,8 @@ public void execute(AppView appView) throws Exception else { // A class pool where the applicable lambda's will be stored - ClassPool lambdaClassPool = new ClassPool(); - ClassPool newProgramClassPool = new ClassPool(); + ClassPool lambdaClassPool = new ClassPool(); + ClassPool newProgramClassPool = new ClassPool(); ClassPoolFiller newProgramClassPoolFiller = new ClassPoolFiller(newProgramClassPool); // find all lambda classes with an empty closure @@ -95,6 +95,7 @@ public void execute(AppView appView) throws Exception new MultiClassVisitor( new ClassPoolFiller(lambdaGroupClassPool), newProgramClassPoolFiller), + mergedLambdaClassCounter, new MultiClassVisitor( new ClassPoolFiller(notMergedLambdaClassPool), newProgramClassPoolFiller), @@ -105,7 +106,7 @@ public void execute(AppView appView) throws Exception PrintWriter out = new PrintWriter(System.out, true); if (configuration.printLambdaGroupMapping != null) { - logger.info("Printing lambda group mapping to [{}]...", + logger.trace("Printing lambda group mapping to [{}]...", PrintWriterUtil.fileName(configuration.printLambdaGroupMapping)); PrintWriter mappingWriter = @@ -133,15 +134,13 @@ public void execute(AppView appView) throws Exception // remove the unused helper methods from the lambda groups shrinkLambdaGroups(newProgramClassPool, appView.libraryClassPool, lambdaGroupClassPool); - logger.info("Considered {} lambda classes for merging", lambdaClassPool.size()); - logger.info("of which {} lambda classes were not merged.", notMergedLambdaClassPool.size()); - logger.info("{} lambda group(s) created.", lambdaGroupClassPool.size()); - logger.info("#lambda groups/#merged lambda classes ratio = {}/{} = {}%", - lambdaGroupClassPool.size(), - lambdaClassPool.size() - notMergedLambdaClassPool.size(), - 100 * lambdaGroupClassPool.size() / (lambdaClassPool.size() - notMergedLambdaClassPool.size())); - logger.info("Size of original program class pool: {}", appView.programClassPool.size()); - logger.info("Size of new program class pool: {}", newProgramClassPool.size()); + logger.info("Number of merged lambda classes: {}", mergedLambdaClassCounter.getCount()); + + logger.trace("Considered {} lambda classes for merging", lambdaClassPool.size()); + logger.trace("of which {} lambda classes were not merged.", notMergedLambdaClassPool.size()); + logger.trace("{} lambda group(s) created.", lambdaGroupClassPool.size()); + logger.trace("Size of original program class pool: {}", appView.programClassPool.size()); + logger.trace("Size of new program class pool: {}", newProgramClassPool.size()); appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); From 492d73b927ce159e63dd66e9bb5465f75d3e94c2 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:16:49 +0200 Subject: [PATCH 189/195] Remove ClassCastException handling of visitCodeAttribute in init updater --- .../kotlin/KotlinLambdaGroupInitUpdater.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index f38d33da0..3f3b5867c 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -48,17 +48,7 @@ private void updateInitMethodDescriptor(ProgramClass programClass, ProgramMethod public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { // This attribute visitor should only be used for program classes. - try - { - visitCodeAttribute((ProgramClass) clazz, (ProgramMethod) method, codeAttribute); - } - catch (ClassCastException exception) - { - logger.error("{} is incorrectly used to visit non-program class / method {} / {}", - this.getClass().getName(), ClassUtil.externalClassName(clazz.getName()), - ClassUtil.externalFullMethodDescription(clazz.getName(), - method.getAccessFlags(), method.getName(clazz), method.getDescriptor(clazz))); - } + visitCodeAttribute((ProgramClass) clazz, (ProgramMethod) method, codeAttribute); } public void visitCodeAttribute(ProgramClass programClass, ProgramMethod programMethod, CodeAttribute codeAttribute) From 210a0a561490800b1b27fb631d3091869f231ce1 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:25:37 +0200 Subject: [PATCH 190/195] Replace `warn'` logging with `trace` for cases that users cannot handle --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 38b795033..079ffe616 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -328,7 +328,7 @@ private static boolean lambdaClassHasNoAccessibleStaticMethods(ProgramClass lamb @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - logger.warn("Lambda class {} contains a static method that cannot be merged: {}", + logger.trace("Lambda class {} contains a static method that cannot be merged: {}", ClassUtil.externalClassName(lambdaClass.getName()), ClassUtil.externalFullMethodDescription(lambdaClass.getName(), programMethod.getAccessFlags(), @@ -357,7 +357,7 @@ private static boolean lambdaClassHasExactlyOneInitConstructor(ProgramClass lamb initMethodCounter)); if (initMethodCounter.getCount() != 1) { - logger.warn("Lambda class {} has {} constructors.", + logger.trace("Lambda class {} has {} constructors.", ClassUtil.externalClassName(lambdaClass.getName()), initMethodCounter.getCount()); } return initMethodCounter.getCount() == 1; @@ -391,7 +391,7 @@ private static boolean nonINSTANCEFieldsAreNotReferencedFromSamePackage(ProgramC new ConstantVisitor() { @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { - logger.warn("{} references non-INSTANCE field {} of lambda class {}.", + logger.trace("{} references non-INSTANCE field {} of lambda class {}.", ClassUtil.externalClassName(clazz.getName()), ClassUtil.externalFullFieldDescription(fieldrefConstant.referencedField.getAccessFlags(), fieldrefConstant.getName(clazz), From 16b41381729e54804ed5dea25d10778a2864960f Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 18:40:36 +0200 Subject: [PATCH 191/195] Change warning to trace for absence of Kotlin Lambda class --- .../java/proguard/optimize/kotlin/KotlinLambdaMerger.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index 079ffe616..e45b6c99e 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -48,10 +48,10 @@ public void execute(AppView appView) throws Exception appView.programClassPool.classesAccept(new ClassCleaner()); appView.libraryClassPool.classesAccept(new ClassCleaner()); - // get the Lambda class and the Function0 interface + // Get the Lambda class Clazz kotlinLambdaClass = getKotlinLambdaClass(appView.programClassPool, appView.libraryClassPool); if (kotlinLambdaClass == null) { - logger.warn("The Kotlin class '{}' is not found, but it is needed to perform lambda merging.", + logger.trace("The Kotlin class '{}' is not found, so merging of Kotlin lambda classes is skipped.", NAME_KOTLIN_LAMBDA); } else From 734cce855f487d07ce9d50e7e0f12cbf3f883781 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 19:02:25 +0200 Subject: [PATCH 192/195] Null check for merged lambda visitor --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index da1184458..93720af86 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -208,7 +208,10 @@ private void mergeLambdaClass(ProgramClass lambdaClass) lambdaClass.getSuperClass().accept(subclassRemover); lambdaClass.interfaceConstantsAccept(new ClassConstantToClassVisitor( subclassRemover)); - lambdaClass.accept(this.mergedLambdaVisitor); + if (this.mergedLambdaVisitor != null) + { + lambdaClass.accept(this.mergedLambdaVisitor); + } } private void canonicalizeLambdaClassFields(ProgramClass lambdaClass) From 56a90c1f17bb5134f74e57a57b986349e7636496 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Wed, 29 Jun 2022 19:02:40 +0200 Subject: [PATCH 193/195] Update LambdaGroupBuilderTest to use updated constructor --- .../proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt index f618f98cd..a5c421e6d 100644 --- a/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt +++ b/base/src/test/kotlin/proguard/optimize/kotlin/KotlinLambdaGroupBuilderTest.kt @@ -41,6 +41,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ val lambdaGroupName = "LambdaGroup" val configuration = Configuration() val entryMapper = ExtraDataEntryNameMap() + val mergedLambdaVisitor = null val notMergedLambdaVisitor = null val builder = KotlinLambdaGroupBuilder( lambdaGroupName, @@ -48,6 +49,7 @@ class KotlinLambdaGroupBuilderTest : FreeSpec({ programClassPool, libraryClassPool, entryMapper, + mergedLambdaVisitor, notMergedLambdaVisitor ) val lambdaClassName = "TestKt\$main\$lambda1\$1" From 9666d9735fdb26e8cdc17a64c591012ff6219899 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 10 Jul 2022 21:50:45 +0200 Subject: [PATCH 194/195] All new classes moved to package proguard.optimize.kotlin --- .../ModifiedAllInnerClassesInfoVisitor.java | 25 ---------- .../ClassConstantReferenceUpdater.java | 28 ----------- .../kotlin/KotlinLambdaClassMerger.java | 20 ++++++++ .../KotlinLambdaEnclosingMethodUpdater.java | 21 ++++++++ .../kotlin/KotlinLambdaGroupBuilder.java | 24 ++++++++++ .../kotlin/KotlinLambdaGroupInitUpdater.java | 20 ++++++++ .../KotlinLambdaGroupInvokeMethodBuilder.java | 20 ++++++++ .../optimize/kotlin/KotlinLambdaMerger.java | 29 +++++++++++ .../kotlin/LambdaGroupMappingPrinter.java | 29 +++++++++++ .../InnerClassInfoClassConstantVisitor.java | 23 ++++++++- .../ModifiedAllInnerClassesInfoVisitor.java | 47 ++++++++++++++++++ .../ClassConstantReferenceUpdater.java | 48 +++++++++++++++++++ .../kotlin}/visitor/FieldCopier.java | 23 ++++++++- .../kotlin}/visitor/FieldReferenceFinder.java | 22 ++++++++- .../kotlin}/visitor/FieldRenamer.java | 23 ++++++++- .../kotlin}/visitor/MethodCopier.java | 23 ++++++++- .../visitor/MethodReferenceFinder.java | 22 ++++++++- .../kotlin}/visitor/PackageGrouper.java | 31 ++++++++++-- .../classfile/visitor/MethodCopierTest.kt | 20 ++++++-- .../classfile/visitor/PackageGrouperTest.kt | 1 + 20 files changed, 433 insertions(+), 66 deletions(-) delete mode 100644 base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java delete mode 100644 base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java rename base/src/main/java/proguard/{classfile => optimize/kotlin}/attribute/visitor/InnerClassInfoClassConstantVisitor.java (53%) create mode 100644 base/src/main/java/proguard/optimize/kotlin/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java create mode 100644 base/src/main/java/proguard/optimize/kotlin/visitor/ClassConstantReferenceUpdater.java rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/FieldCopier.java (75%) rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/FieldReferenceFinder.java (63%) rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/FieldRenamer.java (73%) rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/MethodCopier.java (87%) rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/MethodReferenceFinder.java (51%) rename base/src/main/java/proguard/{classfile => optimize/kotlin}/visitor/PackageGrouper.java (60%) diff --git a/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java b/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java deleted file mode 100644 index 310241800..000000000 --- a/base/src/main/java/proguard/classfile/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package proguard.classfile.attribute.visitor; - -import proguard.classfile.Clazz; -import proguard.classfile.attribute.InnerClassesAttribute; - -/** - * This {@link AllInnerClassesInfoVisitor} revisits each {@link InnerClassesAttribute} everytime its amount of - * referenced classes has been modified in the meantime. - */ -public class ModifiedAllInnerClassesInfoVisitor extends AllInnerClassesInfoVisitor { - - public ModifiedAllInnerClassesInfoVisitor(InnerClassesInfoVisitor innerClassesInfoVisitor) { - super(innerClassesInfoVisitor); - } - - public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) - { - int originalClassesCount = -1; - while (originalClassesCount != innerClassesAttribute.u2classesCount) - { - originalClassesCount = innerClassesAttribute.u2classesCount; - super.visitInnerClassesAttribute(clazz, innerClassesAttribute); - } - } -} diff --git a/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java b/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java deleted file mode 100644 index e50fad1b6..000000000 --- a/base/src/main/java/proguard/classfile/visitor/ClassConstantReferenceUpdater.java +++ /dev/null @@ -1,28 +0,0 @@ -package proguard.classfile.visitor; - -import proguard.classfile.Clazz; -import proguard.classfile.constant.ClassConstant; -import proguard.classfile.constant.Constant; -import proguard.classfile.constant.visitor.ConstantVisitor; - -public class ClassConstantReferenceUpdater implements ConstantVisitor -{ - private final Clazz originalClass; - private final Clazz replacingClass; - public ClassConstantReferenceUpdater(Clazz originalClass, Clazz replacingClass) - { - this.originalClass = originalClass; - this.replacingClass = replacingClass; - } - - @Override - public void visitAnyConstant(Clazz clazz, Constant constant) {} - - @Override - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { - if (classConstant.referencedClass == originalClass) - { - classConstant.referencedClass = replacingClass; - } - } -} diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java index 6e2775454..afa690ea8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaClassMerger.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java index c9219a968..979cd13d3 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaEnclosingMethodUpdater.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import org.apache.logging.log4j.LogManager; @@ -11,6 +31,7 @@ import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; +import proguard.optimize.kotlin.visitor.ClassConstantReferenceUpdater; import proguard.optimize.peephole.RetargetedInnerClassAttributeRemover; public class KotlinLambdaEnclosingMethodUpdater diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java index 93720af86..35b546f88 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupBuilder.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import org.apache.logging.log4j.LogManager; @@ -12,6 +32,10 @@ import proguard.classfile.visitor.*; import proguard.io.ExtraDataEntryNameMap; import proguard.optimize.info.*; +import proguard.optimize.kotlin.attribute.visitor.InnerClassInfoClassConstantVisitor; +import proguard.optimize.kotlin.attribute.visitor.ModifiedAllInnerClassesInfoVisitor; +import proguard.optimize.kotlin.visitor.FieldRenamer; +import proguard.optimize.kotlin.visitor.MethodCopier; import proguard.optimize.peephole.SameClassMethodInliner; import proguard.preverify.CodePreverifier; import proguard.util.ProcessingFlags; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java index 3f3b5867c..13fe209c8 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInitUpdater.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 66749badd..964e41dc6 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import proguard.classfile.*; diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java index e45b6c99e..97e7d30aa 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaMerger.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import org.apache.logging.log4j.LogManager; @@ -14,12 +34,21 @@ import proguard.classfile.util.*; import proguard.classfile.visitor.*; import proguard.optimize.info.*; +import proguard.optimize.kotlin.visitor.FieldReferenceFinder; +import proguard.optimize.kotlin.visitor.MethodReferenceFinder; +import proguard.optimize.kotlin.visitor.PackageGrouper; import proguard.optimize.peephole.*; import proguard.pass.Pass; import proguard.shrink.*; import proguard.util.*; import java.io.PrintWriter; +/** + * This {@link Pass} provides the Kotlin Lambda Merging optimisation. + * It merges the implementations of eligible Kotlin Lambda classes into + * lambda groups per package. + * @author Joren Van Hecke + */ public class KotlinLambdaMerger implements Pass { diff --git a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java index 7240653e3..c06c947f5 100644 --- a/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java +++ b/base/src/main/java/proguard/optimize/kotlin/LambdaGroupMappingPrinter.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ package proguard.optimize.kotlin; import proguard.classfile.Clazz; @@ -8,6 +28,15 @@ import java.io.PrintWriter; +/** + * This {@link ClassVisitor} prints out the merged lambda classes + * and the lambda groups into which they were merged, including their + * original arity and the class id in the resulting lambda group. + * + * @see proguard.optimize.kotlin.KotlinLambdaMerger + * + * @author Joren Van Hecke + */ public class LambdaGroupMappingPrinter implements ClassVisitor { private final PrintWriter pw; diff --git a/base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java b/base/src/main/java/proguard/optimize/kotlin/attribute/visitor/InnerClassInfoClassConstantVisitor.java similarity index 53% rename from base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java rename to base/src/main/java/proguard/optimize/kotlin/attribute/visitor/InnerClassInfoClassConstantVisitor.java index b3020b62d..4fbc35cd7 100644 --- a/base/src/main/java/proguard/classfile/attribute/visitor/InnerClassInfoClassConstantVisitor.java +++ b/base/src/main/java/proguard/optimize/kotlin/attribute/visitor/InnerClassInfoClassConstantVisitor.java @@ -1,8 +1,29 @@ -package proguard.classfile.attribute.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.attribute.visitor; import proguard.classfile.Clazz; import proguard.classfile.ProgramClass; import proguard.classfile.attribute.InnerClassesInfo; +import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; import proguard.classfile.constant.visitor.ConstantVisitor; public class InnerClassInfoClassConstantVisitor implements InnerClassesInfoVisitor { diff --git a/base/src/main/java/proguard/optimize/kotlin/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java b/base/src/main/java/proguard/optimize/kotlin/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java new file mode 100644 index 000000000..199d9b653 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/attribute/visitor/ModifiedAllInnerClassesInfoVisitor.java @@ -0,0 +1,47 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.attribute.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.attribute.InnerClassesAttribute; +import proguard.classfile.attribute.visitor.AllInnerClassesInfoVisitor; +import proguard.classfile.attribute.visitor.InnerClassesInfoVisitor; + +/** + * This {@link AllInnerClassesInfoVisitor} revisits each {@link InnerClassesAttribute} everytime its amount of + * referenced classes has been modified in the meantime. + */ +public class ModifiedAllInnerClassesInfoVisitor extends AllInnerClassesInfoVisitor { + + public ModifiedAllInnerClassesInfoVisitor(InnerClassesInfoVisitor innerClassesInfoVisitor) { + super(innerClassesInfoVisitor); + } + + public void visitInnerClassesAttribute(Clazz clazz, InnerClassesAttribute innerClassesAttribute) + { + int originalClassesCount = -1; + while (originalClassesCount != innerClassesAttribute.u2classesCount) + { + originalClassesCount = innerClassesAttribute.u2classesCount; + super.visitInnerClassesAttribute(clazz, innerClassesAttribute); + } + } +} diff --git a/base/src/main/java/proguard/optimize/kotlin/visitor/ClassConstantReferenceUpdater.java b/base/src/main/java/proguard/optimize/kotlin/visitor/ClassConstantReferenceUpdater.java new file mode 100644 index 000000000..639010ca7 --- /dev/null +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/ClassConstantReferenceUpdater.java @@ -0,0 +1,48 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; + +import proguard.classfile.Clazz; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.visitor.ConstantVisitor; + +public class ClassConstantReferenceUpdater implements ConstantVisitor +{ + private final Clazz originalClass; + private final Clazz replacingClass; + public ClassConstantReferenceUpdater(Clazz originalClass, Clazz replacingClass) + { + this.originalClass = originalClass; + this.replacingClass = replacingClass; + } + + @Override + public void visitAnyConstant(Clazz clazz, Constant constant) {} + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + if (classConstant.referencedClass == originalClass) + { + classConstant.referencedClass = replacingClass; + } + } +} diff --git a/base/src/main/java/proguard/classfile/visitor/FieldCopier.java b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldCopier.java similarity index 75% rename from base/src/main/java/proguard/classfile/visitor/FieldCopier.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/FieldCopier.java index 5d63627a1..53a32e6ff 100644 --- a/base/src/main/java/proguard/classfile/visitor/FieldCopier.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldCopier.java @@ -1,4 +1,24 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -7,6 +27,7 @@ import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.ClassEditor; +import proguard.classfile.visitor.MemberVisitor; public class FieldCopier implements MemberVisitor, ConstantVisitor { diff --git a/base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldReferenceFinder.java similarity index 63% rename from base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/FieldReferenceFinder.java index 0ec8d309c..22c8ed7fe 100644 --- a/base/src/main/java/proguard/classfile/visitor/FieldReferenceFinder.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldReferenceFinder.java @@ -1,4 +1,24 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import proguard.classfile.Clazz; import proguard.classfile.constant.Constant; diff --git a/base/src/main/java/proguard/classfile/visitor/FieldRenamer.java b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldRenamer.java similarity index 73% rename from base/src/main/java/proguard/classfile/visitor/FieldRenamer.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/FieldRenamer.java index b041862b3..ac4986acd 100644 --- a/base/src/main/java/proguard/classfile/visitor/FieldRenamer.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/FieldRenamer.java @@ -1,4 +1,24 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import proguard.classfile.Clazz; import proguard.classfile.Field; @@ -7,6 +27,7 @@ import proguard.classfile.constant.Constant; import proguard.classfile.constant.Utf8Constant; import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.visitor.MemberVisitor; import java.util.HashMap; import java.util.Map; diff --git a/base/src/main/java/proguard/classfile/visitor/MethodCopier.java b/base/src/main/java/proguard/optimize/kotlin/visitor/MethodCopier.java similarity index 87% rename from base/src/main/java/proguard/classfile/visitor/MethodCopier.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/MethodCopier.java index 550abfecf..c17a17e37 100644 --- a/base/src/main/java/proguard/classfile/visitor/MethodCopier.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/MethodCopier.java @@ -1,4 +1,24 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,6 +34,7 @@ import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.MemberVisitor; import java.util.Objects; diff --git a/base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java b/base/src/main/java/proguard/optimize/kotlin/visitor/MethodReferenceFinder.java similarity index 51% rename from base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/MethodReferenceFinder.java index 2f9c82fb6..6a50de9e9 100644 --- a/base/src/main/java/proguard/classfile/visitor/MethodReferenceFinder.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/MethodReferenceFinder.java @@ -1,4 +1,24 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/classfile/visitor/PackageGrouper.java b/base/src/main/java/proguard/optimize/kotlin/visitor/PackageGrouper.java similarity index 60% rename from base/src/main/java/proguard/classfile/visitor/PackageGrouper.java rename to base/src/main/java/proguard/optimize/kotlin/visitor/PackageGrouper.java index b3ab94fca..171672f3b 100644 --- a/base/src/main/java/proguard/classfile/visitor/PackageGrouper.java +++ b/base/src/main/java/proguard/optimize/kotlin/visitor/PackageGrouper.java @@ -1,16 +1,41 @@ -package proguard.classfile.visitor; +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2022 Guardsquare NV + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ +package proguard.optimize.kotlin.visitor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassPoolVisitor; +import proguard.classfile.visitor.ClassVisitor; import java.util.HashMap; import java.util.Map; /** - * This class + * This {@link ClassVisitor} groups the visited classes per package, + * after which the classes can be visited per package. + * @author Joren Van Hecke + * @see proguard.optimize.kotlin.KotlinLambdaMerger */ public class PackageGrouper implements ClassVisitor { @@ -21,8 +46,6 @@ public class PackageGrouper implements ClassVisitor { public void visitAnyClass(Clazz clazz) { String classPackageName = ClassUtil.internalPackageName(clazz.getName()); - // or - // String classPackageName = ClassUtil.internalPackageName(clazz.getName()); if (!packageClassPools.containsKey(classPackageName)) { logger.info("New package found: {}", diff --git a/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt b/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt index e96e436e5..82160ec9f 100644 --- a/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt +++ b/base/src/test/kotlin/proguard/classfile/visitor/MethodCopierTest.kt @@ -11,6 +11,7 @@ import proguard.classfile.editor.ClassBuilder import proguard.classfile.editor.InstructionSequenceBuilder import proguard.classfile.instruction.visitor.AllInstructionVisitor import proguard.classfile.util.InstructionSequenceMatcher +import proguard.optimize.kotlin.visitor.MethodCopier import testutils.MatchDetector class MethodCopierTest : FreeSpec({ @@ -48,7 +49,11 @@ class MethodCopierTest : FreeSpec({ ClassConstants.NAME_JAVA_LANG_OBJECT ).programClass "When the method is copied to the target class" - { - val methodCopier = MethodCopier(targetClass, ClassConstants.METHOD_NAME_INIT, methodAccessFlags) + val methodCopier = MethodCopier( + targetClass, + ClassConstants.METHOD_NAME_INIT, + methodAccessFlags + ) method.accept(testClass, methodCopier) "Then the target class contains a method with the correct name and descriptor" { targetClass.findMethod(methodName, methodDescriptor) shouldNotBe null @@ -86,7 +91,12 @@ class MethodCopierTest : FreeSpec({ "When the method is copied to the target class with a new descriptor" - { val newDescriptor = "(I)V" - val methodCopier = MethodCopier(targetClass, methodName, newDescriptor, methodAccessFlags) + val methodCopier = MethodCopier( + targetClass, + methodName, + newDescriptor, + methodAccessFlags + ) method.accept(testClass, methodCopier) "Then the target class contains a method with the correct name and descriptor" { targetClass.findMethod(methodName, newDescriptor) shouldNotBe null @@ -95,7 +105,11 @@ class MethodCopierTest : FreeSpec({ "When the method is copied to the target class with a new name prefix" - { val newNamePrefix = "copiedMethod" - val methodCopier = MethodCopier(targetClass, newNamePrefix, methodAccessFlags) + val methodCopier = MethodCopier( + targetClass, + newNamePrefix, + methodAccessFlags + ) method.accept(testClass, methodCopier) "Then the target class contains a method with the correct name and descriptor" { targetClass.findMethod(newNamePrefix, methodDescriptor) shouldNotBe null diff --git a/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt b/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt index 7b659b761..9cf25b967 100644 --- a/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt +++ b/base/src/test/kotlin/proguard/classfile/visitor/PackageGrouperTest.kt @@ -26,6 +26,7 @@ import io.kotest.matchers.collections.shouldBeIn import io.kotest.matchers.shouldBe import proguard.classfile.ClassPool import proguard.classfile.util.ClassUtil +import proguard.optimize.kotlin.visitor.PackageGrouper import testutils.ClassPoolBuilder import testutils.KotlinSource From 8cc582f196b4e0e327033872ca24514940cc5d52 Mon Sep 17 00:00:00 2001 From: Joren Van Hecke Date: Sun, 10 Jul 2022 22:07:54 +0200 Subject: [PATCH 195/195] Solve TODO of building empty invoke method code --- .../optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java index 964e41dc6..8089bdd45 100644 --- a/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java +++ b/base/src/main/java/proguard/optimize/kotlin/KotlinLambdaGroupInvokeMethodBuilder.java @@ -122,10 +122,6 @@ public ClassBuilder.CodeBuilder buildCodeBuilder() { return composer -> { int cases = getCaseIndexCounter(); - - // TODO: decide what to do when no cases have been added - // - create an implementation that simply returns Unit - // - or throw an exception if (cases == 0) { composer.getstatic(KotlinConstants.NAME_KOTLIN_UNIT, KotlinConstants.KOTLIN_OBJECT_INSTANCE_FIELD_NAME, KotlinConstants.TYPE_KOTLIN_UNIT);