From 274fd197c47998a9cdb788261f825c14b3544899 Mon Sep 17 00:00:00 2001 From: timothy Date: Thu, 3 Aug 2023 11:40:53 +0200 Subject: [PATCH 01/72] Added lambda inlining Co-authored-by: Maarten Steevens Co-authored-by: Timothy Geerkens --- .../src/main/java/proguard/Configuration.java | 5 + base/src/main/java/proguard/ProGuard.java | 11 + .../optimize/inline/BaseLambdaInliner.java | 301 ++++++++ .../optimize/inline/CallReplacer.java | 48 ++ .../inline/CannotInlineException.java | 7 + .../proguard/optimize/inline/CastRemover.java | 75 ++ .../optimize/inline/DescriptorModifier.java | 50 ++ .../optimize/inline/InstructionAtOffset.java | 45 ++ .../inline/LambdaImplementationVisitor.java | 66 ++ .../optimize/inline/LambdaInliner.java | 103 +++ .../optimize/inline/LambdaUsageFinder.java | 172 +++++ .../optimize/inline/LocalUsageRemover.java | 56 ++ .../inline/MethodInstructionVisitor.java | 41 ++ .../optimize/inline/MethodUsageFinder.java | 66 ++ .../java/proguard/optimize/inline/Node.java | 33 + .../optimize/inline/NullCheckRemover.java | 75 ++ .../optimize/inline/RecursiveInliner.java | 141 ++++ .../optimize/inline/RefMethodFinder.java | 32 + .../java/proguard/optimize/inline/Util.java | 167 +++++ .../inline/lambda_locator/Lambda.java | 66 ++ .../inline/lambda_locator/LambdaLocator.java | 111 +++ .../optimize/inline/lambda_locator/Util.java | 89 +++ .../proguard/lambdaInline/AdvancedTest.kt | 283 +++++++ .../lambdaInline/FieldInliningTest.kt | 53 ++ .../proguard/lambdaInline/LambdaReturnTest.kt | 28 + .../lambdaInline/OneLambdaInArgsTest.kt | 691 ++++++++++++++++++ .../kotlin/proguard/lambdaInline/TestUtil.kt | 419 +++++++++++ .../lambdaInline/ThreeLambdasInArgsTest.kt | 105 +++ .../lambdaInline/TwoLambdasInArgsTest.kt | 171 +++++ .../kotlin/proguard/lambdaInline/TypeTest.kt | 315 ++++++++ gradle.properties | 2 +- 31 files changed, 3826 insertions(+), 1 deletion(-) create mode 100644 base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java create mode 100644 base/src/main/java/proguard/optimize/inline/CallReplacer.java create mode 100644 base/src/main/java/proguard/optimize/inline/CannotInlineException.java create mode 100644 base/src/main/java/proguard/optimize/inline/CastRemover.java create mode 100644 base/src/main/java/proguard/optimize/inline/DescriptorModifier.java create mode 100644 base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java create mode 100644 base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java create mode 100644 base/src/main/java/proguard/optimize/inline/LambdaInliner.java create mode 100644 base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java create mode 100644 base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java create mode 100644 base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java create mode 100644 base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java create mode 100644 base/src/main/java/proguard/optimize/inline/Node.java create mode 100644 base/src/main/java/proguard/optimize/inline/NullCheckRemover.java create mode 100644 base/src/main/java/proguard/optimize/inline/RecursiveInliner.java create mode 100644 base/src/main/java/proguard/optimize/inline/RefMethodFinder.java create mode 100644 base/src/main/java/proguard/optimize/inline/Util.java create mode 100644 base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java create mode 100644 base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java create mode 100644 base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java create mode 100644 base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt create mode 100644 base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 21652abdd..2787bcd17 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 lambdas should be inlined. + */ + public boolean lambdaInlining = true; + /////////////////////////////////////////////////////////////////////////// // Obfuscation options. /////////////////////////////////////////////////////////////////////////// diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 7b6f9b91c..4470e47a7 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -38,6 +38,7 @@ import proguard.optimize.LineNumberTrimmer; import proguard.optimize.Optimizer; import proguard.optimize.gson.GsonOptimizer; +import proguard.optimize.inline.LambdaInliner; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.pass.PassRunner; import proguard.preverify.PreverificationClearer; @@ -195,6 +196,11 @@ public void execute() throws Exception new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); + if (configuration.lambdaInlining) + { + inlineLambdas(); + } + if (configuration.optimize && filter.matches(Optimizer.LIBRARY_GSON)) { @@ -462,6 +468,11 @@ private void optimizeGson() throws Exception passRunner.run(new GsonOptimizer(configuration), appView); } + private void inlineLambdas() throws Exception + { + LambdaInliner lambdaInliner = new LambdaInliner(); + passRunner.run(lambdaInliner, appView); + } /** * Performs the optimization step. diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java new file mode 100644 index 000000000..3c01df209 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -0,0 +1,301 @@ +package proguard.optimize.inline; + +import proguard.AppView; +import proguard.classfile.*; +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.constant.InterfaceMethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.ClassEditor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.editor.MethodCopier; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; +import proguard.classfile.instruction.VariableInstruction; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.ClassReferenceInitializer; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.util.InternalTypeEnumeration; +import proguard.classfile.visitor.MemberVisitor; +import proguard.evaluation.PartialEvaluator; +import proguard.evaluation.TracedStack; +import proguard.optimize.inline.lambda_locator.Lambda; + +import java.util.ArrayList; +import java.util.List; + +import static java.lang.Math.max; + +public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { + private final Clazz consumingClass; + private final Method consumingMethod; + private final AppView appView; + private final Lambda lambda; + private final CodeAttributeEditor codeAttributeEditor; + private final boolean isStatic; + private final int calledLambdaIndex; + private final int sizeAdjustedLamdaIndex; + private final PartialEvaluator partialEvaluator; + private Clazz interfaceClass; + private Clazz lambdaClass; + private Method lambdaInvokeMethod; + private String bridgeDescriptor; + private Method inlinedLambdaMethod; + private Method staticInvokeMethod; + private InterfaceMethodrefConstant referencedInterfaceConstant; + private final List invokeMethodCallOffsets; + + public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, Lambda lambda) { + this.consumingClass = consumingClass; + this.consumingMethod = consumingMethod; + this.appView = appView; + this.lambda = lambda; + this.codeAttributeEditor = new CodeAttributeEditor(); + this.isStatic = (consumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + this.calledLambdaIndex = Util.findFirstLambdaParameter(consumingMethod.getDescriptor(consumingClass), isStatic); + this.sizeAdjustedLamdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, calledLambdaIndex); + this.partialEvaluator = new PartialEvaluator(); + this.invokeMethodCallOffsets = new ArrayList<>(); + } + + /** + * Idea: Inline a lambda by just giving it a consuming method and class in which it has to inline the particular + * lambda, a reference to the newly created function with the inlined lambda is returned, the caller can then + * replace the call instruction with a call to this newly created function. This way we don't need to think about + * how it is called exactly, it can be used as a higher abstraction in other places. + */ + public Method inline() { + if (consumingMethod instanceof LibraryMethod) + return null; + + ConstantInstruction c = lambda.constantInstruction(); + lambda.clazz().constantPoolEntryAccept(c.constantIndex, new LambdaImplementationVisitor((programClass, programMethod, interfaceClass, bridgeDescriptor) -> { + this.interfaceClass = interfaceClass; + this.lambdaClass = programClass; + this.lambdaInvokeMethod = programMethod; + this.bridgeDescriptor = bridgeDescriptor; + + // First we copy the method from the anonymous lambda class into the class where it is used. + MethodCopier copier = new MethodCopier(programClass, programMethod, BaseLambdaInliner.this); + consumingClass.accept(copier); + })); + + return inlinedLambdaMethod; + } + + @Override + public void visitAnyMember(Clazz clazz, Member member) { + ProgramMethod method = (ProgramMethod) member; + method.u2accessFlags = (method.getAccessFlags() & ~AccessConstants.BRIDGE & ~AccessConstants.SYNTHETIC & ~AccessConstants.PRIVATE) | AccessConstants.STATIC; + + // Copy the invoke method + String invokeMethodDescriptor = method.getDescriptor(consumingClass); + DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); + staticInvokeMethod = descriptorModifier.modify(method, + desc -> { + // The method becomes static + String d = desc.replace("(", "(Ljava/lang/Object;"); + // Change return type if it has an effect on the stack size + d = d.replace(")Ljava/lang/Double;", ")D"); + d = d.replace(")Ljava/lang/Float;", ")F"); + return d.replace(")Ljava/lang/Long;", ")J"); + } + , true); + + ProgramMethod copiedConsumingMethod = descriptorModifier.modify((ProgramMethod) consumingMethod, desc -> desc); + + // Don't inline if the lamdba is passed to another method + try { + copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(appView, consumingMethod, lambda)); + } catch(CannotInlineException cie) { + ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); + classEditor.removeMethod(copiedConsumingMethod); + classEditor.removeMethod(staticInvokeMethod); + return; + } + + referencedInterfaceConstant = null; + invokeMethodCallOffsets.clear(); + + // Replace invokeinterface call to invoke method with invokestatic to staticInvokeMethod + codeAttributeEditor.reset(getMethodLength(copiedConsumingMethod, consumingClass)); + copiedConsumingMethod.accept(consumingClass, + new AllAttributeVisitor( + new AllInstructionVisitor( + new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); + + // Remove return value's casting from staticInvokeMethod + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + int len = codeAttribute.u4codeLength; + CodeAttributeEditor codeAttributeEditorStaticInvoke = new CodeAttributeEditor(); + codeAttributeEditorStaticInvoke.reset(codeAttribute.u4codeLength); + codeAttribute.instructionsAccept(clazz, method, len - 4, len, new CastRemover(codeAttributeEditorStaticInvoke)); + codeAttributeEditorStaticInvoke.visitCodeAttribute(clazz, method, codeAttribute); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + })); + + if (referencedInterfaceConstant != null) { + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(invokeMethodDescriptor); + int i = 0; + List keepList = new ArrayList<>(); + while(internalTypeEnumeration.hasMoreTypes()) { + if (internalTypeEnumeration.nextType().equals("Ljava/lang/Object;")) { + // Argument i is object, we should keep the cast for this argument + keepList.add(i); + } + i++; + } + int nbrArgs = ClassUtil.internalMethodParameterCount(invokeMethodDescriptor); + + // Remove casting before and after invoke method call + // Uses same codeAttributeEditor as LambdaInvokeReplacer + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + for (int invokeMethodCallOffset : invokeMethodCallOffsets) { + int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); + int endOffset = invokeMethodCallOffset + 8 + 1; + if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode == Instruction.OP_POP) { + endOffset = invokeMethodCallOffset; + } + + codeAttribute.instructionsAccept(consumingClass, method, startOffset, endOffset, + new CastRemover(codeAttributeEditor, keepList) + ); + } + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + })); + } + + // Important for inlining, we need this so that method invocations have non-null referenced methods. + appView.programClassPool.classesAccept( + new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) + ); + + // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. + Util.inlineMethodInClass(consumingClass, staticInvokeMethod); + + // Remove the static invoke method once it has been inlined + ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); + classEditor.removeMethod(staticInvokeMethod); + + // Remove checkNotNullParameter() call because arguments that are lambdas will be removed + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new AllInstructionVisitor(new NullCheckRemover(sizeAdjustedLamdaIndex)))); + + //remove inlined lambda from arguments through the descriptor + Method methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { + List list = new ArrayList<>(); + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(desc); + while(internalTypeEnumeration.hasMoreTypes()) { + list.add(internalTypeEnumeration.nextType()); + } + list.remove(calledLambdaIndex - (isStatic ? 0 : 1)); + + return ClassUtil.internalMethodDescriptorFromInternalTypes(internalTypeEnumeration.returnType(), list); + }, true); + + + // Remove one of the arguments + methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLamdaIndex)); + + // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace + // calls to the old function to calls to this new function. + inlinedLambdaMethod = methodWithoutLambdaParameter; + } + + @Override + public void visitConstantInstruction(Clazz consumingClass, Method consumingMethod, CodeAttribute consumingMethodCodeAttribute, int consumingMethodCallOffset, ConstantInstruction constantInstruction) { + consumingClass.constantPoolEntryAccept(constantInstruction.constantIndex, this); + Method invokeInterfaceMethod = referencedInterfaceConstant.referencedMethod; + Clazz invokeInterfaceClass = referencedInterfaceConstant.referencedClass; + + // Check if this is an invokeinterface call to the lambda class invoke method + if (invokeInterfaceClass.getName().equals(interfaceClass.getName()) && + invokeInterfaceMethod.getName(invokeInterfaceClass).equals(lambdaInvokeMethod.getName(lambdaClass)) && + invokeInterfaceMethod.getDescriptor(invokeInterfaceClass).equals(bridgeDescriptor) + ) { + partialEvaluator.visitCodeAttribute(consumingClass, consumingMethod, consumingMethodCodeAttribute); + TracedStack tracedStack = partialEvaluator.getStackBefore(consumingMethodCallOffset); + + // If we have a lambda with n arguments we have to skip those n arguments and then get the instance of the lambda used for calling invokeinterface. + int lambdaInstanceStackIndex = ClassUtil.internalMethodParameterCount(invokeInterfaceMethod.getDescriptor(invokeInterfaceClass)); + + consumingMethodCodeAttribute.instructionAccept( + consumingClass, + consumingMethod, + tracedStack.getTopActualProducerValue(lambdaInstanceStackIndex).instructionOffsetValue().instructionOffset(0), + new LambdaInvokeUsageReplacer(consumingMethodCallOffset) + ); + } + } + + @Override + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { + referencedInterfaceConstant = interfaceMethodrefConstant; + } + + private class LambdaInvokeUsageReplacer implements InstructionVisitor { + private final int possibleLambdaInvokeCallOffset; + + public LambdaInvokeUsageReplacer(int possibleLambdaInvokeCallOffset) { + this.possibleLambdaInvokeCallOffset = possibleLambdaInvokeCallOffset; + } + + @Override + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { + // Uses same codeAttributeEditor as the casting remover + Instruction tracedInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); + if (tracedInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { + variableInstruction = (VariableInstruction) tracedInstruction; + + // Check if the aload index is the same as the current lambda index in the arguments + if (variableInstruction.variableIndex == sizeAdjustedLamdaIndex) { + // Replace call to lambda invoke method by a static call to the copied static invoke method + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) clazz); + int constantIndex = constantPoolEditor.addMethodrefConstant(clazz, staticInvokeMethod); + + codeAttributeEditor.replaceInstruction(possibleLambdaInvokeCallOffset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, constantIndex)); + if (staticInvokeMethod.getDescriptor(consumingClass).endsWith("V")) { + // Lambda that returns void, so we delete the pop instruction after the invoke because it originally returned the Object type + codeAttributeEditor.deleteInstruction(possibleLambdaInvokeCallOffset + InstructionFactory.create(codeAttribute.code, possibleLambdaInvokeCallOffset).length(possibleLambdaInvokeCallOffset)); + } + + invokeMethodCallOffsets.add(possibleLambdaInvokeCallOffset); + } + } + } + } + + /** + * @param method A Method object from which we'll get the length. + * @param clazz The class in which the method is. + * @return The length of the method. + */ + private int getMethodLength(Method method, Clazz clazz) { + final int[] length = new int[1]; + method.accept(clazz, new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + length[0] = codeAttribute.u4codeLength; + } + })); + return length[0]; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/CallReplacer.java b/base/src/main/java/proguard/optimize/inline/CallReplacer.java new file mode 100644 index 000000000..9b5796d05 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/CallReplacer.java @@ -0,0 +1,48 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.constant.AnyMethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; + +public class CallReplacer implements InstructionVisitor { + private final CodeAttributeEditor codeAttributeEditor; + private final Clazz methodOwnerClazz; + private final Method oldMethod; + private final Method newMethod; + + public CallReplacer(CodeAttributeEditor codeAttributeEditor, Clazz methodOwnerClazz, Method oldMethod, Method newMethod) { + this.codeAttributeEditor = codeAttributeEditor; + this.methodOwnerClazz = methodOwnerClazz; + this.oldMethod = oldMethod; + this.newMethod = newMethod; + } + + @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) { + if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) clazz); + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + @Override + public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { + if (anyMethodrefConstant.referencedMethod != null && anyMethodrefConstant.referencedMethod.equals(oldMethod)) { + int newMethodIndex = constantPoolEditor.addMethodrefConstant(methodOwnerClazz, newMethod); + codeAttributeEditor.reset(codeAttribute.u4codeLength); + codeAttributeEditor.replaceInstruction(offset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, newMethodIndex)); + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + } + }); + } + } +} diff --git a/base/src/main/java/proguard/optimize/inline/CannotInlineException.java b/base/src/main/java/proguard/optimize/inline/CannotInlineException.java new file mode 100644 index 000000000..9701d74dd --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/CannotInlineException.java @@ -0,0 +1,7 @@ +package proguard.optimize.inline; + +public class CannotInlineException extends RuntimeException { + public CannotInlineException(String reason) { + super(reason); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java new file mode 100644 index 000000000..cc246cab2 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -0,0 +1,75 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.constant.AnyMethodrefConstant; +import proguard.classfile.constant.NameAndTypeConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; +import proguard.classfile.instruction.VariableInstruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.ClassPrinter; + +import java.util.*; + +public class CastRemover implements InstructionVisitor { + private final CodeAttributeEditor codeAttributeEditor; + private List keepList; + private int currentIndex; + + public CastRemover(CodeAttributeEditor codeAttributeEditor, List keepList) { + this.codeAttributeEditor = codeAttributeEditor; + this.currentIndex = 0; + this.keepList = keepList; + } + + public CastRemover(CodeAttributeEditor codeAttributeEditor) { + this(codeAttributeEditor, new ArrayList<>()); + } + + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + Set castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); + if (constantInstruction.opcode == Instruction.OP_CHECKCAST) { + System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); + codeAttributeEditor.deleteInstruction(offset); + } else if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { + if (getInvokedMethodName(clazz, constantInstruction).equals("valueOf")) { + if (!keepList.contains(currentIndex)) { + System.out.print("Removing "); InstructionFactory.create(codeAttribute.code, offset).accept(clazz, method, codeAttribute, offset, new ClassPrinter()); + codeAttributeEditor.deleteInstruction(offset); + } + + currentIndex++; + } + } else if (constantInstruction.opcode == Instruction.OP_INVOKEVIRTUAL) { + System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); + if (castingMethodNames.contains(getInvokedMethodName(clazz, constantInstruction))) { + codeAttributeEditor.deleteInstruction(offset); + } + } + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + } + + private String getInvokedMethodName(Clazz clazz, ConstantInstruction constantInstruction) { + final String[] invokedMethodName = new String[1]; + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + @Override public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { + clazz.constantPoolEntryAccept(anyMethodrefConstant.u2nameAndTypeIndex, new ConstantVisitor() { + @Override + public void visitNameAndTypeConstant(Clazz clazz, NameAndTypeConstant nameAndTypeConstant) { + invokedMethodName[0] = nameAndTypeConstant.getName(clazz); + } + }); + } + }); + return invokedMethodName[0]; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java new file mode 100644 index 000000000..221b897f5 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java @@ -0,0 +1,50 @@ +package proguard.optimize.inline; + +import proguard.classfile.*; +import proguard.classfile.editor.AttributeAdder; +import proguard.classfile.editor.ClassBuilder; +import proguard.classfile.editor.ClassEditor; +import proguard.obfuscate.NameFactory; +import proguard.obfuscate.SimpleNameFactory; +import proguard.obfuscate.UniqueMemberNameFactory; +import proguard.optimize.info.ProgramMemberOptimizationInfoSetter; + +import java.util.function.Function; + +public class DescriptorModifier { + private final ClassBuilder classBuilder; + private final NameFactory nameFactory; + private final Clazz clazz; + + DescriptorModifier(Clazz clazz) { + classBuilder = new ClassBuilder((ProgramClass) clazz); + nameFactory = new UniqueMemberNameFactory(new SimpleNameFactory(), clazz); + this.clazz = clazz; + } + + public ProgramMethod modify(ProgramMethod sourceMethod, Function modifier) { + return modify(sourceMethod, modifier, false); + } + + public ProgramMethod modify(ProgramMethod sourceMethod, Function modifier, boolean removeOriginal) { + ProgramMethod newMethod = classBuilder.addAndReturnMethod( + sourceMethod.u2accessFlags, + nameFactory.nextName(), + modifier.apply(sourceMethod.getDescriptor(clazz)) + ); + + sourceMethod.attributesAccept((ProgramClass) clazz, + new AttributeAdder((ProgramClass) clazz, newMethod, true)); + + if (removeOriginal) + removeOriginal(sourceMethod); + + newMethod.accept(clazz, new ProgramMemberOptimizationInfoSetter()); + return newMethod; + } + + private void removeOriginal(ProgramMethod method) { + ClassEditor classEditor = new ClassEditor((ProgramClass) clazz); + classEditor.removeMethod(method); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java new file mode 100644 index 000000000..ec5ddacb9 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java @@ -0,0 +1,45 @@ +package proguard.optimize.inline; + +import proguard.classfile.instruction.Instruction; + +import java.util.Objects; + +public final class InstructionAtOffset { + private final Instruction instruction; + private final int offset; + + InstructionAtOffset(Instruction instruction, int offset) { + this.instruction = instruction; + this.offset = offset; + } + + public Instruction instruction() { + return instruction; + } + + public int offset() { + return offset; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + InstructionAtOffset that = (InstructionAtOffset) obj; + return Objects.equals(this.instruction, that.instruction) && + this.offset == that.offset; + } + + @Override + public int hashCode() { + return Objects.hash(instruction, offset); + } + + @Override + public String toString() { + return "InstructionAtOffset[" + + "instruction=" + instruction + ", " + + "offset=" + offset + ']'; + } + +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java new file mode 100644 index 000000000..05c905216 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java @@ -0,0 +1,66 @@ +package proguard.optimize.inline; + +import proguard.classfile.*; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.visitor.AllMethodVisitor; +import proguard.classfile.visitor.MemberVisitor; + +public class LambdaImplementationVisitor implements ConstantVisitor, MemberVisitor { + private final InvokeMethodVisitor invokeMethodVisitor; + private Clazz lambdaImplementationClass; + public LambdaImplementationVisitor(InvokeMethodVisitor invokeMethodHandler) { + this.invokeMethodVisitor = invokeMethodHandler; + } + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + lambdaImplementationClass = fieldrefConstant.referencedClass; + fieldrefConstant.referencedClass.interfaceConstantsAccept(this); + } + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant referencedClassConstant) { + referencedClassConstant.referencedClass.methodsAccept(this); + } + + @Override + public void visitProgramMethod(ProgramClass interfaceClass, ProgramMethod programMethod) + { + getLambdaImplementation(interfaceClass, programMethod); + } + + @Override + public void visitLibraryMethod(LibraryClass interfaceClass, LibraryMethod libraryMethod) { + getLambdaImplementation(interfaceClass, libraryMethod); + } + + private void getLambdaImplementation(Clazz interfaceClazz, Method method) { + String descriptor = method.getDescriptor(interfaceClazz); + lambdaImplementationClass.methodAccept("invoke", descriptor, new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + System.out.println("Descriptor " + programMethod.getDescriptor(programClass)); + String descriptor = programMethod.getDescriptor(programClass); + lambdaImplementationClass.accept(new AllMethodVisitor(new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + if (programMethod.getName(programClass).equals("invoke") && programMethod.u2accessFlags == (AccessConstants.PUBLIC | AccessConstants.FINAL)) { + lambdaImplementationClass.methodAccept("invoke", programMethod.getDescriptor(programClass), new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + invokeMethodVisitor.visitInvokeMethod(programClass, programMethod, interfaceClazz, descriptor); + } + }); + } + } + })); + } + }); + } + + public interface InvokeMethodVisitor { + void visitInvokeMethod(ProgramClass programClass, ProgramMethod programMethod, Clazz interfaceClass, String bridgeDescriptor); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java new file mode 100644 index 000000000..629bcfb58 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -0,0 +1,103 @@ +package proguard.optimize.inline; + +//import com.guardsquare.gui.Gui; +import proguard.optimize.inline.lambda_locator.LambdaLocator; +import proguard.AppView; +import proguard.classfile.AccessConstants; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.util.InitializationUtil; +import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.pass.Pass; + +import java.util.HashSet; +import java.util.Set; + +public class LambdaInliner implements Pass { + private final String classNameFilter; + private boolean inlinedAllUsages; + public LambdaInliner(String classNameFilter) { + this.classNameFilter = classNameFilter; + this.inlinedAllUsages = true; + } + + public LambdaInliner() { + this(""); + } + + @Override + public void execute(AppView appView) { + LambdaLocator lambdaLocator = new LambdaLocator(appView.programClassPool, classNameFilter); + + for (Lambda lambda : lambdaLocator.getStaticLambdas()) { + Set remainder = new HashSet<>(); + inlinedAllUsages = true; + InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); + lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), appView, (foundLambda, consumingClass, consumingMethod, consumingCallOffset, consumingCallClazz, consumingCallmethod, consumingCallCodeAttribute, sourceTrace, possibleLambdas) -> { + // Try inlining the lambda in consumingMethod + BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClass, consumingMethod, lambda); + Method inlinedLambamethod = baseLambdaInliner.inline(); + + // We didn't inline anything so no need to change any call instructions. + if (inlinedLambamethod == null) { + inlinedAllUsages = false; + return; + } + + if (possibleLambdas.size() > 1) { + // This lambda is part of a collection of lambdas that might potentially be used, but we do not know which one is actually used. Because of that we cannot inline it. + inlinedAllUsages = false; + return; + } + + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(consumingCallCodeAttribute.u4codeLength); + + /* + * Remove usages that bring the variable on the stack so all the way up until a load + * The store operations before the load might also be used by other functions calls that consume the + * lambda, that's why we need to keep them. + */ + for (int i = 0; i < sourceTrace.size(); i++) { + InstructionAtOffset instrAtOffset = sourceTrace.get(i); + codeAttributeEditor.deleteInstruction(instrAtOffset.offset()); + if (instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_ALOAD || instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_INVOKESTATIC) { + remainder.addAll(sourceTrace.subList(i + 1, sourceTrace.size())); + break; + } + } + + // Replace invokestatic call to a call with the new function + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingCallClazz); + int methodWithoutLambdaParameterIndex = constantPoolEditor.addMethodrefConstant(consumingClass, inlinedLambamethod); + + // Replacing at consumingCallOffset + if ((consumingMethod.getAccessFlags() & AccessConstants.STATIC) == 0) { + codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, methodWithoutLambdaParameterIndex)); + } else { + codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, methodWithoutLambdaParameterIndex)); + } + + codeAttributeEditor.visitCodeAttribute(consumingCallClazz, consumingCallmethod, consumingCallCodeAttribute); + })); + + /* + * Only remove the code needed to obtain a reference to the lambda if we were able to inline everything, if we + * could not do that then we still need the lambda and cannot remove it. + */ + if (inlinedAllUsages) { + // Removing lambda obtaining code because all usages could be inlined! + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(lambda.codeAttribute().u4codeLength); + for (InstructionAtOffset instrAtOffset : remainder) { + codeAttributeEditor.deleteInstruction(instrAtOffset.offset()); + } + codeAttributeEditor.visitCodeAttribute(lambda.clazz(), lambda.method(), lambda.codeAttribute()); + } + } + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java new file mode 100644 index 000000000..eaa06abef --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -0,0 +1,172 @@ +package proguard.optimize.inline; + +import proguard.optimize.inline.lambda_locator.LambdaLocator; +import proguard.AppView; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.SimpleInstruction; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassPrinter; +import proguard.evaluation.PartialEvaluator; +import proguard.evaluation.TracedStack; +import proguard.optimize.inline.lambda_locator.Lambda; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, ConstantVisitor { + private final Lambda targetLambda; + private PartialEvaluator partialEvaluator; + private final Map lambdaMap; + private final AppView appView; + private final LambdaUsageHandler lambdaUsageHandler; + public MethodrefConstant methodrefConstant; + public FieldrefConstant referencedFieldConstant; + private final boolean inlineFromFields; + private final boolean inlineFromMethods; + private final int[] typedReturnInstructions = new int[] { + Instruction.OP_IRETURN, + Instruction.OP_LRETURN, + Instruction.OP_FRETURN, + Instruction.OP_DRETURN, + Instruction.OP_ARETURN + }; + + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, boolean inlineFromFields, boolean inlineFromMethods, LambdaUsageHandler lambdaUsageHandler) { + this.targetLambda = targetLambda; + this.partialEvaluator = new PartialEvaluator(); + this.lambdaMap = lambdaMap; + this.appView = appView; + this.lambdaUsageHandler = lambdaUsageHandler; + this.inlineFromFields = inlineFromFields; + this.inlineFromMethods = inlineFromMethods; + } + + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, LambdaUsageHandler lambdaUsageHandler) { + this(targetLambda, lambdaMap, appView, false, false, lambdaUsageHandler); + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); + codeAttribute.instructionsAccept(clazz, method, + new InstructionOpCodeFilter( + new int[] { + Instruction.OP_INVOKESTATIC, + Instruction.OP_INVOKEVIRTUAL, + Instruction.OP_IRETURN, + Instruction.OP_LRETURN, + Instruction.OP_FRETURN, + Instruction.OP_DRETURN, + Instruction.OP_ARETURN + }, + this + ) + ); + } + + @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) { + // We deleted instructions while iterating, the loop however cannot read the new codeAttribute.u4codeLength value so we must stop it manually! + if (offset >= codeAttribute.u4codeLength) { + return; + } + findUsage(clazz, method, codeAttribute, constantInstruction, offset, this::isTargetLambda); + } + + @Override + public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) {} + + private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, ConstantInstruction consumingMethodCallInstruction, int offset, Function condition) { + System.out.println("--------Start----------"); + System.out.println(consumingMethodCallInstruction); + + clazz.constantPoolEntryAccept(consumingMethodCallInstruction.constantIndex, this); + + partialEvaluator = new PartialEvaluator(); + partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); + TracedStack tracedStack = partialEvaluator.getStackBefore(offset); + System.out.println(tracedStack); + + System.out.println(methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass)); + String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + for (int argIndex = 0; argIndex < argCount; argIndex++) { + int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); + int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + List trace = Util.traceParameterOffset(partialEvaluator, codeAttribute, traceOffset); + List leafNodes = new ArrayList<>(); + Util.traceParameterTree(partialEvaluator, codeAttribute, traceOffset, leafNodes); + boolean match = false; + for (InstructionAtOffset tracedInstructionAtOffset : leafNodes) { + System.out.println("Argument " + argIndex + " source " + tracedInstructionAtOffset); + if (condition.apply(tracedInstructionAtOffset)) { + codeAttribute.accept(clazz, method, new ClassPrinter()); + System.out.println("Lambda " + targetLambda + " consumed by " + methodrefConstant.referencedMethod.getName(methodrefConstant.referencedClass)); + match = true; + break; + } + } + + if (match) { + lambdaUsageHandler.handle( + targetLambda, + methodrefConstant.referencedClass, + methodrefConstant.referencedMethod, + offset, + clazz, + method, + codeAttribute, + trace, + leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { + ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); + return lambdaMap.get(getStaticInstruction.constantIndex); + }).collect(Collectors.toList()) + ); + } + } + System.out.println("---------End-----------"); + } + + private boolean isTargetLambda(InstructionAtOffset instructionAtOffset) { + if (instructionAtOffset.instruction().opcode == Instruction.OP_GETSTATIC) { + ConstantInstruction constantInstruction = (ConstantInstruction) instructionAtOffset.instruction(); + Lambda lambda = lambdaMap.get(constantInstruction.constantIndex); + return lambda != null && lambda.equals(targetLambda); + } + return false; + } + + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + this.methodrefConstant = methodrefConstant; + } + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + this.referencedFieldConstant = fieldrefConstant; + } + + public interface LambdaUsageHandler { + void handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java new file mode 100644 index 000000000..c3e9c54ad --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java @@ -0,0 +1,56 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +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.editor.CodeAttributeEditor; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.VariableInstruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.MemberVisitor; + +public class LocalUsageRemover implements MemberVisitor { + private final CodeAttributeEditor codeAttributeEditor; + private final int argumentIndex; + + public LocalUsageRemover(CodeAttributeEditor codeAttributeEditor, int argumentIndex) { + this.codeAttributeEditor = codeAttributeEditor; + this.argumentIndex = argumentIndex; + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + programMethod.accept(programClass, new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeAttributeEditor.reset(codeAttribute.u4codeLength); + codeAttribute.instructionsAccept(clazz, method, new InstructionVisitor() { + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + @Override + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { + if (variableInstruction.variableIndex == argumentIndex) { + if (variableInstruction.isStore()) { + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_POP)); + } else { + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_ACONST_NULL)); + } + } else if (variableInstruction.variableIndex > argumentIndex){ + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(variableInstruction.canonicalOpcode(), variableInstruction.variableIndex - 1, variableInstruction.constant)); + } + } + }); + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + })); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java new file mode 100644 index 000000000..2d65b7b90 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java @@ -0,0 +1,41 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.MemberVisitor; + +public class MethodInstructionVisitor implements MemberVisitor, AttributeVisitor { + private final InstructionVisitor instructionVisitor; + private final int startOffset; + private final int endOffset; + + public MethodInstructionVisitor(int startOffset, int endOffset, InstructionVisitor instructionVisitor) { + this.instructionVisitor = instructionVisitor; + this.startOffset = startOffset; + this.endOffset = endOffset; + } + + public MethodInstructionVisitor(InstructionVisitor instructionVisitor) { + this(-1, -1, instructionVisitor); + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + programMethod.attributesAccept(programClass, this); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + if (startOffset < 0) codeAttribute.instructionsAccept(clazz, method, instructionVisitor); + else codeAttribute.instructionsAccept(clazz, method, startOffset, endOffset, instructionVisitor); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java b/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java new file mode 100644 index 000000000..257f77803 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java @@ -0,0 +1,66 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.visitor.ClassVisitor; +import proguard.classfile.visitor.MemberVisitor; + +/** + * A class that searches the entire class pool for calls to a specific method. + */ +public class MethodUsageFinder implements ClassVisitor, MemberVisitor, InstructionVisitor, ConstantVisitor { + private final Method targetMethod; + private final UsingMethodHandler usageHandler; + + public MethodUsageFinder(Method targetMethod, UsingMethodHandler usageHandler) { + this.targetMethod = targetMethod; + this.usageHandler = usageHandler; + } + + @Override + public void visitAnyClass(Clazz clazz) {} + + @Override + public void visitProgramClass(ProgramClass programClass) { + programClass.methodsAccept(this); + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + programMethod.accept(programClass, + new AllAttributeVisitor( + new AllInstructionVisitor( + new InstructionOpCodeFilter( + new int[] { + Instruction.OP_INVOKESTATIC, + Instruction.OP_INVOKEVIRTUAL, + Instruction.OP_INVOKESPECIAL + }, + this + ))) + ); + } + + @Override + public void visitConstantInstruction(Clazz consumingCallClazz, Method possibleMethodUser, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + Method referencedMethod = new RefMethodFinder(consumingCallClazz).findReferencedMethod(constantInstruction); + if (referencedMethod != null && referencedMethod.equals(targetMethod)) { + System.out.println("Found a user, method " + possibleMethodUser.getName(consumingCallClazz) + " in class " + consumingCallClazz.getName()); + usageHandler.handle(consumingCallClazz, possibleMethodUser, constantInstruction, offset); + } + } + + public interface UsingMethodHandler { + void handle(Clazz clazz, Method method, ConstantInstruction constantInstruction, int offset); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/Node.java b/base/src/main/java/proguard/optimize/inline/Node.java new file mode 100644 index 000000000..09e07c5e4 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/Node.java @@ -0,0 +1,33 @@ +package proguard.optimize.inline; + +import java.util.List; +import java.util.Objects; + +public final class Node { + public InstructionAtOffset value; + public List children; + + Node(InstructionAtOffset value, List children) { + this.value = value; + this.children = children; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + Node that = (Node) obj; + return Objects.equals(this.value, that.value); + } + + @Override + public int hashCode() { + return Objects.hash(value); + } + + @Override + public String toString() { + return "Node[" + + "value=" + value + ']'; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java new file mode 100644 index 000000000..d0108e5d7 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java @@ -0,0 +1,75 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.InstructionSequenceMatcher; + +class NullCheckRemover implements InstructionVisitor { + private final InstructionSequenceMatcher insSeqMatcher; + private final int X = InstructionSequenceMatcher.X; + private final int Y = InstructionSequenceMatcher.Y; + private final int C = InstructionSequenceMatcher.C; + private final Instruction[] pattern; + Constant[] constants; + private final int argumentIndex; + private final InstructionVisitor extraInstructionVisitor; + + public NullCheckRemover(int argumentIndex, InstructionVisitor extraInstructionVisitor) { + InstructionSequenceBuilder ____ = + new InstructionSequenceBuilder(); + + pattern = ____.aload(X) + .ldc_(C) + .invokestatic(Y).__(); + + constants = ____.constants(); + + this.insSeqMatcher = new InstructionSequenceMatcher(constants, pattern); + this.argumentIndex = argumentIndex; + this.extraInstructionVisitor = extraInstructionVisitor; + } + + public NullCheckRemover(int argumentIndex) { + this(argumentIndex, null); + } + + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + //System.out.println(instruction.toString(clazz, offset)); + instruction.accept(clazz, method, codeAttribute, offset, insSeqMatcher); + if (insSeqMatcher.isMatching() && insSeqMatcher.matchedConstantIndex(X) == argumentIndex) { + //System.out.println(" -> matching sequence starting at [" + insSeqMatcher.matchedInstructionOffset(0) + "]"); + + insSeqMatcher.matchedConstantIndex(Y); + clazz.constantPoolEntryAccept(insSeqMatcher.matchedArgument(Y), new ConstantVisitor() { + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + // Check if the called function matches kotlin.jvm.internal.Intrinsics#void checkNotNullParameter(java.lang.Object,java.lang.String) + if ( + methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && + methodrefConstant.getName(clazz).equals("checkNotNullParameter") && + methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V") + ) { + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(codeAttribute.u4codeLength); + for (int insIndex = 0; insIndex < pattern.length; insIndex++) { + int insOffset = insSeqMatcher.matchedInstructionOffset(insIndex); + if (extraInstructionVisitor != null) + extraInstructionVisitor.visitAnyInstruction(clazz, method, codeAttribute, insSeqMatcher.matchedInstructionOffset(insIndex), InstructionFactory.create(codeAttribute.code, insOffset)); + codeAttributeEditor.deleteInstruction(insOffset); + } + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + } + }); + } + } +} diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java new file mode 100644 index 000000000..d02258fcb --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -0,0 +1,141 @@ +package proguard.optimize.inline; + +import proguard.optimize.inline.lambda_locator.LambdaLocator; +import proguard.AppView; +import proguard.classfile.*; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.ClassEditor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.VariableInstruction; +import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.ClassReferenceInitializer; +import proguard.classfile.util.ClassUtil; +import proguard.classfile.visitor.ClassPrinter; +import proguard.classfile.visitor.MemberVisitor; +import proguard.evaluation.PartialEvaluator; +import proguard.evaluation.TracedStack; +import proguard.optimize.inline.lambda_locator.Lambda; + +/** + * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. + * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. + */ +public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor { + private final AppView appView; + private Clazz consumingClazz; + private Method copiedConsumingMethod; + private final Method originalConsumingMethod; + private final boolean isStatic; + private final Lambda lambda; + private final boolean enableRecursiveMerging; + private final PartialEvaluator partialEvaluator; + private Clazz referencedClass; + private Method referencedMethod; + private int callOffset; + + /** + * @param originalConsumingMethod The original consuming method reference, this is used to detect recursion. + */ + public RecursiveInliner(AppView appView, Method originalConsumingMethod, Lambda lambda, boolean enableRecursiveMerging) { + this.appView = appView; + this.consumingClazz = null; + this.copiedConsumingMethod = null; + this.originalConsumingMethod = originalConsumingMethod; + this.isStatic = (originalConsumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + this.lambda = lambda; + this.enableRecursiveMerging = enableRecursiveMerging; + this.partialEvaluator = new PartialEvaluator(); + this.referencedMethod = null; + } + + public RecursiveInliner(AppView appView, Method originalConsumingMethod, Lambda lambda) { + this(appView, originalConsumingMethod, lambda, false); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeAttribute.accept(clazz, method, new ClassPrinter()); + partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); + codeAttribute.instructionsAccept(clazz, method, + new InstructionOpCodeFilter(new int[] {Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL, Instruction.OP_INVOKESPECIAL}, + new InstructionVisitor() { + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + System.out.println("At " + constantInstruction); + TracedStack tracedStack = partialEvaluator.getStackBefore(offset); + + if (tracedStack == null) + return; + + System.out.println("Stack size " + tracedStack.size()); + if (tracedStack.size() <= 0) + return; + + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + if (methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && + methodrefConstant.getName(clazz).equals("checkNotNullParameter") && + methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) + return; + + String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + boolean referencedMethodStatic = (methodrefConstant.referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + // We will now check if any of the arguments has a value that comes from the lambda parameters + for (int argIndex = 0; argIndex < argCount; argIndex++) { + int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); + int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + referencedMethod = methodrefConstant.referencedMethod; + referencedClass = methodrefConstant.referencedClass; + callOffset = offset; + codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); + } + } + }); + } + })); + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + + @Override + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { + if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { + System.out.println("Value from " + variableInstruction + " " + variableInstruction.variableIndex); + + Instruction sourceInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); + System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); + if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { + VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; + + String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); + int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); + int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); + + if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { + throw new CannotInlineException("Cannot inline lambda into recursive function"); + } + } + } + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + this.consumingClazz = programClass; + this.copiedConsumingMethod = programMethod; + programMethod.attributesAccept(programClass, this); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java new file mode 100644 index 000000000..4f3a38c9a --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java @@ -0,0 +1,32 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Member; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +import proguard.classfile.instruction.ConstantInstruction; +import proguard.classfile.visitor.MemberVisitor; +import proguard.classfile.visitor.ReferencedMemberVisitor; + +public class RefMethodFinder { + private final Clazz clazz; + private Method foundMethod; + public RefMethodFinder(Clazz clazz) { + this.clazz = clazz; + this.foundMethod = null; + } + + public Method findReferencedMethod(ConstantInstruction constantInstruction) { + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ReferencedMemberVisitor(new MemberVisitor() { + @Override + public void visitAnyMember(Clazz clazz, Member member) {} + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + foundMethod = programMethod; + } + })); + return foundMethod; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java new file mode 100644 index 000000000..66afe4c26 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -0,0 +1,167 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; +import proguard.classfile.instruction.VariableInstruction; +import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.InternalTypeEnumeration; +import proguard.classfile.visitor.ClassPrinter; +import proguard.evaluation.PartialEvaluator; +import proguard.evaluation.TracedStack; +import proguard.evaluation.value.InstructionOffsetValue; +import proguard.optimize.peephole.MethodInliner; + +import java.util.ArrayList; +import java.util.List; + +public class Util { + /** + * Given an offset of an instruction, trace the source producer value. + */ + public static Instruction traceParameter(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { + List trace = traceParameterOffset(partialEvaluator, codeAttribute, offset); + return trace.get(trace.size() - 1).instruction(); + } + + public static List traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { + List trace = new ArrayList<>(); + TracedStack currentTracedStack; + int currentOffset = offset; + Instruction currentInstruction = InstructionFactory.create(codeAttribute.code, currentOffset); + trace.add(new InstructionAtOffset(currentInstruction, currentOffset)); + + // Maybe make use of stackPopCount, if an instruction doesn't pop anything from the stack then it is the producer? + while ( + (!isLoad(currentInstruction) || + !partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && + currentInstruction.opcode != Instruction.OP_ACONST_NULL && + currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + currentInstruction.opcode != Instruction.OP_LDC && + currentInstruction.opcode != Instruction.OP_LDC_W && + currentInstruction.opcode != Instruction.OP_LDC2_W && + currentInstruction.opcode != Instruction.OP_NEW && + currentInstruction.opcode != Instruction.OP_GETSTATIC && + currentInstruction.opcode != Instruction.OP_INVOKESTATIC && + currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && + currentInstruction.opcode != Instruction.OP_GETFIELD + ) { + System.out.println(currentInstruction.toString(currentOffset)); + currentTracedStack = partialEvaluator.getStackBefore(currentOffset); + System.out.println(currentTracedStack); + + // There is no stack value, it's coming from the variables + if (isLoad(currentInstruction)) { + InstructionOffsetValue offsetValue = partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue(); + // There are multiple sources, we don't know which one! We could in the future return a tree instead of just null if that would be useful. + if (offsetValue.instructionOffsetCount() > 1) { + return null; + } + currentOffset = offsetValue.instructionOffset(0); + } else { + currentOffset = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue().instructionOffset(0); + } + currentInstruction = InstructionFactory.create(codeAttribute.code, currentOffset); + trace.add(new InstructionAtOffset(currentInstruction, currentOffset)); + } + return trace; + } + + public static boolean isLoad(Instruction instruction) { + return instruction.getClass().equals(VariableInstruction.class) && ((VariableInstruction) instruction).isLoad(); + } + + public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset, List leafNodes) { + TracedStack currentTracedStack; + Instruction currentInstruction = InstructionFactory.create(codeAttribute.code, offset); + Node root = new Node(new InstructionAtOffset(currentInstruction, offset), new ArrayList<>()); + + // Maybe make use of stackPopCount, if an instruction doesn't pop anything from the stack then it is the producer? + if ( + (!isLoad(currentInstruction) || + !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && + currentInstruction.opcode != Instruction.OP_ACONST_NULL && + currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + currentInstruction.opcode != Instruction.OP_LDC && + currentInstruction.opcode != Instruction.OP_LDC_W && + currentInstruction.opcode != Instruction.OP_LDC2_W && + currentInstruction.opcode != Instruction.OP_NEW && + currentInstruction.opcode != Instruction.OP_GETSTATIC && + currentInstruction.opcode != Instruction.OP_INVOKESTATIC && + currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && + currentInstruction.opcode != Instruction.OP_GETFIELD + ) { + System.out.println(currentInstruction.toString(offset)); + currentTracedStack = partialEvaluator.getStackBefore(offset); + System.out.println(currentTracedStack); + + // There is no stack value, it's coming from the variables + InstructionOffsetValue offsetValue; + if (isLoad(currentInstruction)) { + offsetValue = partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue(); + } + else { + offsetValue = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue(); + } + for (int i = 0; i < offsetValue.instructionOffsetCount(); i++) { + root.children.add(traceParameterTree(partialEvaluator, codeAttribute, offsetValue.instructionOffset(i), leafNodes)); + } + } + else { + // This is a leaf node, add the instruction to the list. + leafNodes.add(root.value); + } + return root; + } + + public static int findFirstLambdaParameter(String descriptor) { + return findFirstLambdaParameter(descriptor, true); + } + + public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); + int index = 0; + while (internalTypeEnumeration.hasMoreTypes()) { + if (internalTypeEnumeration.nextType().startsWith("Lkotlin/jvm/functions/Function")) { + break; + } + index ++; + } + return index + (isStatic ? 0 : 1); + } + + /** + * Inline a specified method in the locations it is called in the current clazz. + * @param clazz The clazz in which we want to inline the targetMethod + * @param targetMethod The method we want to inline. + */ + public static void inlineMethodInClass(Clazz clazz, Method targetMethod) { + clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, 20000, true, false, null) { + @Override + protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { + return method.equals(targetMethod); + } + })); + } + + public static void printMethodWithRange(Clazz clazz, Method method, int startOffset, int endOffset, int specialOffset) { + method.accept(clazz, new AllAttributeVisitor(new AllInstructionVisitor(new InstructionVisitor() { + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + String str = " "; + if (offset == specialOffset) str = " * "; + else if (offset >= startOffset && offset < endOffset) str = " | "; + System.out.print(str); + instruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); + } + }))); + } + + public static void printMethodWithRange(Clazz clazz, Method method, int startOffset, int endOffset) { + printMethodWithRange(clazz, method, startOffset, endOffset, -1); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java new file mode 100644 index 000000000..fdc1f24d1 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java @@ -0,0 +1,66 @@ +package proguard.optimize.inline.lambda_locator; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.instruction.ConstantInstruction; + +import java.util.Objects; + +public final class Lambda { + private final Clazz clazz; + private final Method method; + private final CodeAttribute codeAttribute; + private final int offset; + private final ConstantInstruction constantInstruction; + + Lambda(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + this.clazz = clazz; + this.method = method; + this.codeAttribute = codeAttribute; + this.offset = offset; + this.constantInstruction = constantInstruction; + } + + public Clazz clazz() { + return clazz; + } + + public Method method() { + return method; + } + + public CodeAttribute codeAttribute() { + return codeAttribute; + } + + public int offset() { + return offset; + } + + public ConstantInstruction constantInstruction() { + return constantInstruction; + } + + @Override + public boolean equals(Object obj) { + if (obj == this) return true; + if (obj == null || obj.getClass() != this.getClass()) return false; + Lambda that = (Lambda) obj; + return Objects.equals(this.clazz, that.clazz) && + Objects.equals(this.method, that.method) && + Objects.equals(this.codeAttribute, that.codeAttribute) && + this.offset == that.offset && + Objects.equals(this.constantInstruction, that.constantInstruction); + } + + @Override + public int hashCode() { + return Objects.hash(clazz, method, codeAttribute, offset, constantInstruction); + } + + @Override + public String toString() { + return constantInstruction.toString(); + } +} \ No newline at end of file diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java new file mode 100644 index 000000000..bc9fec0c4 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -0,0 +1,111 @@ +package proguard.optimize.inline.lambda_locator; + +import proguard.backport.LambdaExpression; +import proguard.backport.LambdaExpressionCollector; +import proguard.classfile.*; +import proguard.classfile.attribute.*; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.*; +import proguard.classfile.constant.visitor.ConstantVisitor; +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.ClassReferenceInitializer; +import proguard.classfile.visitor.MemberVisitor; + +import java.util.*; + +public class LambdaLocator { + private final Map>> classLambdas = new HashMap<>(); + private final List staticLambdas = new ArrayList<>(); + private final Map staticLambdaMap = new HashMap<>(); + private final Set lambdaClasses = new HashSet<>(); + + public LambdaLocator(ClassPool classPool, String classNameFilter) { + classPool.classesAccept(classNameFilter, clazz -> { + // Find classes that inherit from kotlin.jvm.internal.Lambda + clazz.superClassConstantAccept(new ConstantVisitor() { + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + clazz.constantPoolEntryAccept(classConstant.u2nameIndex, new ConstantVisitor() { + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { + if (utf8Constant.getString().equals("kotlin/jvm/internal/Lambda")) { + System.out.println("Class " + clazz.getName() + " is a kotlin lambda class!"); + lambdaClasses.add(clazz); + } else { + System.out.println("Class " + clazz.getName() + " is not a kotlin lambda class!"); + } + } + }); + } + }); + }); + + // Find static lambdas + classPool.classesAccept(classNameFilter, clazz -> { + + HashMap h = new HashMap<>(); + LambdaExpressionCollector lec = new LambdaExpressionCollector(h); + lec.visitProgramClass((ProgramClass) clazz); + + clazz.methodsAccept(new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + programMethod.accept(programClass, new AllAttributeVisitor(new AllInstructionVisitor(new InstructionVisitor() { + @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) { + if (constantInstruction.opcode == Instruction.OP_GETSTATIC) { + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, new ConstantVisitor() { + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + clazz.constantPoolEntryAccept(classConstant.u2nameIndex, new ConstantVisitor() { + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { + classPool.classAccept(utf8Constant.getString(), referencedClazz -> { + if (lambdaClasses.contains(referencedClazz)) { + System.out.println("Found a lambda invocation " + constantInstruction); + + classLambdas.putIfAbsent(clazz, new HashMap<>()); + classLambdas.get(clazz).putIfAbsent(method, new HashSet<>()); + classLambdas.get(clazz).get(method).add(new Lambda(clazz, method, codeAttribute, offset, constantInstruction)); + + Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); + staticLambdas.add(lambda); + staticLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); + } + }); + } + }); + } + }); + } + }); + } + } + }))); + } + }); + }); + } + + public Map>> getLambdasByClass() { + return classLambdas; + } + + public List getStaticLambdas() { + return staticLambdas; + } + + public Map getStaticLambdaMap() { + return staticLambdaMap; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java new file mode 100644 index 000000000..bdb983732 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java @@ -0,0 +1,89 @@ +package proguard.optimize.inline.lambda_locator; + +import proguard.classfile.visitor.ClassPrinter; +import proguard.classfile.ClassPool; +import proguard.classfile.ProgramClass; +import proguard.classfile.visitor.ClassPoolFiller; +import proguard.io.*; +import proguard.io.util.IOUtil; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; +import java.util.zip.ZipInputStream; + +public class Util { + /*public static ClassPool loadApk(String filename) throws IOException { + return IOUtil.read( + new File(filename), + false, + false, + (dataEntryReader, classVisitor) -> new NameFilteredDataEntryReader( + "classes*.dex", + new DexClassReader( + true, + classVisitor + ), + dataEntryReader) + ); + }*/ + + public static ClassPool loadJar(String filename) throws IOException { + ClassPool classPool = new ClassPool(); + + DataEntrySource source = + new FileSource( + new File(filename)); + + source.pumpDataEntries( + new JarReader(false, + new ClassFilter( + new ClassReader(false, false, false, false, null, + new ClassPoolFiller(classPool))))); + return classPool; + } + + public static String getMainClassFromJar(String filename) throws IOException { + String mainClass = null; + ZipFile zipFile = new ZipFile(filename); + ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(filename), StandardCharsets.UTF_8); + while (true) { + ZipEntry entry = zipInputStream.getNextEntry(); + if (entry == null) break; + + if (entry.getName().equals("META-INF/MANIFEST.MF")) { + System.out.println(entry); + InputStream iStream = zipFile.getInputStream(entry); + BufferedReader reader = new BufferedReader(new InputStreamReader(iStream)); + while (true) { + String line = reader.readLine(); + if (line == null) break; + + if (line.startsWith("Main-Class: ")) + mainClass = line.substring(line.indexOf(':')+1).trim(); + } + } + } + zipInputStream.close(); + zipFile.close(); + return mainClass; + } + + public static void writeJar(ClassPool programClassPool, String outputJarFileName) throws IOException { + JarWriter jarWriter = + new JarWriter( + new ZipWriter( + new FixedFileWriter( + new File(outputJarFileName)))); + + programClassPool.classesAccept( + new DataEntryClassWriter(jarWriter)); + + jarWriter.close(); + } + + public static void printClass(ClassPool classPool, String className) { + classPool.classAccept(className, new ClassPrinter()); + } +} diff --git a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt new file mode 100644 index 000000000..34fe4b22a --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt @@ -0,0 +1,283 @@ +package proguard.lambdaInline + +import compareOutputAndMainInstructions +import io.kotest.core.spec.style.FreeSpec +import proguard.testutils.KotlinSource + +class AdvancedTest: FreeSpec ({ + "three lambda functions with two of same size and one different" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + } + fun a(b: (String) -> Int) { + println(b("Hello World!") + 1) + } + + fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + println(a(3)) + println(b("Hello", true, 0)) + println(c(5)) + } + + + fun main() { + var index = 0 + while (index < 100) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf( + "iconst_0", + "istore_0", + "iload_0", + "bipush", + "if_icmpge", + "iinc", + "nop", + "invokestatic", + "invokestatic", + "invokestatic", + "goto", + "return" + ) + ) + } + + "switch case, unit lambda, multiple call to the same lambda" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Unit) { + a(1) + a(2) + a(3) + } + + fun main() { + test { + when(it) { + 1 -> println("Hello") + 2 -> println("World") + 3 -> println("!") + } + } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting basic types after the lambda call" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + + var i: Int = 1 + var f: Float = i.toFloat() + println(f + 4.3) + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting types before the lambda call" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + val a = test1(5) + if (a is Int) { + println(a + 1) + } else if (a is Boolean) { + println(a and true) + } + + println(a(12)) + } + + fun test1(a:Any): Any { + if (a is Int) { + return true + } else if (a is Boolean) { + return 7 + } else { + return 'a' + } + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting types after the lambda call" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + + val a = test1(5) + if (a is Int) { + println(a + 1) + } else if (a is Boolean) { + println(a and true) + } + } + + fun test1(a:Any): Any { + if (a is Int) { + return true + } else if (a is Boolean) { + return 7 + } else { + return 'a' + } + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting types between lambda calls" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + + val a = test1(5) + if (a is Int) { + println(a + 1) + } else if (a is Boolean) { + println(a and true) + } + + println(a(12)) + } + + fun test1(a:Any): Any { + if (a is Int) { + return true + } else if (a is Boolean) { + return 7 + } else { + return 'a' + } + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting types inside lambda call" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Any) -> Int) { + println(a(1)) + } + + fun main() { + test { a: Any -> if (a is Int) { + println(a + 1) + } else if (a is Boolean) { + println(a and true) + }; 1} + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Casting types inside lambda call + other arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Any, Int) -> Int) { + println(a(1, 2)) + } + + fun main() { + test { a: Any, b: Int -> if (a is Int) { + println(a + 1) + } else if (a is Boolean) { + println(a and true) + }; b} + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Recursive function should not be inlined" { + val code = KotlinSource( + "Main.kt", + """ + fun test(i: Int, f: (Int) -> Unit) { + if (i >= 0) { + f(i) + test(i - 1, f) + } + } + + fun test2(f: (Int) -> Int) { + println(f(2)) + } + + fun main() { + test(5) { println(it * 2) } + test2 { it * 3} + } + """.trimIndent()) + + compareOutputAndMainInstructions(code, listOf("iconst_5", "getstatic", "checkcast", "invokestatic", "invokestatic", "return"), false) + } + + +}) \ No newline at end of file diff --git a/base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt b/base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt new file mode 100644 index 000000000..c779b0b50 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt @@ -0,0 +1,53 @@ +package proguard.lambdaInline + +import io.kotest.core.spec.style.FreeSpec +import onlyTestRunning +import proguard.testutils.KotlinSource + +class FieldInliningTest: FreeSpec ({ + "call lambda from field in method" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + class MainKtWithAField { + val f = { i: Int -> i + 1 } + } + + fun test(a: (Int) -> Int) { + println(a(12)) + } + + fun main() { + val instance = MainKtWithAField() + test(instance.f) + } + """ + ) + + onlyTestRunning(code) + } + + "call lambda from a non-final field in method" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + class MainKtWithAField { + var f = { i: Int -> i + 1 } + } + + fun test(a: (Int) -> Int) { + println(a(12)) + } + + fun main() { + val instance = MainKtWithAField() + test(instance.f) + } + """ + ) + + onlyTestRunning(code) + } +}) \ No newline at end of file diff --git a/base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt b/base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt new file mode 100644 index 000000000..05d7b49eb --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt @@ -0,0 +1,28 @@ +package proguard.lambdaInline + +import io.kotest.core.spec.style.FreeSpec +import onlyTestRunning +import proguard.testutils.KotlinSource + +class LambdaReturnTest : FreeSpec({ + "Inline a lambda which is returned from a method" { + val code = KotlinSource( + "Main.kt", + """ + fun test(): (Int) -> Int { + return { it * 2 } + } + + fun consumer(f: (Int) -> Int) { + println(f(7)) + } + + fun main() { + consumer(test()) + } + """ + ) + + onlyTestRunning(code) + } +}) diff --git a/base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt new file mode 100644 index 000000000..489676771 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt @@ -0,0 +1,691 @@ +package proguard.lambdaInline + +import compareOutputAndMainInstructions +import io.kotest.core.spec.style.FreeSpec +import onlyTestRunning +import proguard.testutils.KotlinSource + +class OneLambdaInArgsTest: FreeSpec({ + "One lambda in args, basic case with int" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "One lambda in args, basic case with boolean" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Boolean) -> Boolean) { + println(a(false)) + } + + fun main() { + test { a: Boolean -> a and true} + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "One lambda in args, basic case with char" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Char) -> Char) { + println(a('a')) + } + + fun main() { + test { a: Char -> a} + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "One lambda in args and instruction before lambda function calls in main" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(3)) + } + + fun main() { + val x=1 + println(x) + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("iconst_1", "istore_0", "getstatic", "iload_0", "invokevirtual", "invokestatic", "return") + ) + } + + "One lambda in args with if" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Boolean) { + println(a(3)) + } + + fun main() { + test { a: Int -> a == 2 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "One lambda but in a class companion object" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun main() { + MainKtTest.test { a: Int -> a == 2 } + } + + class MainKtTest { + companion object { + @JvmStatic + fun test(a: (Int) -> Boolean) { + println(a(2)) + } + } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("getstatic", "invokevirtual", "return")) + } + + "One lambda but in a different class" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun main() { + val instance = MainKtTest() + instance.test { a: Int -> a == 2 } + } + + class MainKtTest { + fun test(a: (Int) -> Boolean) { + println(a(2)) + } + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("new", "dup", "invokespecial", "astore_0", "aload_0", "invokevirtual", "return") + ) + } + + "One lambda in a class + other arguments" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun main() { + val instance = MainKtTest() + instance.foo { it == 2 } + instance.bar(2) { it * 3 } + instance.foo { it == 3 } + } + + class MainKtTest { + fun foo(a: (Int) -> Boolean) { + println(a(2)) + } + + fun bar(i: Int, f: (Int) -> Int) { + println(f(i)) + } + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("new", "dup", "invokespecial", "astore_0", "aload_0", "invokevirtual", "aload_0", "iconst_2", "invokevirtual", "aload_0", "invokevirtual", "return") + ) + } + + "One lambda in a class + other arguments + nested + private" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun main() { + val instance = MainKtTest() + instance.foo({ it * 2 }, { it + 3 }) + instance.bar(2) { it * 3 } + instance.foo({ it + 3 }, { it * 2 }) + } + + class MainKtTest { + fun foo(f1: (Int) -> Int, f2: (Int) -> Int) { + level2(2, f2) + level2(3, f1) + } + + fun bar(i: Int, f: (Int) -> Int) { + level2(i, f) + } + + private fun level2(i: Int, f: (Int) -> Int) { + println(f(i)) + } + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda in a class with a call to a function in another class" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun main() { + val instance = MainKtTest() + instance.foo { it * 2 } + } + + class MainKtTest { + val instance = MainKtTest2() + fun foo(f: (Int) -> Int) { + instance.level2(2, f) + } + } + + class MainKtTest2 { + val offset = 5 + fun level2(i: Int, f: (Int) -> Int) { + println(f(i + offset)) + } + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda in a class with a call to a function in another class that calls a function in yet another class" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun main() { + val instance = MainKtTest() + instance.foo { it * 2 } + } + + class MainKtTest { + val instance = MainKtTest2() + + fun foo(f: (Int) -> Int) { + instance.foo(2, f) + } + } + + class MainKtTest2 { + val instance = MainKtTest3() + val offset = 5 + + fun foo(i: Int, f: (Int) -> Int) { + instance.foo(i + offset, f) + } + } + + class MainKtTest3 { + val offset = 7 + + fun foo(i: Int, f: (Int) -> Int) { + println(f(i + offset)) + } + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda but one other argument after lambda arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun test(a: (Int) -> Int, b: Int) { + println(a(3)) + println(b) + } + + fun main() { + test({ a: Int -> a + a }, 1) + } + + """ + ) + + compareOutputAndMainInstructions(code, listOf("iconst_1", "invokestatic", "return")) + } + + "One lambda but one other argument before lambda arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun test(b: Int, a: (Int) -> Int) { + println(a(3)) + println(b) + } + + fun main() { + test(1) { a: Int -> a + a } + } + + """ + ) + + compareOutputAndMainInstructions(code, listOf("iconst_1", "invokestatic", "return")) + } + + "One lambda but one other argument before and after lambda arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun test(b: Int, a: (Int) -> Int, c: Int) { + println(a(3)) + println(b) + println(c) + } + + fun main() { + test(1, { a: Int -> a + a }, 123) + } + + """ + ) + + compareOutputAndMainInstructions(code, listOf("iconst_1", "bipush", "invokestatic", "return")) + } + "One lambda but multiple other arguments before and after lambda arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun test(e:Int, b: Int, a: (Int) -> Int, c: Int, d: Int) { + println(a(3)) + println(b) + println(c) + println(d) + println(e) + } + + fun main() { + test(12, 1, { a: Int -> a + a }, 123, 122) + } + + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("bipush", "iconst_1", "bipush", "bipush", "invokestatic", "return") + ) + } + + "One lambda but the lambda is consumed by a function called from the consuming method" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(f: (Int) -> Int) { + println(level2(f)) + } + + fun level2(f: (Int) -> Int): Int { + return f(5) + } + + fun main() { + level1 { it * 2} + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda but the lambda is consumed by a function called from the consuming method LONG" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(b: Long, f: (Int) -> Int, a: Long) { + println(level2(f, 26L, 12L, 16L)) + } + + fun level2(f: (Int) -> Int, c: Long, a: Long, b: Long): Int { + println(a) + return f(5) + } + + fun main() { + level1(26L, { it * 2}, 7L) + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda but the lambda is consumed by multiple other methods from the original consuming method" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(f: (Int) -> Int) { + println(level2_1(f)) + println(level2_2(f)) + } + + fun level2_1(f: (Int) -> Int): Int { + return f(5) + } + + fun level2_2(f: (Int) -> Int): Int { + return f(7) + } + + fun main() { + level1 { it * 2} + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda but the lambda at even more levels" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(f: (Int) -> Int) { + println(level2_1(f)) + println(level2_2(f)) + } + + fun level2_1(f: (Int) -> Int): Int { + return f(5) + } + + fun level2_2(f: (Int) -> Int): Int { + return level3(f) + } + + fun level3(f: (Int) -> Int): Int { + return f(7) + } + + fun main() { + level1 { it * 2} + } + """ + ) + + onlyTestRunning(code) + } + + "One lambda but the lambda at even more levels + private" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(f: (Int) -> Int) { + println(level2_1(f)) + println(level2_2(f)) + } + + fun level2_1(f: (Int) -> Int): Int { + return f(5) + } + + private fun level2_2(f: (Int) -> Int): Int { + return level3(f) + } + + fun level3(f: (Int) -> Int): Int { + return f(7) + } + + fun main() { + level1 { it * 2} + } + """ + ) + + onlyTestRunning(code) + } + + "Lambda in a variable" { + val code = KotlinSource( + "Main.kt", + """ + fun level1(f: (Int) -> Int) { + val x = 5 * f(2) + println(x) + val renamedFunction = f + println(level2_2(f)) + println(level2_1(renamedFunction)) + } + + fun level2_1(f: (Int) -> Int): Int { + return f(5) + } + + fun level2_2(f: (Int) -> Int): Int { + return level3(f) + } + + fun level3(f: (Int) -> Int): Int { + return f(7) + } + + + fun main() { + println("Hello there!") + level1 { it * 2} + } + """.trimIndent() + ) + + onlyTestRunning(code) + } + + "Lambda in a variable more advanced" { + val code = KotlinSource( + "Main.kt", + """ + // Note: The lambda x currently does not get inlined because it is not detected in the usage pattern finder + // but in the future if we do try inlining it, it cannot be inlined if it is used in println but that's very + // uncommon anyway and only used for debugging purposes. + fun level1(f: (Int) -> Int) { + var x : (Int) -> Int = { it * 2 } + println(x) + val f2 = f + println(level2_2(f)) + println(level2_1(f2)) + println(level2_1(x)) + } + + fun level2_1(f: (Int) -> Int): Int { + return f(5) + } + + fun level2_2(f: (Int) -> Int): Int { + return level3(f) + } + + fun level3(f: (Int) -> Int): Int { + return f(7) + } + + + fun main() { + level1 { it * 2} + } + """.trimIndent() + ) + + onlyTestRunning(code) + } + + "Lambda in a variable multiple usages" { + val code = KotlinSource( + "Main.kt", + """ + fun foo(i: Int, f: (Int) -> Int) { + println(f(i)) + } + + fun main() { + val x : (Int) -> Int = { it * 2 } + foo(1, x) + foo(2, x) + } + """.trimIndent() + ) + + // TODO: Currently broken due to a bug in the proguard-core test utils + //compareOutputAndMainInstructions(code, listOf("iconst_1", "invokestatic", "iconst_2", "invokestatic", "return")) + } + + "Lambda in a variable multiple usages with a sum of 2 functions that take lambdas" { + val code = KotlinSource( + "Main.kt", + """ + fun foo(i: Int, f: (Int) -> Int): Int { + return f(i) + } + + fun main() { + val x : (Int) -> Int = { it * 2 } + val y : (Int) -> Int = { it * 2 } + println(foo(2, y)) + println(foo(1, x) + foo(2, x)) + } + """.trimIndent() + ) + + compareOutputAndMainInstructions(code, listOf( + "iconst_2", + "invokestatic", + "istore_2", + "getstatic", + "iload_2", + "invokevirtual", + "iconst_1", + "invokestatic", + "iconst_2", + "invokestatic", + "iadd", + "istore_2", + "getstatic", + "iload_2", + "invokevirtual", + "return" + )) + } + + "Don't inline lambda in a variable that changes depending on an if statement" { + val code = KotlinSource( + "Main.kt", + """ + fun foo(i: Int, f: (Int) -> Int) { + println(f(i)) + } + + fun main() { + var x : (Int) -> Int = { it * 2 } + + var i = 0 + if (i * 5 == 0) { + x = { it * 3 } + } + + foo(1, x) + } + """.trimIndent() + ) + + compareOutputAndMainInstructions(code, listOf( + "getstatic", + "checkcast", + "astore_0", + "iconst_0", + "istore_1", + "iload_1", + "iconst_5", + "imul", + "ifne", + "getstatic", + "checkcast", + "astore_0", + "iconst_1", + "aload_0", + "invokestatic", + "return" + ), false) + } + + "Not using lambda return value" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } +}) diff --git a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt new file mode 100644 index 000000000..726c11dc7 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt @@ -0,0 +1,419 @@ +import proguard.optimize.inline.lambda_locator.Util +import io.kotest.assertions.withClue +import io.kotest.matchers.shouldBe +import proguard.optimize.inline.LambdaInliner +import proguard.AppView +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.* +import proguard.classfile.constant.visitor.ConstantVisitor +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.ClassReferenceInitializer +import proguard.classfile.visitor.AllMethodVisitor +import proguard.classfile.visitor.ClassPoolFiller +import proguard.classfile.visitor.MultiClassVisitor +import proguard.evaluation.BasicInvocationUnit +import proguard.evaluation.PartialEvaluator +import proguard.evaluation.value.TypedReferenceValueFactory +import proguard.io.util.IOUtil +import proguard.optimize.info.ProgramClassOptimizationInfoSetter +import proguard.optimize.info.ProgramMemberOptimizationInfoSetter +import proguard.optimize.peephole.LineNumberLinearizer +import proguard.preverify.CodePreverifier +import proguard.testutils.ClassPoolBuilder +import proguard.testutils.PartialEvaluatorUtil +import proguard.testutils.TestSource +import java.util.regex.Pattern +import kotlin.math.pow +import kotlin.math.sqrt +import kotlin.system.measureTimeMillis + +fun compareOutputAndMainInstructions(code: TestSource, correctInstructions: List, checkInvokeRemoved: Boolean=true): ClassPool { + val mydata = System.getProperty("java.class.path") + val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") + val matcher = pattern.matcher(mydata) + matcher.find() + val libraryClassPool = Util.loadJar(matcher.group(0)) + val (classPool, _) = ClassPoolBuilder.fromSource(code) + + libraryClassPool.classesAccept(ClassPoolFiller(classPool)) + IOUtil.writeJar(classPool, "test-files/original.jar", "MainKt") + + val optimizationInfoInitializer = MultiClassVisitor( + ProgramClassOptimizationInfoSetter(), + AllMethodVisitor( + ProgramMemberOptimizationInfoSetter() + ) + ) + classPool.classesAccept(optimizationInfoInitializer) + + val appView = AppView(classPool, libraryClassPool) + + appView.programClassPool.classesAccept( + ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) + ) + + val inliner = LambdaInliner("Main**") + inliner.execute(appView) + + val linearizer = LineNumberLinearizer() + linearizer.execute(appView) + + classPool.classesAccept("Main**", AllMethodVisitor(AllAttributeVisitor(CodePreverifier(false)))) + + IOUtil.writeJar(classPool, "test-files/result.jar", "MainKt") + + val valueFactory = TypedReferenceValueFactory() + val invocationUnit = BasicInvocationUnit(valueFactory) + val partialEvaluator = PartialEvaluator(valueFactory, invocationUnit, true) + //test + val (instructions, _) = PartialEvaluatorUtil.evaluate( + "MainKt", + "main", + "()V", + classPool, + partialEvaluator + ) + + val procOriginal = ProcessBuilder("java", "-jar", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + val resultOriginal = procOriginal.waitFor() + + val procGenerated = ProcessBuilder("java", "-jar", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + val result = procGenerated.waitFor() + + //clean up + ProcessBuilder("rm", "-f", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + ProcessBuilder("rm", "-f", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + + val generatedErrorString = procGenerated.errorStream.bufferedReader().readText() + if (generatedErrorString.isNotEmpty()) { + println("Error output from running jar:") + println(generatedErrorString) + } + withClue("Exit code should be the same!") { + result shouldBe resultOriginal + } + procGenerated.inputStream.bufferedReader().readText() shouldBe procOriginal.inputStream.bufferedReader().readText() + generatedErrorString shouldBe procOriginal.errorStream.bufferedReader().readText() + + withClue({ "Instructions: $instructions\nExpected instructions: $correctInstructions" }) { + instructions.size shouldBe correctInstructions.size + } + + val lambdaMethods = mutableSetOf("main") + + + val c: Clazz = classPool.getClass("MainKt") + var i = 0 + while (i < correctInstructions.size) { + instructions[i].second.name shouldBe correctInstructions[i] + + //find all functions called in main method + if (instructions[i].second.opcode == Instruction.OP_INVOKESTATIC || instructions[i].second.opcode == Instruction.OP_INVOKEVIRTUAL) { + c.constantPoolEntryAccept(((instructions[i].second) as ConstantInstruction).constantIndex, object: ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {} + override fun visitMethodrefConstant(clazz: Clazz?, methodrefConstant: MethodrefConstant?) { + methodrefConstant?.nameAndTypeIndex?.let { + clazz?.constantPoolEntryAccept(it, object: ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {} + override fun visitNameAndTypeConstant(clazz: Clazz?, nameAndTypeConstant: NameAndTypeConstant?) { + if (nameAndTypeConstant != null) { + clazz?.constantPoolEntryAccept(nameAndTypeConstant.u2nameIndex, object: ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz?, constant: Constant?) {} + override fun visitUtf8Constant(clazz: Clazz?, utf8Constant: Utf8Constant?) { + if (utf8Constant != null) { + lambdaMethods.add(utf8Constant.string) + } + } + }) + } + } + }) + } + } + }) + } + i++ + } + + if (checkInvokeRemoved) { + classPool.classesAccept("MainKt", AllMethodVisitor(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 + ) { + //only check functions that are called in main method + if (constantInstruction.opcode == Instruction.OP_INVOKEINTERFACE && lambdaMethods.contains(method.getName(clazz))) { + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, object : ConstantVisitor { + override fun visitAnyConstant(clazz: Clazz, constant: Constant) {} + + override fun visitInterfaceMethodrefConstant( + clazz: Clazz, + interfaceMethodrefConstant: InterfaceMethodrefConstant + ) { + if (interfaceMethodrefConstant.referencedClass != null) { + withClue("There should be no more invoke usage after inlining.") { + interfaceMethodrefConstant.referencedClass.name.startsWith("kotlin/jvm/functions/Function") shouldBe false + } + } + } + }) + } + } + })))) + } + + return classPool +} + +fun onlyTestRunning(code: TestSource) { + val mydata = System.getProperty("java.class.path") + val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") + val matcher = pattern.matcher(mydata) + matcher.find() + val libraryClassPool = Util.loadJar(matcher.group(0)) + val (classPool, _) = ClassPoolBuilder.fromSource(code) + + libraryClassPool.classesAccept(ClassPoolFiller(classPool)) + IOUtil.writeJar(classPool, "test-files/original.jar", "MainKt") + + val optimizationInfoInitializer = MultiClassVisitor( + ProgramClassOptimizationInfoSetter(), + AllMethodVisitor( + ProgramMemberOptimizationInfoSetter() + ) + ) + classPool.classesAccept(optimizationInfoInitializer) + + val appView = AppView(classPool, libraryClassPool) + + appView.programClassPool.classesAccept( + ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) + ) + + val inliner = LambdaInliner("Main**") + inliner.execute(appView) + + val linearizer = LineNumberLinearizer() + linearizer.execute(appView) + + classPool.classesAccept("Main**", AllMethodVisitor(AllAttributeVisitor(CodePreverifier(false)))) + + IOUtil.writeJar(classPool, "test-files/result.jar", "MainKt") + + val valueFactory = TypedReferenceValueFactory() + val invocationUnit = BasicInvocationUnit(valueFactory) + val partialEvaluator = PartialEvaluator(valueFactory, invocationUnit, true) + //test + val (instructions, _) = PartialEvaluatorUtil.evaluate( + "MainKt", + "main", + "()V", + classPool, + partialEvaluator + ) + + val procOriginal = ProcessBuilder("java", "-jar", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + val resultOriginal = procOriginal.waitFor() + + val procGenerated = ProcessBuilder("java", "-jar", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + + val result = procGenerated.waitFor() + + //clean up + ProcessBuilder("rm", "-f", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + ProcessBuilder("rm", "-f", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + + val generatedErrorString = procGenerated.errorStream.bufferedReader().readText() + if (generatedErrorString.isNotEmpty()) { + println("Error output from running jar:") + println(generatedErrorString) + } + withClue("Exit code should be the same!") { + result shouldBe resultOriginal + } + procGenerated.inputStream.bufferedReader().readText() shouldBe procOriginal.inputStream.bufferedReader().readText() + generatedErrorString shouldBe procOriginal.errorStream.bufferedReader().readText() +} + +fun testPerf(code: TestSource, inlineCode: TestSource, cleanUp: Boolean): Triple, MutableList, MutableList> { + val mydata = System.getProperty("java.class.path") + val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") + val matcher = pattern.matcher(mydata) + matcher.find() + val libraryClassPool = Util.loadJar(matcher.group(0)) + val (classPool, _) = ClassPoolBuilder.fromSource(code) + val (inlinedClassPool, _) = ClassPoolBuilder.fromSource(inlineCode) + + libraryClassPool.classesAccept(ClassPoolFiller(classPool)) + libraryClassPool.classesAccept(ClassPoolFiller(inlinedClassPool)) + IOUtil.writeJar(classPool, "test-files/original.jar", "MainKt") + IOUtil.writeJar(inlinedClassPool, "test-files/originalInlined.jar", "MainKt") + + val optimizationInfoInitializer = MultiClassVisitor( + ProgramClassOptimizationInfoSetter(), + AllMethodVisitor( + ProgramMemberOptimizationInfoSetter() + ) + ) + classPool.classesAccept(optimizationInfoInitializer) + + val inliner = LambdaInliner("Main**") + val appView = AppView(classPool, libraryClassPool) + inliner.execute(appView) + + val linearizer = LineNumberLinearizer() + linearizer.execute(appView) + + classPool.classesAccept("Main**", AllMethodVisitor(AllAttributeVisitor(CodePreverifier(false)))) + + IOUtil.writeJar(classPool, "test-files/result.jar", "MainKt") + + //test + val originalTimes = mutableListOf() + var i = 0 + while (i < 10000) { + i++ + originalTimes.add(measureTimeMillis { + ProcessBuilder("java", "-jar", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + }) + } + + val originalInlinedTimes = mutableListOf() + i = 0 + while (i < 10000) { + i++ + originalInlinedTimes.add(measureTimeMillis { + ProcessBuilder("java", "-jar", "test-files/originalInlined.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + }) + } + + val generatedTimes = mutableListOf() + i = 0 + while (i < 10000) { + i++ + generatedTimes.add(measureTimeMillis { + ProcessBuilder("java", "-jar", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + }) + } + + + //clean up + if (cleanUp) { + ProcessBuilder("rm", "-f", "test-files/original.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + ProcessBuilder("rm", "-f", "test-files/originalInlined.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + ProcessBuilder("rm", "-f", "test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + } + + val originalMean = originalTimes.average() + val originalInlinedMean = originalInlinedTimes.average() + val generatedMean = generatedTimes.average() + println("Original code : ${originalMean}ms (stdev = ${stdev(originalTimes)})") + println("Original inlined code : ${originalInlinedMean}ms (stdev = ${stdev(originalInlinedTimes)})") + println("Generated code : ${generatedMean}ms (stdev = ${stdev(generatedTimes)})") + if (originalMean < generatedMean) { + println("Generated is slower by ${generatedMean - originalMean}ms") + } else { + println("Original is slower by ${originalMean - generatedMean}ms") + } + + + println("ORIGINAL : ") + println(originalTimes) + println("ORIGINAL INLINED : ") + println(originalInlinedTimes) + println("GENERATED : ") + println(generatedTimes) + return Triple(originalTimes, originalInlinedTimes, generatedTimes) +} + +fun stdev(numArray: MutableList): Double { + var sum = 0.0 + var standardDeviation = 0.0 + + for (num in numArray) { + sum += num + } + + val mean = sum / numArray.size + + for (num in numArray) { + standardDeviation += (num - mean).pow(2.0) + } + + return sqrt(standardDeviation / numArray.size) +} diff --git a/base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt new file mode 100644 index 000000000..ffd5650d2 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt @@ -0,0 +1,105 @@ +package proguard.lambdaInline + +import compareOutputAndMainInstructions +import io.kotest.core.spec.style.FreeSpec +import proguard.testutils.KotlinSource + +class ThreeLambdasInArgsTest: FreeSpec({ + "Three lambda in args, basic case" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int, Int) -> Int, c: (String, Boolean, Int) -> Boolean) { + println(a(3)) + println(b(5,7)) + println(c("Hello", true, 0)) + } + + fun main() { + test({ a: Int -> a * a }, {a : Int, b : Int -> a+b}, { a : String, b : Boolean, c : Int -> println(a); b }) + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Three lambdas in args and instruction before lambda function calls in main" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int, Int) -> Int, c: (String, Boolean, Int) -> Boolean) { + println(a(3)) + println(b(5,7)) + println(c("Hello", true, 0)) + } + + fun main() { + val x = 1 + val y = 5 + println(x + y) + test({ a: Int -> a * a }, {a : Int, b : Int -> a+b}, { a : String, b : Boolean, c : Int -> println(a); b}) + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf( + "iconst_1", + "istore_0", + "iconst_5", + "istore_1", + "iload_0", + "iload_1", + "iadd", + "istore_2", + "getstatic", + "iload_2", + "invokevirtual", + "invokestatic", + "return" + ) + ) + } + "Three lambdas in args and two are the same types" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int) -> Int, c: (String, Boolean, Int) -> Boolean) { + println(a(3)) + println(b(5)) + println(c("Hello", true, 0)) + } + + fun main() { + test({ a: Int -> a * a }, {a : Int -> a+a}, { a : String, b : Boolean, c : Int -> println(a); b}) + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Three lambdas in args and two are the same types shuffled" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + println(a(3)) + println(b("Hello", true, 0)) + println(c(5)) + } + + fun main() { + test({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> println(a); b}, {a : Int -> a+a}) + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } +}) diff --git a/base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt new file mode 100644 index 000000000..f1e68de35 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt @@ -0,0 +1,171 @@ +package proguard.lambdaInline + +import compareOutputAndMainInstructions +import io.kotest.core.spec.style.FreeSpec +import onlyTestRunning +import proguard.testutils.KotlinSource + +class TwoLambdasInArgsTest: FreeSpec({ + "Two lambda in args, basic case" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int, Int) -> Int) { + println(a(3)) + println(b(5,7)) + } + + fun main() { + test({ a: Int -> a * a }, {a : Int, b : Int -> a+b}) + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Two lambdas in args and instruction before lambda function calls in main" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int, Int) -> Int) { + println(a(3)) + println(b(5,7)) + } + + fun main() { + val x=1 + println(x) + test({ a: Int -> a * a }, {a : Int, b : Int -> a+b}) +} + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("iconst_1", "istore_0", "getstatic", "iload_0", "invokevirtual", "invokestatic", "return") + ) + } + + "Twice the same lambda types in args" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int) -> Int) { + println(a(3)) + println(b(5)) + println((a(12) - b(15))) + } + + fun main() { + test({ a: Int -> a * a }, {a : Int -> a+a}) + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Twice the same lambda types in args + one lambda with one arg" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int, b: (Int) -> Int) { + println(a(3)) + println(b(5)) + println((a(12) - b(15))) + } + + fun test(f: (Int) -> Int) { + println(f(3)) + } + + fun main() { + test {it * 2} + + test({ a: Int -> a * a }, {a : Int -> a + a}) + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "invokestatic", "return")) + } + + "Two lambdas but multiple other arguments before and after lambda arg" { + val code = KotlinSource( //create java file + "Main.kt", + """ + + + fun test(e:Int, b: Int, a: (Int) -> Int, c: Int, f: (Int) -> Int, d: Int) { + println(a(3)) + println(b) + println(c) + println(d) + println(e) + println(f(3)) + } + + fun main() { + test(12, 1, { a: Int -> a + a }, 123, { a: Int -> a * a }, 122) + } + """ + ) + + compareOutputAndMainInstructions( + code, + listOf("bipush", "iconst_1", "bipush", "bipush", "invokestatic", "return") + ) + } + + + "Twice the same lambda types in args multiple levels" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(a: (Int) -> Int, b: (Int) -> Int) { + level2(b) + level2(a) + } + + fun level2(f: (Int) -> Int) { + println(f(3)) + } + + fun main() { + level1({ a -> a * a }, {a -> a + a}) + } + """ + ) + + onlyTestRunning(code) + } + + "Twice the same lambda types in args multiple levels with one non lambda before the lambdas" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun level1(i1: Int, a: (Int) -> Int, b: (Int) -> Int) { + level2(b) + level2(a) + } + + fun level2(f: (Int) -> Int) { + println(f(3)) + } + + fun main() { + level1(7, { a -> a * a }, {a -> 26}) + } + """ + ) + + onlyTestRunning(code) + } +}) diff --git a/base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt b/base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt new file mode 100644 index 000000000..eeca8bc9a --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt @@ -0,0 +1,315 @@ +package proguard.lambdaInline + +import compareOutputAndMainInstructions +import io.kotest.core.spec.style.FreeSpec +import proguard.testutils.KotlinSource + +class TypeTest: FreeSpec ({ + "Byte" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Byte) -> Byte) { + println(a(12)) + } + + fun main() { + test { a: Byte -> (a + 1).toByte() } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Short" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Short) ->Short) { + println(a(12)) + } + + fun main() { + test { a: Short -> (a + 1).toShort() } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Int" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + println(a(12)) + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + + "Long" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Long) -> Long) { + println(a(12L)) + } + + fun main() { + test { a: Long -> a + 2L } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Long in consumingMethod args" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(c:Long, a: (Long) -> Long, b: Long) { + println(b) + println(c) + println(a(b)) + } + + fun main() { + test(42L, { a: Long -> a + 2L }, 12L) + } + """ + ) + compareOutputAndMainInstructions(code, listOf("ldc2_w", "ldc2_w", "invokestatic", "return")) + } + + "Float" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Float) -> Float) { + println(a(12f)) + } + + fun main() { + test { a: Float -> a + 2.3f } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Double" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Double) -> Double) { + println(a(3.14)) + } + + fun main() { + test { a: Double -> a + 2.3 } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Boolean" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Boolean) -> Boolean) { + println(a(true)) + } + + fun main() { + test { a: Boolean -> a and true } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Char" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Char) -> Char) { + println(a('j')) + } + + fun main() { + test { a: Char -> a.uppercaseChar() } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "String" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (String) -> String) { + println(a("Hello World!")) + } + + fun main() { + test { a: String -> a + " And you!" } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "ByteArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (ByteArray) -> ByteArray) { + println(a(byteArrayOf(1, 5, 3)).contentToString()) + } + + fun main() { + test { a: ByteArray -> byteArrayOf(1) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "ShortArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (ShortArray) -> ShortArray) { + println(a(shortArrayOf(1, 5, 3)).contentToString()) + } + + fun main() { + test { a: ShortArray -> shortArrayOf(1) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "IntArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (IntArray) -> IntArray) { + println(a(intArrayOf(1, 5, 3)).contentToString()) + } + + fun main() { + test { a: IntArray -> intArrayOf(1) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "LongArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (LongArray) -> LongArray) { + println(a(longArrayOf(1, 5, 3)).contentToString()) + } + + fun main() { + test { a: LongArray -> longArrayOf(1) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "FloatArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (FloatArray) -> FloatArray) { + println(a(floatArrayOf(1.12f, 5f, 3.89f)).contentToString()) + } + + fun main() { + test { a: FloatArray -> floatArrayOf(1.2f) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "DoubleArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (DoubleArray) -> DoubleArray) { + println(a(doubleArrayOf(1.12, 5.0, 3.89)).contentToString()) + } + + fun main() { + test { a: DoubleArray -> doubleArrayOf(1.2) + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "CharArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (CharArray) -> CharArray) { + println(a(charArrayOf('a', 't', '%')).contentToString()) + } + + fun main() { + test { a: CharArray -> charArrayOf('5') + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "StringArray" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Array) -> Array) { + println(a(arrayOf("raspberry", "pear", "banana")).contentToString()) + } + + fun main() { + test { a: Array -> arrayOf("apple") + a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + + "Any" { + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Any) -> Any) { + println(a(arrayOf("raspberry", "pear", "banana"))::class) + } + + fun main() { + test { a: Any -> a } + } + """ + ) + compareOutputAndMainInstructions(code, listOf("invokestatic", "return")) + } + +}) \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 27982f0f1..5bfe5fd8f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,7 +1,7 @@ proguardVersion = 7.3.3 # The version of ProGuardCORE that sub-projects are built with -proguardCoreVersion = 9.0.9 +proguardCoreVersion = 9.0.10 gsonVersion = 2.9.0 kotlinVersion = 1.7.20 target = 1.8 From 043be3a6418ed5825fd599a22184c6b7677ad4bc Mon Sep 17 00:00:00 2001 From: MaartenS Date: Fri, 4 Aug 2023 12:59:28 +0200 Subject: [PATCH 02/72] Fixes from inlining lambdas in the klox compiler We ran the inliner on a real world application and found a lot of issues, this commit aims to resolve those. --- .../optimize/inline/BaseLambdaInliner.java | 54 ++++--- .../optimize/inline/CastPatternRemover.java | 56 +++++++ .../optimize/inline/LambdaInliner.java | 6 +- .../optimize/inline/LambdaUsageFinder.java | 59 +++++-- .../optimize/inline/LocalUsageRemover.java | 10 +- .../optimize/inline/NullCheckRemover.java | 11 +- .../optimize/inline/RecursiveInliner.java | 152 +++++++++++++++++- .../optimize/inline/RefMethodFinder.java | 6 +- .../java/proguard/optimize/inline/Util.java | 2 + .../proguard/lambdaInline/AdvancedTest.kt | 125 ++++++++++++++ 10 files changed, 426 insertions(+), 55 deletions(-) create mode 100644 base/src/main/java/proguard/optimize/inline/CastPatternRemover.java diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 3c01df209..18de8d9a0 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -6,17 +6,16 @@ import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; +import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.InterfaceMethodrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.ClassEditor; -import proguard.classfile.editor.CodeAttributeEditor; -import proguard.classfile.editor.ConstantPoolEditor; -import proguard.classfile.editor.MethodCopier; +import proguard.classfile.editor.*; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.AllInstructionVisitor; +import proguard.classfile.instruction.visitor.InstructionCounter; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassReferenceInitializer; @@ -99,12 +98,12 @@ public void visitAnyMember(Clazz clazz, Member member) { DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); staticInvokeMethod = descriptorModifier.modify(method, desc -> { - // The method becomes static - String d = desc.replace("(", "(Ljava/lang/Object;"); - // Change return type if it has an effect on the stack size - d = d.replace(")Ljava/lang/Double;", ")D"); - d = d.replace(")Ljava/lang/Float;", ")F"); - return d.replace(")Ljava/lang/Long;", ")J"); + // The method becomes static + String d = desc.replace("(", "(Ljava/lang/Object;"); + // Change return type if it has an effect on the stack size + d = d.replace(")Ljava/lang/Double;", ")D"); + d = d.replace(")Ljava/lang/Float;", ")F"); + return d.replace(")Ljava/lang/Long;", ")J"); } , true); @@ -131,19 +130,7 @@ public void visitAnyMember(Clazz clazz, Member member) { new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); // Remove return value's casting from staticInvokeMethod - staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - int len = codeAttribute.u4codeLength; - CodeAttributeEditor codeAttributeEditorStaticInvoke = new CodeAttributeEditor(); - codeAttributeEditorStaticInvoke.reset(codeAttribute.u4codeLength); - codeAttribute.instructionsAccept(clazz, method, len - 4, len, new CastRemover(codeAttributeEditorStaticInvoke)); - codeAttributeEditorStaticInvoke.visitCodeAttribute(clazz, method, codeAttribute); - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - })); + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AllInstructionVisitor(new CastPatternRemover()))); if (referencedInterfaceConstant != null) { InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(invokeMethodDescriptor); @@ -195,7 +182,8 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} classEditor.removeMethod(staticInvokeMethod); // Remove checkNotNullParameter() call because arguments that are lambdas will be removed - copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new AllInstructionVisitor(new NullCheckRemover(sizeAdjustedLamdaIndex)))); + InstructionCounter removedNullCheckInstrCounter = new InstructionCounter(); + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLamdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); //remove inlined lambda from arguments through the descriptor Method methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { @@ -211,7 +199,23 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} // Remove one of the arguments - methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLamdaIndex)); + lambda.clazz().constantPoolEntryAccept(lambda.constantInstruction().constantIndex, new ConstantVisitor() { + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + System.out.println(fieldrefConstant); + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); + int lambdaInstanceFieldIndex = constantPoolEditor.addFieldrefConstant(fieldrefConstant.referencedClass, fieldrefConstant.referencedField); + + // If the argument had a null check removed then it was non-nullable, so we can replace usages with null + // just fine because no one will ever null check on it. Even if the programmer did do a null check on it + // the kotlin compiler will remove it. + Instruction replacementInstruction = removedNullCheckInstrCounter.getCount() != 0 ? + new VariableInstruction(Instruction.OP_ACONST_NULL) : + new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); + methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLamdaIndex, replacementInstruction)); + } + }); + appView.programClassPool.classesAccept(new AccessFixer()); // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace // calls to the old function to calls to this new function. diff --git a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java new file mode 100644 index 000000000..9081f22b3 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java @@ -0,0 +1,56 @@ +package proguard.optimize.inline; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.constant.Constant; +import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.visitor.ConstantVisitor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.InstructionSequenceBuilder; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.visitor.InstructionVisitor; +import proguard.classfile.util.InstructionSequenceMatcher; +import proguard.classfile.visitor.ClassPrinter; + +import java.io.PrintWriter; +import java.io.StringWriter; + +public class CastPatternRemover implements InstructionVisitor { + private final Logger logger = LogManager.getLogger(this.getClass()); + private final InstructionSequenceMatcher insSeqMatcher; + + public CastPatternRemover() { + InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(); + Constant[] constants = ____.constants(); + Instruction[] pattern = ____.invokestatic(InstructionSequenceMatcher.X).areturn().__(); + this.insSeqMatcher = new InstructionSequenceMatcher(constants, pattern); + } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { + instruction.accept(clazz, method, codeAttribute, offset, insSeqMatcher); + if (insSeqMatcher.isMatching()) { + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(codeAttribute.u4codeLength); + int constantIndex = insSeqMatcher.matchedConstantIndex(InstructionSequenceMatcher.X); + clazz.constantPoolEntryAccept(constantIndex, new ConstantVisitor() { + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + if (methodrefConstant.getName(clazz).equals("valueOf")) { + if (logger.isDebugEnabled()) { + StringWriter stringWriter = new StringWriter(); + instruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter(new PrintWriter(stringWriter))); + logger.debug("Removing " + stringWriter); + } + + codeAttributeEditor.deleteInstruction(insSeqMatcher.matchedInstructionOffset(0)); + } + } + }); + codeAttribute.accept(clazz, method, codeAttributeEditor); + } + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 629bcfb58..64ed0ec87 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -45,14 +45,15 @@ public void execute(AppView appView) { // We didn't inline anything so no need to change any call instructions. if (inlinedLambamethod == null) { inlinedAllUsages = false; - return; + return false; } if (possibleLambdas.size() > 1) { // This lambda is part of a collection of lambdas that might potentially be used, but we do not know which one is actually used. Because of that we cannot inline it. inlinedAllUsages = false; - return; + return false; } + InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); codeAttributeEditor.reset(consumingCallCodeAttribute.u4codeLength); @@ -83,6 +84,7 @@ public void execute(AppView appView) { } codeAttributeEditor.visitCodeAttribute(consumingCallClazz, consumingCallmethod, consumingCallCodeAttribute); + return true; })); /* diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index eaa06abef..e9a2fffc0 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -13,20 +13,19 @@ import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.SimpleInstruction; import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassUtil; +import proguard.classfile.util.InitializationUtil; import proguard.classfile.visitor.ClassPrinter; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.optimize.inline.lambda_locator.Lambda; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -40,6 +39,7 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, public FieldrefConstant referencedFieldConstant; private final boolean inlineFromFields; private final boolean inlineFromMethods; + private boolean changed = false; private final int[] typedReturnInstructions = new int[] { Instruction.OP_IRETURN, Instruction.OP_LRETURN, @@ -65,7 +65,7 @@ public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, Ap @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); - codeAttribute.instructionsAccept(clazz, method, + /*codeAttribute.instructionsAccept(clazz, method, new InstructionOpCodeFilter( new int[] { Instruction.OP_INVOKESTATIC, @@ -78,7 +78,31 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt }, this ) - ); + );*/ + long lastTime = System.currentTimeMillis(); + int offset = 0; + while (offset < codeAttribute.u4codeLength) + { + // Note that the instruction is only volatile. + Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); + int instructionLength = instruction.length(offset); + HashSet opcodes = new HashSet<>(Arrays.asList(Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL)); + if (opcodes.contains(instruction.opcode)) { + changed = false; + instruction.accept(clazz, method, codeAttribute, offset, this); + if (changed) { + System.out.println("Start another iteration"); + //visitCodeAttribute(clazz, method, codeAttribute); + codeAttribute.accept(clazz, method, this); + break; + } + } + if (System.currentTimeMillis() > lastTime + 2000) { + System.out.printf("Progress %s/%s\n", offset, codeAttribute.u4codeLength); + lastTime = System.currentTimeMillis(); + } + offset += instructionLength; + } } @Override @@ -97,19 +121,28 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c public void visitSimpleInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, SimpleInstruction simpleInstruction) {} private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, ConstantInstruction consumingMethodCallInstruction, int offset, Function condition) { + clazz.constantPoolEntryAccept(consumingMethodCallInstruction.constantIndex, this); + + //String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); + String methodDescriptor = methodrefConstant.getType(clazz); + + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + if (!methodDescriptor.contains("Lkotlin/jvm/functions/Function") && !methodDescriptor.contains("Ljava/lang/Object")) + return; + + if (methodrefConstant.referencedMethod == null) + System.out.println(methodrefConstant.getClassName(clazz) + "#" + methodrefConstant.getName(clazz)); + + System.out.println(methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass)); + System.out.println("--------Start----------"); System.out.println(consumingMethodCallInstruction); - clazz.constantPoolEntryAccept(consumingMethodCallInstruction.constantIndex, this); - partialEvaluator = new PartialEvaluator(); partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); TracedStack tracedStack = partialEvaluator.getStackBefore(offset); System.out.println(tracedStack); - System.out.println(methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass)); - String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); - int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); for (int argIndex = 0; argIndex < argCount; argIndex++) { int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); @@ -128,7 +161,7 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, } if (match) { - lambdaUsageHandler.handle( + changed = lambdaUsageHandler.handle( targetLambda, methodrefConstant.referencedClass, methodrefConstant.referencedMethod, @@ -167,6 +200,6 @@ public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant } public interface LambdaUsageHandler { - void handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas); + boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas); } } diff --git a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java index c3e9c54ad..c746beb7e 100644 --- a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java +++ b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java @@ -17,10 +17,16 @@ public class LocalUsageRemover implements MemberVisitor { private final CodeAttributeEditor codeAttributeEditor; private final int argumentIndex; + private final Instruction replacementInstruction; - public LocalUsageRemover(CodeAttributeEditor codeAttributeEditor, int argumentIndex) { + public LocalUsageRemover(CodeAttributeEditor codeAttributeEditor, int argumentIndex, Instruction replacementInstruction) { this.codeAttributeEditor = codeAttributeEditor; this.argumentIndex = argumentIndex; + this.replacementInstruction = replacementInstruction; + } + + public LocalUsageRemover(CodeAttributeEditor codeAttributeEditor, int argumentIndex) { + this(codeAttributeEditor, argumentIndex, new VariableInstruction(Instruction.OP_ACONST_NULL)); } @Override @@ -42,7 +48,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c if (variableInstruction.isStore()) { codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_POP)); } else { - codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_ACONST_NULL)); + codeAttributeEditor.replaceInstruction(offset, replacementInstruction); } } else if (variableInstruction.variableIndex > argumentIndex){ codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(variableInstruction.canonicalOpcode(), variableInstruction.variableIndex - 1, variableInstruction.constant)); diff --git a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java index d0108e5d7..402a33971 100644 --- a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java @@ -22,8 +22,9 @@ class NullCheckRemover implements InstructionVisitor { Constant[] constants; private final int argumentIndex; private final InstructionVisitor extraInstructionVisitor; + private final CodeAttributeEditor codeAttributeEditor; - public NullCheckRemover(int argumentIndex, InstructionVisitor extraInstructionVisitor) { + public NullCheckRemover(int argumentIndex, CodeAttributeEditor codeAttributeEditor, InstructionVisitor extraInstructionVisitor) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(); @@ -36,10 +37,11 @@ public NullCheckRemover(int argumentIndex, InstructionVisitor extraInstructionVi this.insSeqMatcher = new InstructionSequenceMatcher(constants, pattern); this.argumentIndex = argumentIndex; this.extraInstructionVisitor = extraInstructionVisitor; + this.codeAttributeEditor = codeAttributeEditor; } - public NullCheckRemover(int argumentIndex) { - this(argumentIndex, null); + public NullCheckRemover(int argumentIndex, CodeAttributeEditor codeAttributeEditor) { + this(argumentIndex, codeAttributeEditor, null); } public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { @@ -58,15 +60,12 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst methodrefConstant.getName(clazz).equals("checkNotNullParameter") && methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V") ) { - CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); - codeAttributeEditor.reset(codeAttribute.u4codeLength); for (int insIndex = 0; insIndex < pattern.length; insIndex++) { int insOffset = insSeqMatcher.matchedInstructionOffset(insIndex); if (extraInstructionVisitor != null) extraInstructionVisitor.visitAnyInstruction(clazz, method, codeAttribute, insSeqMatcher.matchedInstructionOffset(insIndex), InstructionFactory.create(codeAttribute.code, insOffset)); codeAttributeEditor.deleteInstruction(insOffset); } - codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } } }); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index d02258fcb..a98d17945 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -4,15 +4,18 @@ import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.BootstrapMethodsAttribute; import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.editor.ClassEditor; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; @@ -28,7 +31,7 @@ * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. */ -public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor { +public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor, ConstantVisitor { private final AppView appView; private Clazz consumingClazz; private Method copiedConsumingMethod; @@ -36,10 +39,12 @@ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, M private final boolean isStatic; private final Lambda lambda; private final boolean enableRecursiveMerging; - private final PartialEvaluator partialEvaluator; + private PartialEvaluator partialEvaluator; + private final CodeAttributeEditor codeAttributeEditor; private Clazz referencedClass; private Method referencedMethod; private int callOffset; + private boolean changed = false; /** * @param originalConsumingMethod The original consuming method reference, this is used to detect recursion. @@ -53,6 +58,7 @@ public RecursiveInliner(AppView appView, Method originalConsumingMethod, Lambda this.lambda = lambda; this.enableRecursiveMerging = enableRecursiveMerging; this.partialEvaluator = new PartialEvaluator(); + this.codeAttributeEditor = new CodeAttributeEditor(); this.referencedMethod = null; } @@ -67,7 +73,7 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.accept(clazz, method, new ClassPrinter()); partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); - codeAttribute.instructionsAccept(clazz, method, + /*codeAttribute.instructionsAccept(clazz, method, new InstructionOpCodeFilter(new int[] {Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL, Instruction.OP_INVOKESPECIAL}, new InstructionVisitor() { @Override @@ -105,7 +111,137 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst } }); } - })); + }));*/ + + int offset = 0; + + while (offset < codeAttribute.u4codeLength) + { + // Note that the instruction is only volatile. + Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); + int instructionLength = instruction.length(offset); + changed = false; + instruction.accept(clazz, method, codeAttribute, offset, new InstructionOpCodeFilter( + new int[] { + Instruction.OP_INVOKESTATIC, + Instruction.OP_INVOKEVIRTUAL, + Instruction.OP_INVOKESPECIAL, + Instruction.OP_INVOKEDYNAMIC, + Instruction.OP_INVOKEINTERFACE + }, + new InstructionVisitor() { + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + if (changed) + return; + System.out.println("At " + constantInstruction); + TracedStack tracedStack = partialEvaluator.getStackBefore(offset); + + if (tracedStack == null) + return; + + System.out.println("Stack size " + tracedStack.size()); + if (tracedStack.size() <= 0) + return; + + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + handle(methodrefConstant); + } + + @Override + public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { + System.out.println(invokeDynamicConstant); + // MethodRefTraveler + + clazz.attributesAccept(new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { + @Override + public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { + bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (clazz1, bootstrapMethodInfo) -> { + System.out.println(bootstrapMethodInfo); + + ProgramClass programClass = (ProgramClass) clazz; + MethodHandleConstant bootstrapMethodHandle = + (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); + + if (bootstrapMethodHandle.getClassName(clazz).equals("java/lang/invoke/LambdaMetafactory")) + { + MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) clazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); + referencedMethod = new RefMethodFinder(clazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); + referencedClass = clazz; + System.out.println(referencedMethod); + + if (changed) + return; + + String methodDescriptor = referencedMethod.getDescriptor(clazz); + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + // We will now check if any of the arguments has a value that comes from the lambda parameters + for (int argIndex = 0; argIndex < argCount; argIndex++) { + System.out.println("123Descriptor " + referencedMethod.getDescriptor(clazz) + " " + argIndex + " " + argCount); + System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(offset)); + constantInstruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); + codeAttribute.accept(clazz, method, new ClassPrinter()); + int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); + int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + callOffset = offset; + codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); + if (changed) { + break; + } + } + } + }); + } + })); + } + + @Override + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { + handle(interfaceMethodrefConstant); + } + + public void handle(AnyMethodrefConstant methodrefConstant) { + if (changed) + return; + + if (methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && + methodrefConstant.getName(clazz).equals("checkNotNullParameter") && + methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) + return; + + String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + boolean referencedMethodStatic = (methodrefConstant.referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + // We will now check if any of the arguments has a value that comes from the lambda parameters + for (int argIndex = 0; argIndex < argCount; argIndex++) { + System.out.println("123Descriptor " + methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass) + " " + argIndex + " " + argCount); + System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(offset)); + constantInstruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); + codeAttribute.accept(clazz, method, new ClassPrinter()); + int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); + int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + referencedMethod = methodrefConstant.referencedMethod; + referencedClass = methodrefConstant.referencedClass; + callOffset = offset; + codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); + if (changed) { + break; + } + } + } + }); + } + } + )); + if (changed) { + offset = 0; + continue; + } + offset += instructionLength; + } } @Override @@ -117,6 +253,10 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c System.out.println("Value from " + variableInstruction + " " + variableInstruction.variableIndex); Instruction sourceInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); + if (sourceInstruction == null) { + throw new CannotInlineException("Argument has multiple source locations, cannot inline lambda!"); + } + System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; @@ -126,7 +266,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { - throw new CannotInlineException("Cannot inline lambda into recursive function"); + throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); } } } diff --git a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java index 4f3a38c9a..e3481da42 100644 --- a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java +++ b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java @@ -18,7 +18,11 @@ public RefMethodFinder(Clazz clazz) { } public Method findReferencedMethod(ConstantInstruction constantInstruction) { - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ReferencedMemberVisitor(new MemberVisitor() { + return findReferencedMethod(constantInstruction.constantIndex); + } + + public Method findReferencedMethod(int constantIndex) { + clazz.constantPoolEntryAccept(constantIndex, new ReferencedMemberVisitor(new MemberVisitor() { @Override public void visitAnyMember(Clazz clazz, Member member) {} diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index 66afe4c26..1af23b051 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -25,6 +25,8 @@ public class Util { */ public static Instruction traceParameter(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { List trace = traceParameterOffset(partialEvaluator, codeAttribute, offset); + if (trace == null) + return null; return trace.get(trace.size() - 1).instruction(); } diff --git a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt index 34fe4b22a..46976dd55 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt @@ -279,5 +279,130 @@ class AdvancedTest: FreeSpec ({ compareOutputAndMainInstructions(code, listOf("iconst_5", "getstatic", "checkcast", "invokestatic", "invokestatic", "return"), false) } + "Using a kotlin lambda within a invokedynamic lambda" { + val code = KotlinSource( + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(7) + + useInterfaceLambda(Lambda { + a(5) + }) + } + + fun interface Lambda { + fun run() + } + + fun useInterfaceLambda(lambda: Lambda) { + lambda.run() + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("getstatic", "checkcast", "invokestatic", "return"), false) + } + + "Lambda used by invokeinterface" { + val code = KotlinSource( + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(7) + + val x = Lambda { + it(5) + } + + x.run(a) + } + + fun interface Lambda { + fun run(a: (Int) -> Int) + } + + fun main() { + test { a: Int -> (a * a) + 1 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("getstatic", "checkcast", "invokestatic", "return"), false) + } + + "Chain test" { + val code = KotlinSource( + "Main.kt", + """ + class MainKtTest2 { + fun test(f: (Int) -> Int): MainKtTest2 { + f(7) + return this + } + } + + fun main() { + MainKtTest2().test { it * 3 }.test { it * 5 }.test { it * 7 }.test { it * 26 }.test { it * 12 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("new", "dup", "invokespecial", "invokevirtual", "invokevirtual", "invokevirtual","invokevirtual","invokevirtual","pop","return"), false) + } + + "Null check ?.let test" { + val code = KotlinSource( + "Main.kt", + """ + fun test(f: ((Int) -> Unit)?) { + f?.let { println(it(5)) } + + if (f != null) { + println(f(5)) + } + val x = f + if (x != null) { + println(x(5)) + } + } + + fun main() { + test { it * 3 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) + } + "Null check ?.let on non-nullable thing" { + // The kotlin compiler should remove null checks on non-nullable things if they exist so this should still work. + val code = KotlinSource( + "Main.kt", + """ + fun test(f: (Int) -> Unit) { + f?.let { println(it(5)) } + + if (f != null) { + println(f(5)) + } + val x = f + if (x != null) { + println(x(5)) + } + } + + fun main() { + test { it * 3 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) + } }) \ No newline at end of file From c098ad84886b02dcfe1873de540d0c907e1ab80a Mon Sep 17 00:00:00 2001 From: timothy Date: Fri, 4 Aug 2023 17:05:00 +0200 Subject: [PATCH 03/72] Added test for retracer + addressed Oberon's comment from index 1 to 9 --- base/build.gradle | 1 + .../src/main/java/proguard/Configuration.java | 2 +- base/src/main/java/proguard/ProGuard.java | 2 + .../optimize/inline/BaseLambdaInliner.java | 115 ++++++++++-------- .../optimize/inline/CallReplacer.java | 48 -------- .../kotlin/proguard/lambdaInline/TestUtil.kt | 46 +++++++ 6 files changed, 112 insertions(+), 102 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/inline/CallReplacer.java diff --git a/base/build.gradle b/base/build.gradle index a3ae8a0f7..480fb769a 100644 --- a/base/build.gradle +++ b/base/build.gradle @@ -33,6 +33,7 @@ dependencies { testImplementation 'io.kotest:kotest-assertions-core-jvm:5.5.4' // for kotest core jvm assertions testImplementation 'io.kotest:kotest-property-jvm:5.5.4' // for kotest property test testImplementation 'io.mockk:mockk:1.13.2' // for mocking + testImplementation 'com.guardsquare:proguard-retrace:7.3.2' testImplementation(testFixtures("com.guardsquare:proguard-core:9.0.8")) { exclude group: 'com.guardsquare', module: 'proguard-core' diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 2787bcd17..5804247a4 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -203,7 +203,7 @@ public class Configuration /** * Specifies whether lambdas should be inlined. */ - public boolean lambdaInlining = true; + public boolean lambdaInlining = false; /////////////////////////////////////////////////////////////////////////// // Obfuscation options. diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 4470e47a7..58dbdd0f2 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -196,6 +196,8 @@ public void execute() throws Exception new ListParser(new NameParser()).parse(configuration.optimizations) : new ConstantMatcher(true); + // Inline kotlin lambdas passed as arguments to methods as long as they are + // created in the arguments or created in a local variable and passed as an argument if (configuration.lambdaInlining) { inlineLambdas(); diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 3c01df209..b5d58a63f 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -32,7 +32,7 @@ import static java.lang.Math.max; -public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { +public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor, AttributeVisitor { private final Clazz consumingClass; private final Method consumingMethod; private final AppView appView; @@ -92,23 +92,24 @@ public Method inline() { @Override public void visitAnyMember(Clazz clazz, Member member) { ProgramMethod method = (ProgramMethod) member; - method.u2accessFlags = (method.getAccessFlags() & ~AccessConstants.BRIDGE & ~AccessConstants.SYNTHETIC & ~AccessConstants.PRIVATE) | AccessConstants.STATIC; + method.u2accessFlags = (method.getAccessFlags() & ~AccessConstants.BRIDGE & ~AccessConstants.SYNTHETIC & ~AccessConstants.PRIVATE) + | AccessConstants.STATIC; // Copy the invoke method String invokeMethodDescriptor = method.getDescriptor(consumingClass); DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); staticInvokeMethod = descriptorModifier.modify(method, - desc -> { + originalDescriptor -> { // The method becomes static - String d = desc.replace("(", "(Ljava/lang/Object;"); + String modifiedDescriptor = originalDescriptor.replace("(", "(Ljava/lang/Object;"); // Change return type if it has an effect on the stack size - d = d.replace(")Ljava/lang/Double;", ")D"); - d = d.replace(")Ljava/lang/Float;", ")F"); - return d.replace(")Ljava/lang/Long;", ")J"); + modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Double;", ")D"); + modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Float;", ")F"); + return modifiedDescriptor.replace(")Ljava/lang/Long;", ")J"); } , true); - ProgramMethod copiedConsumingMethod = descriptorModifier.modify((ProgramMethod) consumingMethod, desc -> desc); + ProgramMethod copiedConsumingMethod = descriptorModifier.modify((ProgramMethod) consumingMethod, descriptor -> descriptor); // Don't inline if the lamdba is passed to another method try { @@ -131,55 +132,12 @@ public void visitAnyMember(Clazz clazz, Member member) { new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); // Remove return value's casting from staticInvokeMethod - staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - int len = codeAttribute.u4codeLength; - CodeAttributeEditor codeAttributeEditorStaticInvoke = new CodeAttributeEditor(); - codeAttributeEditorStaticInvoke.reset(codeAttribute.u4codeLength); - codeAttribute.instructionsAccept(clazz, method, len - 4, len, new CastRemover(codeAttributeEditorStaticInvoke)); - codeAttributeEditorStaticInvoke.visitCodeAttribute(clazz, method, codeAttribute); - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - })); + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(this)); if (referencedInterfaceConstant != null) { - InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(invokeMethodDescriptor); - int i = 0; - List keepList = new ArrayList<>(); - while(internalTypeEnumeration.hasMoreTypes()) { - if (internalTypeEnumeration.nextType().equals("Ljava/lang/Object;")) { - // Argument i is object, we should keep the cast for this argument - keepList.add(i); - } - i++; - } - int nbrArgs = ClassUtil.internalMethodParameterCount(invokeMethodDescriptor); - // Remove casting before and after invoke method call // Uses same codeAttributeEditor as LambdaInvokeReplacer - copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - for (int invokeMethodCallOffset : invokeMethodCallOffsets) { - int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); - int endOffset = invokeMethodCallOffset + 8 + 1; - if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode == Instruction.OP_POP) { - endOffset = invokeMethodCallOffset; - } - - codeAttribute.instructionsAccept(consumingClass, method, startOffset, endOffset, - new CastRemover(codeAttributeEditor, keepList) - ); - } - codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - })); + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PrePostCastRemover(invokeMethodDescriptor))); } // Important for inlining, we need this so that method invocations have non-null referenced methods. @@ -281,6 +239,57 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c } } + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + int len = codeAttribute.u4codeLength; + CodeAttributeEditor codeAttributeEditorStaticInvoke = new CodeAttributeEditor(); + codeAttributeEditorStaticInvoke.reset(codeAttribute.u4codeLength); + codeAttribute.instructionsAccept(clazz, method, len - 4, len, new CastRemover(codeAttributeEditorStaticInvoke)); + codeAttributeEditorStaticInvoke.visitCodeAttribute(clazz, method, codeAttribute); + } + + private class PrePostCastRemover implements AttributeVisitor{ + private final int nbrArgs; + private final List keepList; + + private PrePostCastRemover(String invokeMethodDescriptor) { + this.nbrArgs = ClassUtil.internalMethodParameterCount(invokeMethodDescriptor); + this.keepList = new ArrayList<>(); + + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(invokeMethodDescriptor); + int i = 0; + while(internalTypeEnumeration.hasMoreTypes()) { + if (internalTypeEnumeration.nextType().equals("Ljava/lang/Object;")) { + // Argument i is object, we should keep the cast for this argument + keepList.add(i); + } + i++; + } + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + for (int invokeMethodCallOffset : invokeMethodCallOffsets) { + int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); + int endOffset = invokeMethodCallOffset + 8 + 1; + if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode == Instruction.OP_POP) { + endOffset = invokeMethodCallOffset; + } + + codeAttribute.instructionsAccept(consumingClass, method, startOffset, endOffset, + new CastRemover(codeAttributeEditor, keepList) + ); + } + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + } + /** * @param method A Method object from which we'll get the length. * @param clazz The class in which the method is. diff --git a/base/src/main/java/proguard/optimize/inline/CallReplacer.java b/base/src/main/java/proguard/optimize/inline/CallReplacer.java deleted file mode 100644 index 9b5796d05..000000000 --- a/base/src/main/java/proguard/optimize/inline/CallReplacer.java +++ /dev/null @@ -1,48 +0,0 @@ -package proguard.optimize.inline; - -import proguard.classfile.Clazz; -import proguard.classfile.Method; -import proguard.classfile.ProgramClass; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.constant.AnyMethodrefConstant; -import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.CodeAttributeEditor; -import proguard.classfile.editor.ConstantPoolEditor; -import proguard.classfile.instruction.ConstantInstruction; -import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.visitor.InstructionVisitor; - -public class CallReplacer implements InstructionVisitor { - private final CodeAttributeEditor codeAttributeEditor; - private final Clazz methodOwnerClazz; - private final Method oldMethod; - private final Method newMethod; - - public CallReplacer(CodeAttributeEditor codeAttributeEditor, Clazz methodOwnerClazz, Method oldMethod, Method newMethod) { - this.codeAttributeEditor = codeAttributeEditor; - this.methodOwnerClazz = methodOwnerClazz; - this.oldMethod = oldMethod; - this.newMethod = newMethod; - } - - @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) { - if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { - ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) clazz); - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { - @Override - public void visitAnyMethodrefConstant(Clazz clazz, AnyMethodrefConstant anyMethodrefConstant) { - if (anyMethodrefConstant.referencedMethod != null && anyMethodrefConstant.referencedMethod.equals(oldMethod)) { - int newMethodIndex = constantPoolEditor.addMethodrefConstant(methodOwnerClazz, newMethod); - codeAttributeEditor.reset(codeAttribute.u4codeLength); - codeAttributeEditor.replaceInstruction(offset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, newMethodIndex)); - codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); - } - } - }); - } - } -} diff --git a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt index 726c11dc7..f48b1d086 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt @@ -3,6 +3,9 @@ import io.kotest.assertions.withClue import io.kotest.matchers.shouldBe import proguard.optimize.inline.LambdaInliner import proguard.AppView +import proguard.Configuration +import proguard.ConfigurationParser +import proguard.ProGuard import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.Method @@ -26,9 +29,11 @@ import proguard.optimize.info.ProgramClassOptimizationInfoSetter import proguard.optimize.info.ProgramMemberOptimizationInfoSetter import proguard.optimize.peephole.LineNumberLinearizer import proguard.preverify.CodePreverifier +import proguard.retrace.ReTrace import proguard.testutils.ClassPoolBuilder import proguard.testutils.PartialEvaluatorUtil import proguard.testutils.TestSource +import java.io.* import java.util.regex.Pattern import kotlin.math.pow import kotlin.math.sqrt @@ -417,3 +422,44 @@ fun stdev(numArray: MutableList): Double { return sqrt(standardDeviation / numArray.size) } + +fun getUnobfuscatedStackTrace(lambdaInlining: Boolean, configPath: String): String { + val file = File(configPath) + val parser = ConfigurationParser(file, System.getProperties()) + val configuration = Configuration() + parser.parse(configuration) + configuration.lambdaInlining = lambdaInlining + configuration.optimize = false + val a = ProGuard(configuration) + a.execute() + val mappingFile = File("/home/timothy/Documents/proguard/base/test-files/mapping.txt") + val retrace = ReTrace(mappingFile) + + val procResult = ProcessBuilder("java", "-jar", "/home/timothy/Documents/proguard/base/test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.PIPE) + .redirectError(ProcessBuilder.Redirect.PIPE) + .start() + procResult.waitFor() + + val reader = LineNumberReader(procResult.errorStream.bufferedReader()) + + val outputStream = ByteArrayOutputStream() + + val writer = PrintWriter(OutputStreamWriter(outputStream, "UTF-8")) + + retrace.retrace(reader, writer) + + //clean up + ProcessBuilder("rm", "-f", "/home/timothy/Documents/proguard/base/test-files/mapping.txt") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + ProcessBuilder("rm", "-f", "/home/timothy/Documents/proguard/base/test-files/result.jar") + .redirectOutput(ProcessBuilder.Redirect.INHERIT) + .redirectError(ProcessBuilder.Redirect.INHERIT) + .start() + .waitFor() + + return outputStream.toString() +} From bb66672cd6bdc50b6c808f17303d315f4b87905a Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 11:36:02 +0200 Subject: [PATCH 04/72] Fixed regression from merge --- .../optimize/inline/BaseLambdaInliner.java | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index d7837d873..9a5d0bc5a 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -31,7 +31,7 @@ import static java.lang.Math.max; -public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor, AttributeVisitor { +public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; private final Method consumingMethod; private final AppView appView; @@ -131,7 +131,7 @@ public void visitAnyMember(Clazz clazz, Member member) { new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); // Remove return value's casting from staticInvokeMethod - staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(this)); + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AllInstructionVisitor(new CastPatternRemover()))); if (referencedInterfaceConstant != null) { // Remove casting before and after invoke method call @@ -255,18 +255,6 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c } } - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - int len = codeAttribute.u4codeLength; - CodeAttributeEditor codeAttributeEditorStaticInvoke = new CodeAttributeEditor(); - codeAttributeEditorStaticInvoke.reset(codeAttribute.u4codeLength); - codeAttribute.instructionsAccept(clazz, method, len - 4, len, new CastRemover(codeAttributeEditorStaticInvoke)); - codeAttributeEditorStaticInvoke.visitCodeAttribute(clazz, method, codeAttribute); - } - private class PrePostCastRemover implements AttributeVisitor{ private final int nbrArgs; private final List keepList; From 573995e59ddd14e8d6f25eb0b6a30ba57d599ad7 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 11:39:23 +0200 Subject: [PATCH 05/72] Removed some unused fields from the RecursiveInliner --- .../optimize/inline/BaseLambdaInliner.java | 2 +- .../optimize/inline/RecursiveInliner.java | 56 +------------------ 2 files changed, 4 insertions(+), 54 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 9a5d0bc5a..c106d5eb4 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -112,7 +112,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Don't inline if the lamdba is passed to another method try { - copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(appView, consumingMethod, lambda)); + copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(consumingMethod)); } catch(CannotInlineException cie) { ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(copiedConsumingMethod); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index a98d17945..0fdccee0a 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -1,7 +1,5 @@ package proguard.optimize.inline; -import proguard.optimize.inline.lambda_locator.LambdaLocator; -import proguard.AppView; import proguard.classfile.*; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.BootstrapMethodsAttribute; @@ -10,60 +8,36 @@ import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.ClassEditor; -import proguard.classfile.editor.CodeAttributeEditor; -import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; -import proguard.optimize.inline.lambda_locator.Lambda; /** * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. */ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor, ConstantVisitor { - private final AppView appView; private Clazz consumingClazz; private Method copiedConsumingMethod; - private final Method originalConsumingMethod; private final boolean isStatic; - private final Lambda lambda; - private final boolean enableRecursiveMerging; - private PartialEvaluator partialEvaluator; - private final CodeAttributeEditor codeAttributeEditor; - private Clazz referencedClass; - private Method referencedMethod; - private int callOffset; - private boolean changed = false; + private final PartialEvaluator partialEvaluator; /** * @param originalConsumingMethod The original consuming method reference, this is used to detect recursion. */ - public RecursiveInliner(AppView appView, Method originalConsumingMethod, Lambda lambda, boolean enableRecursiveMerging) { - this.appView = appView; + public RecursiveInliner(Method originalConsumingMethod) { this.consumingClazz = null; this.copiedConsumingMethod = null; - this.originalConsumingMethod = originalConsumingMethod; this.isStatic = (originalConsumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - this.lambda = lambda; - this.enableRecursiveMerging = enableRecursiveMerging; this.partialEvaluator = new PartialEvaluator(); - this.codeAttributeEditor = new CodeAttributeEditor(); - this.referencedMethod = null; - } - - public RecursiveInliner(AppView appView, Method originalConsumingMethod, Lambda lambda) { - this(appView, originalConsumingMethod, lambda, false); } @Override @@ -120,7 +94,6 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst // Note that the instruction is only volatile. Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); int instructionLength = instruction.length(offset); - changed = false; instruction.accept(clazz, method, codeAttribute, offset, new InstructionOpCodeFilter( new int[] { Instruction.OP_INVOKESTATIC, @@ -132,8 +105,6 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst new InstructionVisitor() { @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - if (changed) - return; System.out.println("At " + constantInstruction); TracedStack tracedStack = partialEvaluator.getStackBefore(offset); @@ -168,13 +139,9 @@ public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribut if (bootstrapMethodHandle.getClassName(clazz).equals("java/lang/invoke/LambdaMetafactory")) { MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) clazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); - referencedMethod = new RefMethodFinder(clazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); - referencedClass = clazz; + Method referencedMethod = new RefMethodFinder(clazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); System.out.println(referencedMethod); - if (changed) - return; - String methodDescriptor = referencedMethod.getDescriptor(clazz); int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; @@ -186,11 +153,7 @@ public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribut codeAttribute.accept(clazz, method, new ClassPrinter()); int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); - callOffset = offset; codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); - if (changed) { - break; - } } } }); @@ -204,9 +167,6 @@ public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConst } public void handle(AnyMethodrefConstant methodrefConstant) { - if (changed) - return; - if (methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && methodrefConstant.getName(clazz).equals("checkNotNullParameter") && methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) @@ -223,23 +183,13 @@ public void handle(AnyMethodrefConstant methodrefConstant) { codeAttribute.accept(clazz, method, new ClassPrinter()); int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); - referencedMethod = methodrefConstant.referencedMethod; - referencedClass = methodrefConstant.referencedClass; - callOffset = offset; codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); - if (changed) { - break; - } } } }); } } )); - if (changed) { - offset = 0; - continue; - } offset += instructionLength; } } From e4ce1e0c2c183e00736ce14df2f2897d4d4047a8 Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 11:53:33 +0200 Subject: [PATCH 06/72] Removed import .* in LambdaLocator Made LambdaUsageHandler a class Put loadJar inside testUtil.kt and removed LambdaLocator.Util Added comments in CastRemover --- .../proguard/optimize/inline/CastRemover.java | 33 ++++-- .../optimize/inline/LambdaInliner.java | 111 ++++++++++-------- .../optimize/inline/LambdaUsageFinder.java | 15 +-- .../java/proguard/optimize/inline/Util.java | 17 --- .../inline/lambda_locator/LambdaLocator.java | 15 ++- .../optimize/inline/lambda_locator/Util.java | 89 -------------- .../kotlin/proguard/lambdaInline/TestUtil.kt | 67 ++++------- 7 files changed, 120 insertions(+), 227 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java index cc246cab2..4cd6ed278 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -9,21 +9,22 @@ import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.InstructionFactory; -import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.visitor.ClassPrinter; import java.util.*; public class CastRemover implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; - private List keepList; - private int currentIndex; + private final List keepList; + private int argIndex; + + // The names of all method taking a boxed type variable and returning the variable with the unboxed type + private final Set castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); + public CastRemover(CodeAttributeEditor codeAttributeEditor, List keepList) { this.codeAttributeEditor = codeAttributeEditor; - this.currentIndex = 0; + this.argIndex = 0; this.keepList = keepList; } @@ -33,21 +34,27 @@ public CastRemover(CodeAttributeEditor codeAttributeEditor) { @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - Set castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); + /* Casting on a lambda invoke call looks like this : + * iload + * invokestatic valueOf + * invokeinterface invoke //lambda call + * checkast + * invokevirtual intValue + * istore + * + * We remove valueOf, checkast and intValue + */ if (constantInstruction.opcode == Instruction.OP_CHECKCAST) { - System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); codeAttributeEditor.deleteInstruction(offset); } else if (constantInstruction.opcode == Instruction.OP_INVOKESTATIC) { if (getInvokedMethodName(clazz, constantInstruction).equals("valueOf")) { - if (!keepList.contains(currentIndex)) { - System.out.print("Removing "); InstructionFactory.create(codeAttribute.code, offset).accept(clazz, method, codeAttribute, offset, new ClassPrinter()); + // Don't remove valueOf call when the lambda takes an object as argument + if (!keepList.contains(argIndex)) { codeAttributeEditor.deleteInstruction(offset); } - - currentIndex++; + argIndex++; } } else if (constantInstruction.opcode == Instruction.OP_INVOKEVIRTUAL) { - System.out.println("Removing " + InstructionFactory.create(codeAttribute.code, offset).toString(offset)); if (castingMethodNames.contains(getInvokedMethodName(clazz, constantInstruction))) { codeAttributeEditor.deleteInstruction(offset); } diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 629bcfb58..b3458fa0e 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -1,6 +1,8 @@ package proguard.optimize.inline; //import com.guardsquare.gui.Gui; +import proguard.classfile.Clazz; +import proguard.classfile.attribute.CodeAttribute; import proguard.optimize.inline.lambda_locator.LambdaLocator; import proguard.AppView; import proguard.classfile.AccessConstants; @@ -15,6 +17,7 @@ import proguard.pass.Pass; import java.util.HashSet; +import java.util.List; import java.util.Set; public class LambdaInliner implements Pass { @@ -37,53 +40,7 @@ public void execute(AppView appView) { Set remainder = new HashSet<>(); inlinedAllUsages = true; InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); - lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), appView, (foundLambda, consumingClass, consumingMethod, consumingCallOffset, consumingCallClazz, consumingCallmethod, consumingCallCodeAttribute, sourceTrace, possibleLambdas) -> { - // Try inlining the lambda in consumingMethod - BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClass, consumingMethod, lambda); - Method inlinedLambamethod = baseLambdaInliner.inline(); - - // We didn't inline anything so no need to change any call instructions. - if (inlinedLambamethod == null) { - inlinedAllUsages = false; - return; - } - - if (possibleLambdas.size() > 1) { - // This lambda is part of a collection of lambdas that might potentially be used, but we do not know which one is actually used. Because of that we cannot inline it. - inlinedAllUsages = false; - return; - } - - CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); - codeAttributeEditor.reset(consumingCallCodeAttribute.u4codeLength); - - /* - * Remove usages that bring the variable on the stack so all the way up until a load - * The store operations before the load might also be used by other functions calls that consume the - * lambda, that's why we need to keep them. - */ - for (int i = 0; i < sourceTrace.size(); i++) { - InstructionAtOffset instrAtOffset = sourceTrace.get(i); - codeAttributeEditor.deleteInstruction(instrAtOffset.offset()); - if (instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_ALOAD || instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_INVOKESTATIC) { - remainder.addAll(sourceTrace.subList(i + 1, sourceTrace.size())); - break; - } - } - - // Replace invokestatic call to a call with the new function - ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingCallClazz); - int methodWithoutLambdaParameterIndex = constantPoolEditor.addMethodrefConstant(consumingClass, inlinedLambamethod); - - // Replacing at consumingCallOffset - if ((consumingMethod.getAccessFlags() & AccessConstants.STATIC) == 0) { - codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, methodWithoutLambdaParameterIndex)); - } else { - codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, methodWithoutLambdaParameterIndex)); - } - - codeAttributeEditor.visitCodeAttribute(consumingCallClazz, consumingCallmethod, consumingCallCodeAttribute); - })); + lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), appView, new LambdaUsageHandler(appView, remainder))); /* * Only remove the code needed to obtain a reference to the lambda if we were able to inline everything, if we @@ -100,4 +57,64 @@ public void execute(AppView appView) { } } } + + + public class LambdaUsageHandler { + + private final AppView appView; + private final Set remainder; + + public LambdaUsageHandler(AppView appView, Set remainder) { + this.appView = appView; + this.remainder = remainder; + } + + void handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { + // Try inlining the lambda in consumingMethod + BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClazz, consumingMethod, lambda); + Method inlinedLambamethod = baseLambdaInliner.inline(); + + // We didn't inline anything so no need to change any call instructions. + if (inlinedLambamethod == null) { + inlinedAllUsages = false; + return; + } + + if (possibleLambdas.size() > 1) { + // This lambda is part of a collection of lambdas that might potentially be used, but we do not know which one is actually used. Because of that we cannot inline it. + inlinedAllUsages = false; + return; + } + + CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); + codeAttributeEditor.reset(consumingCallCodeAttribute.u4codeLength); + + /* + * Remove usages that bring the variable on the stack so all the way up until a load + * The store operations before the load might also be used by other functions calls that consume the + * lambda, that's why we need to keep them. + */ + for (int i = 0; i < sourceTrace.size(); i++) { + InstructionAtOffset instrAtOffset = sourceTrace.get(i); + codeAttributeEditor.deleteInstruction(instrAtOffset.offset()); + if (instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_ALOAD || instrAtOffset.instruction().canonicalOpcode() == Instruction.OP_INVOKESTATIC) { + remainder.addAll(sourceTrace.subList(i + 1, sourceTrace.size())); + break; + } + } + + // Replace invokestatic call to a call with the new function + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingCallClass); + int methodWithoutLambdaParameterIndex = constantPoolEditor.addMethodrefConstant(consumingClazz, inlinedLambamethod); + + // Replacing at consumingCallOffset + if ((consumingMethod.getAccessFlags() & AccessConstants.STATIC) == 0) { + codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKEVIRTUAL, methodWithoutLambdaParameterIndex)); + } else { + codeAttributeEditor.replaceInstruction(consumingCallOffset, new ConstantInstruction(Instruction.OP_INVOKESTATIC, methodWithoutLambdaParameterIndex)); + } + + codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); + } + } } diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index eaa06abef..256ffc7b5 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -1,12 +1,9 @@ package proguard.optimize.inline; -import proguard.optimize.inline.lambda_locator.LambdaLocator; import proguard.AppView; -import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.MethodrefConstant; @@ -14,7 +11,6 @@ import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.SimpleInstruction; -import proguard.classfile.instruction.visitor.AllInstructionVisitor; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassUtil; @@ -26,7 +22,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.function.Function; import java.util.stream.Collectors; @@ -35,7 +30,7 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, private PartialEvaluator partialEvaluator; private final Map lambdaMap; private final AppView appView; - private final LambdaUsageHandler lambdaUsageHandler; + private final LambdaInliner.LambdaUsageHandler lambdaUsageHandler; public MethodrefConstant methodrefConstant; public FieldrefConstant referencedFieldConstant; private final boolean inlineFromFields; @@ -48,7 +43,7 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, Instruction.OP_ARETURN }; - public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, boolean inlineFromFields, boolean inlineFromMethods, LambdaUsageHandler lambdaUsageHandler) { + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, boolean inlineFromFields, boolean inlineFromMethods, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { this.targetLambda = targetLambda; this.partialEvaluator = new PartialEvaluator(); this.lambdaMap = lambdaMap; @@ -58,7 +53,7 @@ public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, Ap this.inlineFromMethods = inlineFromMethods; } - public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, LambdaUsageHandler lambdaUsageHandler) { + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { this(targetLambda, lambdaMap, appView, false, false, lambdaUsageHandler); } @@ -165,8 +160,4 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { this.referencedFieldConstant = fieldrefConstant; } - - public interface LambdaUsageHandler { - void handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas); - } } diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index 66afe4c26..3a5b1d418 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -147,21 +147,4 @@ protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAtt } })); } - - public static void printMethodWithRange(Clazz clazz, Method method, int startOffset, int endOffset, int specialOffset) { - method.accept(clazz, new AllAttributeVisitor(new AllInstructionVisitor(new InstructionVisitor() { - @Override - public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { - String str = " "; - if (offset == specialOffset) str = " * "; - else if (offset >= startOffset && offset < endOffset) str = " | "; - System.out.print(str); - instruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); - } - }))); - } - - public static void printMethodWithRange(Clazz clazz, Method method, int startOffset, int endOffset) { - printMethodWithRange(clazz, method, startOffset, endOffset, -1); - } } diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java index bc9fec0c4..bc24464ae 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -2,17 +2,22 @@ import proguard.backport.LambdaExpression; import proguard.backport.LambdaExpressionCollector; -import proguard.classfile.*; -import proguard.classfile.attribute.*; +//import proguard.classfile.*; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ClassPool; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; +import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.*; +import proguard.classfile.constant.ClassConstant; +import proguard.classfile.constant.FieldrefConstant; +import proguard.classfile.constant.Utf8Constant; import proguard.classfile.constant.visitor.ConstantVisitor; 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.ClassReferenceInitializer; import proguard.classfile.visitor.MemberVisitor; import java.util.*; diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java deleted file mode 100644 index bdb983732..000000000 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/Util.java +++ /dev/null @@ -1,89 +0,0 @@ -package proguard.optimize.inline.lambda_locator; - -import proguard.classfile.visitor.ClassPrinter; -import proguard.classfile.ClassPool; -import proguard.classfile.ProgramClass; -import proguard.classfile.visitor.ClassPoolFiller; -import proguard.io.*; -import proguard.io.util.IOUtil; - -import java.io.*; -import java.nio.charset.StandardCharsets; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; -import java.util.zip.ZipInputStream; - -public class Util { - /*public static ClassPool loadApk(String filename) throws IOException { - return IOUtil.read( - new File(filename), - false, - false, - (dataEntryReader, classVisitor) -> new NameFilteredDataEntryReader( - "classes*.dex", - new DexClassReader( - true, - classVisitor - ), - dataEntryReader) - ); - }*/ - - public static ClassPool loadJar(String filename) throws IOException { - ClassPool classPool = new ClassPool(); - - DataEntrySource source = - new FileSource( - new File(filename)); - - source.pumpDataEntries( - new JarReader(false, - new ClassFilter( - new ClassReader(false, false, false, false, null, - new ClassPoolFiller(classPool))))); - return classPool; - } - - public static String getMainClassFromJar(String filename) throws IOException { - String mainClass = null; - ZipFile zipFile = new ZipFile(filename); - ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(filename), StandardCharsets.UTF_8); - while (true) { - ZipEntry entry = zipInputStream.getNextEntry(); - if (entry == null) break; - - if (entry.getName().equals("META-INF/MANIFEST.MF")) { - System.out.println(entry); - InputStream iStream = zipFile.getInputStream(entry); - BufferedReader reader = new BufferedReader(new InputStreamReader(iStream)); - while (true) { - String line = reader.readLine(); - if (line == null) break; - - if (line.startsWith("Main-Class: ")) - mainClass = line.substring(line.indexOf(':')+1).trim(); - } - } - } - zipInputStream.close(); - zipFile.close(); - return mainClass; - } - - public static void writeJar(ClassPool programClassPool, String outputJarFileName) throws IOException { - JarWriter jarWriter = - new JarWriter( - new ZipWriter( - new FixedFileWriter( - new File(outputJarFileName)))); - - programClassPool.classesAccept( - new DataEntryClassWriter(jarWriter)); - - jarWriter.close(); - } - - public static void printClass(ClassPool classPool, String className) { - classPool.classAccept(className, new ClassPrinter()); - } -} diff --git a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt index f48b1d086..3650199f9 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt @@ -1,7 +1,5 @@ -import proguard.optimize.inline.lambda_locator.Util import io.kotest.assertions.withClue import io.kotest.matchers.shouldBe -import proguard.optimize.inline.LambdaInliner import proguard.AppView import proguard.Configuration import proguard.ConfigurationParser @@ -24,9 +22,11 @@ import proguard.classfile.visitor.MultiClassVisitor import proguard.evaluation.BasicInvocationUnit import proguard.evaluation.PartialEvaluator import proguard.evaluation.value.TypedReferenceValueFactory +import proguard.io.* import proguard.io.util.IOUtil import proguard.optimize.info.ProgramClassOptimizationInfoSetter import proguard.optimize.info.ProgramMemberOptimizationInfoSetter +import proguard.optimize.inline.LambdaInliner import proguard.optimize.peephole.LineNumberLinearizer import proguard.preverify.CodePreverifier import proguard.retrace.ReTrace @@ -44,7 +44,7 @@ fun compareOutputAndMainInstructions(code: TestSource, correctInstructions: List val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") val matcher = pattern.matcher(mydata) matcher.find() - val libraryClassPool = Util.loadJar(matcher.group(0)) + val libraryClassPool = loadJar(matcher.group(0)) val (classPool, _) = ClassPoolBuilder.fromSource(code) libraryClassPool.classesAccept(ClassPoolFiller(classPool)) @@ -211,7 +211,7 @@ fun onlyTestRunning(code: TestSource) { val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") val matcher = pattern.matcher(mydata) matcher.find() - val libraryClassPool = Util.loadJar(matcher.group(0)) + val libraryClassPool = loadJar(matcher.group(0)) val (classPool, _) = ClassPoolBuilder.fromSource(code) libraryClassPool.classesAccept(ClassPoolFiller(classPool)) @@ -296,7 +296,7 @@ fun testPerf(code: TestSource, inlineCode: TestSource, cleanUp: Boolean): Triple val pattern = Pattern.compile(System.getProperty("user.home") + "/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib/(.*?).jar") val matcher = pattern.matcher(mydata) matcher.find() - val libraryClassPool = Util.loadJar(matcher.group(0)) + val libraryClassPool = loadJar(matcher.group(0)) val (classPool, _) = ClassPoolBuilder.fromSource(code) val (inlinedClassPool, _) = ClassPoolBuilder.fromSource(inlineCode) @@ -423,43 +423,22 @@ fun stdev(numArray: MutableList): Double { return sqrt(standardDeviation / numArray.size) } -fun getUnobfuscatedStackTrace(lambdaInlining: Boolean, configPath: String): String { - val file = File(configPath) - val parser = ConfigurationParser(file, System.getProperties()) - val configuration = Configuration() - parser.parse(configuration) - configuration.lambdaInlining = lambdaInlining - configuration.optimize = false - val a = ProGuard(configuration) - a.execute() - val mappingFile = File("/home/timothy/Documents/proguard/base/test-files/mapping.txt") - val retrace = ReTrace(mappingFile) - - val procResult = ProcessBuilder("java", "-jar", "/home/timothy/Documents/proguard/base/test-files/result.jar") - .redirectOutput(ProcessBuilder.Redirect.PIPE) - .redirectError(ProcessBuilder.Redirect.PIPE) - .start() - procResult.waitFor() - - val reader = LineNumberReader(procResult.errorStream.bufferedReader()) - - val outputStream = ByteArrayOutputStream() - - val writer = PrintWriter(OutputStreamWriter(outputStream, "UTF-8")) - - retrace.retrace(reader, writer) - - //clean up - ProcessBuilder("rm", "-f", "/home/timothy/Documents/proguard/base/test-files/mapping.txt") - .redirectOutput(ProcessBuilder.Redirect.INHERIT) - .redirectError(ProcessBuilder.Redirect.INHERIT) - .start() - .waitFor() - ProcessBuilder("rm", "-f", "/home/timothy/Documents/proguard/base/test-files/result.jar") - .redirectOutput(ProcessBuilder.Redirect.INHERIT) - .redirectError(ProcessBuilder.Redirect.INHERIT) - .start() - .waitFor() - - return outputStream.toString() +@Throws(IOException::class) +fun loadJar(filename: String?): ClassPool { + val classPool = ClassPool() + val source: DataEntrySource = FileSource( + File(filename) + ) + source.pumpDataEntries( + JarReader( + false, + ClassFilter( + ClassReader( + false, false, false, false, null, + ClassPoolFiller(classPool) + ) + ) + ) + ) + return classPool } From 70e59667c50c140776e1fa063d97c81c5fd795b2 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 11:54:03 +0200 Subject: [PATCH 07/72] Simplified the RecursiveInliner a bit We can do some of these changes because we don't modify the code in this version --- .../optimize/inline/RecursiveInliner.java | 111 ++++-------------- 1 file changed, 23 insertions(+), 88 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 0fdccee0a..96660f979 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -6,11 +6,13 @@ import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.*; +import proguard.classfile.constant.InterfaceMethodrefConstant; +import proguard.classfile.constant.InvokeDynamicConstant; +import proguard.classfile.constant.MethodHandleConstant; +import proguard.classfile.constant.MethodrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; @@ -47,54 +49,8 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.accept(clazz, method, new ClassPrinter()); partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); - /*codeAttribute.instructionsAccept(clazz, method, - new InstructionOpCodeFilter(new int[] {Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL, Instruction.OP_INVOKESPECIAL}, - new InstructionVisitor() { - @Override - public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - System.out.println("At " + constantInstruction); - TracedStack tracedStack = partialEvaluator.getStackBefore(offset); - - if (tracedStack == null) - return; - - System.out.println("Stack size " + tracedStack.size()); - if (tracedStack.size() <= 0) - return; - - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { - @Override - public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { - if (methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && - methodrefConstant.getName(clazz).equals("checkNotNullParameter") && - methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) - return; - - String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); - int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); - boolean referencedMethodStatic = (methodrefConstant.referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - // We will now check if any of the arguments has a value that comes from the lambda parameters - for (int argIndex = 0; argIndex < argCount; argIndex++) { - int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); - int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); - referencedMethod = methodrefConstant.referencedMethod; - referencedClass = methodrefConstant.referencedClass; - callOffset = offset; - codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); - } - } - }); - } - }));*/ - - int offset = 0; - - while (offset < codeAttribute.u4codeLength) - { - // Note that the instruction is only volatile. - Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); - int instructionLength = instruction.length(offset); - instruction.accept(clazz, method, codeAttribute, offset, new InstructionOpCodeFilter( + codeAttribute.instructionsAccept(clazz, method, + new InstructionOpCodeFilter( new int[] { Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL, @@ -108,9 +64,6 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c System.out.println("At " + constantInstruction); TracedStack tracedStack = partialEvaluator.getStackBefore(offset); - if (tracedStack == null) - return; - System.out.println("Stack size " + tracedStack.size()); if (tracedStack.size() <= 0) return; @@ -118,43 +71,27 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { @Override public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { - handle(methodrefConstant); + handleCalledMethod(methodrefConstant.referencedClass, methodrefConstant.referencedMethod); } @Override public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { - System.out.println(invokeDynamicConstant); // MethodRefTraveler clazz.attributesAccept(new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { @Override public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { - bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (clazz1, bootstrapMethodInfo) -> { - System.out.println(bootstrapMethodInfo); - - ProgramClass programClass = (ProgramClass) clazz; + bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (bootstrapClazz, bootstrapMethodInfo) -> { + ProgramClass programClass = (ProgramClass) bootstrapClazz; MethodHandleConstant bootstrapMethodHandle = (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); - if (bootstrapMethodHandle.getClassName(clazz).equals("java/lang/invoke/LambdaMetafactory")) + if (bootstrapMethodHandle.getClassName(bootstrapClazz).equals("java/lang/invoke/LambdaMetafactory")) { - MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) clazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); - Method referencedMethod = new RefMethodFinder(clazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); - System.out.println(referencedMethod); - - String methodDescriptor = referencedMethod.getDescriptor(clazz); - int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); - boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - // We will now check if any of the arguments has a value that comes from the lambda parameters - for (int argIndex = 0; argIndex < argCount; argIndex++) { - System.out.println("123Descriptor " + referencedMethod.getDescriptor(clazz) + " " + argIndex + " " + argCount); - System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(offset)); - constantInstruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); - codeAttribute.accept(clazz, method, new ClassPrinter()); - int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); - int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); - codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); - } + MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) bootstrapClazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); + Method referencedMethod = new RefMethodFinder(bootstrapClazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); + + handleCalledMethod(bootstrapClazz, referencedMethod); } }); } @@ -163,21 +100,20 @@ public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribut @Override public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { - handle(interfaceMethodrefConstant); + handleCalledMethod(interfaceMethodrefConstant.referencedClass, interfaceMethodrefConstant.referencedMethod); } - public void handle(AnyMethodrefConstant methodrefConstant) { - if (methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && - methodrefConstant.getName(clazz).equals("checkNotNullParameter") && - methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) + public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { + if (referencedClass.getName().equals("kotlin/jvm/internal/Intrinsics") && + referencedMethod.getName(referencedClass).equals("checkNotNullParameter") && + referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) return; - String methodDescriptor = methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass); + String methodDescriptor = referencedMethod.getDescriptor(referencedClass); int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); - boolean referencedMethodStatic = (methodrefConstant.referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; // We will now check if any of the arguments has a value that comes from the lambda parameters for (int argIndex = 0; argIndex < argCount; argIndex++) { - System.out.println("123Descriptor " + methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass) + " " + argIndex + " " + argCount); System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(offset)); constantInstruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); codeAttribute.accept(clazz, method, new ClassPrinter()); @@ -189,9 +125,8 @@ public void handle(AnyMethodrefConstant methodrefConstant) { }); } } - )); - offset += instructionLength; - } + ) + ); } @Override From aabc5bd0a8d81ca9529832865689115b067145c2 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 12:22:47 +0200 Subject: [PATCH 08/72] Cleaned up RecursiveInliner by moving some code into a new CalledMethodHandler class --- .../optimize/inline/RecursiveInliner.java | 158 ++++++++++-------- 1 file changed, 87 insertions(+), 71 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 96660f979..1c75494d8 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -6,10 +6,7 @@ import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.InterfaceMethodrefConstant; -import proguard.classfile.constant.InvokeDynamicConstant; -import proguard.classfile.constant.MethodHandleConstant; -import proguard.classfile.constant.MethodrefConstant; +import proguard.classfile.constant.*; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; @@ -58,73 +55,7 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt Instruction.OP_INVOKEDYNAMIC, Instruction.OP_INVOKEINTERFACE }, - new InstructionVisitor() { - @Override - public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - System.out.println("At " + constantInstruction); - TracedStack tracedStack = partialEvaluator.getStackBefore(offset); - - System.out.println("Stack size " + tracedStack.size()); - if (tracedStack.size() <= 0) - return; - - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { - @Override - public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { - handleCalledMethod(methodrefConstant.referencedClass, methodrefConstant.referencedMethod); - } - - @Override - public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { - // MethodRefTraveler - - clazz.attributesAccept(new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { - @Override - public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { - bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (bootstrapClazz, bootstrapMethodInfo) -> { - ProgramClass programClass = (ProgramClass) bootstrapClazz; - MethodHandleConstant bootstrapMethodHandle = - (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); - - if (bootstrapMethodHandle.getClassName(bootstrapClazz).equals("java/lang/invoke/LambdaMetafactory")) - { - MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) bootstrapClazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); - Method referencedMethod = new RefMethodFinder(bootstrapClazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); - - handleCalledMethod(bootstrapClazz, referencedMethod); - } - }); - } - })); - } - - @Override - public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { - handleCalledMethod(interfaceMethodrefConstant.referencedClass, interfaceMethodrefConstant.referencedMethod); - } - - public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { - if (referencedClass.getName().equals("kotlin/jvm/internal/Intrinsics") && - referencedMethod.getName(referencedClass).equals("checkNotNullParameter") && - referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) - return; - - String methodDescriptor = referencedMethod.getDescriptor(referencedClass); - int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); - boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - // We will now check if any of the arguments has a value that comes from the lambda parameters - for (int argIndex = 0; argIndex < argCount; argIndex++) { - System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(offset)); - constantInstruction.accept(clazz, method, codeAttribute, offset, new ClassPrinter()); - codeAttribute.accept(clazz, method, new ClassPrinter()); - int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); - int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); - codeAttribute.instructionAccept(clazz, method, traceOffset, RecursiveInliner.this); - } - } - }); - } - } + new CalledMethodHandler(this) ) ); } @@ -132,6 +63,10 @@ public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} + /** + * This visit function is used to visit the source instructions of arguments to methods called within the current + * method, we check if the source instruction is a lambda, if it is we will stop trying to inline this lambda. + */ @Override public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { @@ -163,4 +98,85 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM this.copiedConsumingMethod = programMethod; programMethod.attributesAccept(programClass, this); } + + private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor { + private final InstructionVisitor sourceInstructionVisitor; + private TracedStack tracedStack; + private Clazz clazz; + private Method method; + private CodeAttribute codeAttribute; + private int callOffset; + + public CalledMethodHandler(InstructionVisitor sourceInstructionVisitor) { + this.sourceInstructionVisitor = sourceInstructionVisitor; + } + + @Override + public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int callOffset, ConstantInstruction constantInstruction) { + this.clazz = clazz; + this.method = method; + this.codeAttribute = codeAttribute; + this.callOffset = callOffset; + this.tracedStack = partialEvaluator.getStackBefore(callOffset); + + System.out.println("Stack size " + tracedStack.size()); + if (tracedStack.size() <= 0) + return; + + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); + } + + @Override + public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConstant) { + handleCalledMethod(methodrefConstant.referencedClass, methodrefConstant.referencedMethod); + } + + @Override + public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { + // MethodRefTraveler + + clazz.attributesAccept(new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { + @Override + public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { + bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (bootstrapClazz, bootstrapMethodInfo) -> { + ProgramClass programClass = (ProgramClass) bootstrapClazz; + MethodHandleConstant bootstrapMethodHandle = + (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); + + if (bootstrapMethodHandle.getClassName(bootstrapClazz).equals("java/lang/invoke/LambdaMetafactory")) { + MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) bootstrapClazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); + Method referencedMethod = new RefMethodFinder(bootstrapClazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); + + handleCalledMethod(bootstrapClazz, referencedMethod); + } + }); + } + })); + } + + @Override + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { + handleCalledMethod(interfaceMethodrefConstant.referencedClass, interfaceMethodrefConstant.referencedMethod); + } + + public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { + // There can be null checks on the lambda parameter, this doesn't stop us from inlining because we will + // remove these later. + if (referencedClass.getName().equals("kotlin/jvm/internal/Intrinsics") && + referencedMethod.getName(referencedClass).equals("checkNotNullParameter") && + referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) + return; + + String methodDescriptor = referencedMethod.getDescriptor(referencedClass); + int argCount = ClassUtil.internalMethodParameterCount(methodDescriptor); + boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; + // We will now check if any of the arguments has a value that comes from the lambda parameters + for (int argIndex = 0; argIndex < argCount; argIndex++) { + System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(callOffset)); + int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); + int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + codeAttribute.instructionAccept(clazz, method, traceOffset, sourceInstructionVisitor); + } + } + } } From f4a22fad8123496a9f9f54040f8f340ca53b8b4d Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 12:38:04 +0200 Subject: [PATCH 09/72] Move some code into IndyLambdaImplVisitor class --- .../optimize/inline/RecursiveInliner.java | 60 ++++++++++++------- 1 file changed, 40 insertions(+), 20 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 1c75494d8..8cba09543 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -99,7 +99,7 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM programMethod.attributesAccept(programClass, this); } - private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor { + private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor, AttributeVisitor { private final InstructionVisitor sourceInstructionVisitor; private TracedStack tracedStack; private Clazz clazz; @@ -133,25 +133,15 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst @Override public void visitInvokeDynamicConstant(Clazz clazz, InvokeDynamicConstant invokeDynamicConstant) { - // MethodRefTraveler - - clazz.attributesAccept(new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, new AttributeVisitor() { - @Override - public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { - bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (bootstrapClazz, bootstrapMethodInfo) -> { - ProgramClass programClass = (ProgramClass) bootstrapClazz; - MethodHandleConstant bootstrapMethodHandle = - (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); - - if (bootstrapMethodHandle.getClassName(bootstrapClazz).equals("java/lang/invoke/LambdaMetafactory")) { - MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) bootstrapClazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); - Method referencedMethod = new RefMethodFinder(bootstrapClazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); - - handleCalledMethod(bootstrapClazz, referencedMethod); - } - }); - } - })); + clazz.attributesAccept( + new AttributeNameFilter(Attribute.BOOTSTRAP_METHODS, + new IndyLambdaImplVisitor(invokeDynamicConstant, new MemberVisitor() { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + handleCalledMethod(programClass, programMethod); + } + })) + ); } @Override @@ -179,4 +169,34 @@ public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { } } } + + /** + * A simple class that visits the static invocation method that implements an invokedynamic lambda. The + * implementation method is visited by the implMethodVisitor. + */ + private static class IndyLambdaImplVisitor implements AttributeVisitor { + private final InvokeDynamicConstant invokeDynamicConstant; + private final MemberVisitor implMethodVisitor; + + public IndyLambdaImplVisitor(InvokeDynamicConstant invokeDynamicConstant, MemberVisitor implMethodVisitor) { + this.invokeDynamicConstant = invokeDynamicConstant; + this.implMethodVisitor = implMethodVisitor; + } + + @Override + public void visitBootstrapMethodsAttribute(Clazz clazz, BootstrapMethodsAttribute bootstrapMethodsAttribute) { + bootstrapMethodsAttribute.bootstrapMethodEntryAccept(clazz, invokeDynamicConstant.u2bootstrapMethodAttributeIndex, (bootstrapClazz, bootstrapMethodInfo) -> { + ProgramClass programClass = (ProgramClass) bootstrapClazz; + MethodHandleConstant bootstrapMethodHandle = + (MethodHandleConstant) programClass.getConstant(bootstrapMethodInfo.u2methodHandleIndex); + + if (bootstrapMethodHandle.getClassName(bootstrapClazz).equals("java/lang/invoke/LambdaMetafactory")) { + MethodHandleConstant methodHandleConstant = (MethodHandleConstant) ((ProgramClass) bootstrapClazz).getConstant(bootstrapMethodInfo.u2methodArguments[1]); + Method referencedMethod = new RefMethodFinder(bootstrapClazz).findReferencedMethod(methodHandleConstant.u2referenceIndex); + + referencedMethod.accept(bootstrapClazz, implMethodVisitor); + } + }); + } + } } From 7b2146e8c94457ed4ca65aca7ae93d86ac2aab8a Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 12:44:19 +0200 Subject: [PATCH 10/72] Remove argument from RecursiveInliner because it isn't really needed anymore --- .../java/proguard/optimize/inline/BaseLambdaInliner.java | 2 +- .../java/proguard/optimize/inline/RecursiveInliner.java | 9 +++------ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index c106d5eb4..f6e4c5a13 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -112,7 +112,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Don't inline if the lamdba is passed to another method try { - copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(consumingMethod)); + copiedConsumingMethod.accept(consumingClass, new RecursiveInliner()); } catch(CannotInlineException cie) { ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(copiedConsumingMethod); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 8cba09543..375c1c553 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -26,16 +26,12 @@ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor, ConstantVisitor { private Clazz consumingClazz; private Method copiedConsumingMethod; - private final boolean isStatic; + private boolean isStatic; private final PartialEvaluator partialEvaluator; - /** - * @param originalConsumingMethod The original consuming method reference, this is used to detect recursion. - */ - public RecursiveInliner(Method originalConsumingMethod) { + public RecursiveInliner() { this.consumingClazz = null; this.copiedConsumingMethod = null; - this.isStatic = (originalConsumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; this.partialEvaluator = new PartialEvaluator(); } @@ -96,6 +92,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { this.consumingClazz = programClass; this.copiedConsumingMethod = programMethod; + this.isStatic = (copiedConsumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; programMethod.attributesAccept(programClass, this); } From 3573c1999b0b67328d1aba49949d4a1e21d91849 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 12:54:24 +0200 Subject: [PATCH 11/72] Reduce line length a bit by putting index calculation in a variable --- .../java/proguard/optimize/inline/LambdaUsageFinder.java | 5 +++-- .../main/java/proguard/optimize/inline/RecursiveInliner.java | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index e9a2fffc0..bb7572eb1 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -144,8 +144,9 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, System.out.println(tracedStack); for (int argIndex = 0; argIndex < argCount; argIndex++) { - int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); - int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + int sizeAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); + int stackEntryIndex = tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argCount) + sizeAdjustedIndex; + int traceOffset = tracedStack.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue().instructionOffset(0); List trace = Util.traceParameterOffset(partialEvaluator, codeAttribute, traceOffset); List leafNodes = new ArrayList<>(); Util.traceParameterTree(partialEvaluator, codeAttribute, traceOffset, leafNodes); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 375c1c553..5a16f3d32 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -160,8 +160,9 @@ public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { // We will now check if any of the arguments has a value that comes from the lambda parameters for (int argIndex = 0; argIndex < argCount; argIndex++) { System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(callOffset)); - int stackAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); - int traceOffset = tracedStack.getBottomActualProducerValue(tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + stackAdjustedIndex).instructionOffsetValue().instructionOffset(0); + int sizeAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); + int stackEntryIndex = tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + sizeAdjustedIndex; + int traceOffset = tracedStack.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue().instructionOffset(0); codeAttribute.instructionAccept(clazz, method, traceOffset, sourceInstructionVisitor); } } From 025571ce856336a25e70cdedde2473d9ea933dcd Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 13:00:55 +0200 Subject: [PATCH 12/72] Remove wildcard imports from RecursiveInliner --- .../proguard/optimize/inline/RecursiveInliner.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 5a16f3d32..3ff3ed37f 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -1,12 +1,19 @@ package proguard.optimize.inline; -import proguard.classfile.*; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.BootstrapMethodsAttribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AttributeNameFilter; import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.constant.*; +import proguard.classfile.constant.InterfaceMethodrefConstant; +import proguard.classfile.constant.InvokeDynamicConstant; +import proguard.classfile.constant.MethodHandleConstant; +import proguard.classfile.constant.MethodrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; From 13b38d52f4004fc920b3985a6eb8b14125554317 Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 13:59:12 +0200 Subject: [PATCH 13/72] Removed visitor staircase in LambdaLocator Refactored LambdaImplementationVisitor visitProgramMethod --- .../inline/LambdaImplementationVisitor.java | 56 ++++---- .../inline/lambda_locator/LambdaLocator.java | 134 ++++++++++-------- 2 files changed, 102 insertions(+), 88 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java index 05c905216..bf373ff0d 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java @@ -1,6 +1,12 @@ package proguard.optimize.inline; -import proguard.classfile.*; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.LibraryClass; +import proguard.classfile.LibraryMethod; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; import proguard.classfile.constant.ClassConstant; import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; @@ -10,6 +16,7 @@ public class LambdaImplementationVisitor implements ConstantVisitor, MemberVisitor { private final InvokeMethodVisitor invokeMethodVisitor; private Clazz lambdaImplementationClass; + public LambdaImplementationVisitor(InvokeMethodVisitor invokeMethodHandler) { this.invokeMethodVisitor = invokeMethodHandler; } @@ -28,39 +35,32 @@ public void visitClassConstant(Clazz clazz, ClassConstant referencedClassConstan @Override public void visitProgramMethod(ProgramClass interfaceClass, ProgramMethod programMethod) { - getLambdaImplementation(interfaceClass, programMethod); + lambdaImplementationClass.accept(new AllMethodVisitor(new NonBridgeMethodFinder(interfaceClass, programMethod.getDescriptor(interfaceClass)))); } @Override - public void visitLibraryMethod(LibraryClass interfaceClass, LibraryMethod libraryMethod) { - getLambdaImplementation(interfaceClass, libraryMethod); - } - - private void getLambdaImplementation(Clazz interfaceClazz, Method method) { - String descriptor = method.getDescriptor(interfaceClazz); - lambdaImplementationClass.methodAccept("invoke", descriptor, new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - System.out.println("Descriptor " + programMethod.getDescriptor(programClass)); - String descriptor = programMethod.getDescriptor(programClass); - lambdaImplementationClass.accept(new AllMethodVisitor(new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - if (programMethod.getName(programClass).equals("invoke") && programMethod.u2accessFlags == (AccessConstants.PUBLIC | AccessConstants.FINAL)) { - lambdaImplementationClass.methodAccept("invoke", programMethod.getDescriptor(programClass), new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - invokeMethodVisitor.visitInvokeMethod(programClass, programMethod, interfaceClazz, descriptor); - } - }); - } - } - })); - } - }); + public void visitLibraryMethod(LibraryClass interfaceClass, LibraryMethod libraryMethod) + { + lambdaImplementationClass.accept(new AllMethodVisitor(new NonBridgeMethodFinder(interfaceClass, libraryMethod.getDescriptor(interfaceClass)))); } public interface InvokeMethodVisitor { void visitInvokeMethod(ProgramClass programClass, ProgramMethod programMethod, Clazz interfaceClass, String bridgeDescriptor); } + + private class NonBridgeMethodFinder implements MemberVisitor{ + private final Clazz interfaceClazz; + private final String descriptor; + public NonBridgeMethodFinder(Clazz interfaceClazz, String descriptor) { + this.interfaceClazz = interfaceClazz; + this.descriptor = descriptor; + } + + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + if (programMethod.getName(programClass).equals("invoke") && programMethod.u2accessFlags == (AccessConstants.PUBLIC | AccessConstants.FINAL)) { + invokeMethodVisitor.visitInvokeMethod(programClass, programMethod, interfaceClazz, descriptor); + } + } + } } diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java index bc24464ae..690daa83a 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -22,31 +22,19 @@ import java.util.*; -public class LambdaLocator { +public class LambdaLocator implements InstructionVisitor, ConstantVisitor, MemberVisitor { private final Map>> classLambdas = new HashMap<>(); private final List staticLambdas = new ArrayList<>(); private final Map staticLambdaMap = new HashMap<>(); private final Set lambdaClasses = new HashSet<>(); + private final ClassPool classPool; public LambdaLocator(ClassPool classPool, String classNameFilter) { + this.classPool = classPool; + classPool.classesAccept(classNameFilter, clazz -> { // Find classes that inherit from kotlin.jvm.internal.Lambda - clazz.superClassConstantAccept(new ConstantVisitor() { - @Override - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { - clazz.constantPoolEntryAccept(classConstant.u2nameIndex, new ConstantVisitor() { - @Override - public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { - if (utf8Constant.getString().equals("kotlin/jvm/internal/Lambda")) { - System.out.println("Class " + clazz.getName() + " is a kotlin lambda class!"); - lambdaClasses.add(clazz); - } else { - System.out.println("Class " + clazz.getName() + " is not a kotlin lambda class!"); - } - } - }); - } - }); + clazz.superClassConstantAccept(this); }); // Find static lambdas @@ -56,52 +44,37 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { LambdaExpressionCollector lec = new LambdaExpressionCollector(h); lec.visitProgramClass((ProgramClass) clazz); - clazz.methodsAccept(new MemberVisitor() { - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - programMethod.accept(programClass, new AllAttributeVisitor(new AllInstructionVisitor(new InstructionVisitor() { - @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) { - if (constantInstruction.opcode == Instruction.OP_GETSTATIC) { - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ConstantVisitor() { - @Override - public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { - clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, new ConstantVisitor() { - @Override - public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { - clazz.constantPoolEntryAccept(classConstant.u2nameIndex, new ConstantVisitor() { - @Override - public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { - classPool.classAccept(utf8Constant.getString(), referencedClazz -> { - if (lambdaClasses.contains(referencedClazz)) { - System.out.println("Found a lambda invocation " + constantInstruction); - - classLambdas.putIfAbsent(clazz, new HashMap<>()); - classLambdas.get(clazz).putIfAbsent(method, new HashSet<>()); - classLambdas.get(clazz).get(method).add(new Lambda(clazz, method, codeAttribute, offset, constantInstruction)); - - Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); - staticLambdas.add(lambda); - staticLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); - } - }); - } - }); - } - }); - } - }); - } - } - }))); - } - }); + clazz.methodsAccept(this); }); } + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { + programMethod.accept(programClass, new AllAttributeVisitor(new AllInstructionVisitor(this))); + } + + @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) { + if (constantInstruction.opcode == Instruction.OP_GETSTATIC) { + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new StaticLambdaFinder(method, codeAttribute, constantInstruction, offset)); + } + } + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + clazz.constantPoolEntryAccept(classConstant.u2nameIndex, this); + } + + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { + if (utf8Constant.getString().equals("kotlin/jvm/internal/Lambda")) { + lambdaClasses.add(clazz); + } + } + public Map>> getLambdasByClass() { return classLambdas; } @@ -113,4 +86,45 @@ public List getStaticLambdas() { public Map getStaticLambdaMap() { return staticLambdaMap; } + + private class StaticLambdaFinder implements ConstantVisitor { + private final ConstantInstruction constantInstruction; + private final Method method; + private final int offset; + private final CodeAttribute codeAttribute; + + public StaticLambdaFinder(Method method, CodeAttribute codeAttribute, ConstantInstruction constantInstruction, int offset) { + this.method = method; + this.codeAttribute = codeAttribute; + this.constantInstruction = constantInstruction; + this.offset = offset; + } + + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + clazz.constantPoolEntryAccept(fieldrefConstant.u2classIndex, this); + } + + @Override + public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { + clazz.constantPoolEntryAccept(classConstant.u2nameIndex, this); + } + + @Override + public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { + classPool.classAccept(utf8Constant.getString(), referencedClazz -> { + if (lambdaClasses.contains(referencedClazz)) { + System.out.println("Found a lambda invocation " + constantInstruction); + + classLambdas.putIfAbsent(clazz, new HashMap<>()); + classLambdas.get(clazz).putIfAbsent(method, new HashSet<>()); + classLambdas.get(clazz).get(method).add(new Lambda(clazz, method, codeAttribute, offset, constantInstruction)); + + Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); + staticLambdas.add(lambda); + staticLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); + } + }); + } + } } From fb9fe7d45983a71b25bfe0ac3ef0b8d2cea1a510 Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 14:23:38 +0200 Subject: [PATCH 14/72] Removed wildcards in import Cleaned commented code in NullCheckRemover Removed unused class Removed staircase in LocalUsageRemover --- .../optimize/inline/BaseLambdaInliner.java | 8 ++- .../proguard/optimize/inline/CastRemover.java | 6 +- .../optimize/inline/DescriptorModifier.java | 4 +- .../inline/LambdaImplementationVisitor.java | 1 - .../optimize/inline/LambdaInliner.java | 1 - .../optimize/inline/LocalUsageRemover.java | 54 +++++++-------- .../inline/MethodInstructionVisitor.java | 41 ------------ .../optimize/inline/MethodUsageFinder.java | 66 ------------------- .../optimize/inline/NullCheckRemover.java | 2 - .../optimize/inline/RecursiveInliner.java | 11 ++-- .../java/proguard/optimize/inline/Util.java | 3 - .../inline/lambda_locator/LambdaLocator.java | 19 ++---- 12 files changed, 54 insertions(+), 162 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java delete mode 100644 base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index b5d58a63f..7e236d941 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -1,7 +1,13 @@ package proguard.optimize.inline; import proguard.AppView; -import proguard.classfile.*; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.LibraryMethod; +import proguard.classfile.Member; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AllAttributeVisitor; diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java index 4cd6ed278..b3fb9465a 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -11,7 +11,11 @@ import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.visitor.InstructionVisitor; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; public class CastRemover implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; diff --git a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java index 221b897f5..28761addd 100644 --- a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java +++ b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java @@ -1,6 +1,8 @@ package proguard.optimize.inline; -import proguard.classfile.*; +import proguard.classfile.Clazz; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; import proguard.classfile.editor.AttributeAdder; import proguard.classfile.editor.ClassBuilder; import proguard.classfile.editor.ClassEditor; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java index bf373ff0d..92c8f7415 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java @@ -4,7 +4,6 @@ import proguard.classfile.Clazz; import proguard.classfile.LibraryClass; import proguard.classfile.LibraryMethod; -import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.constant.ClassConstant; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index b3458fa0e..d47f80324 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -1,6 +1,5 @@ package proguard.optimize.inline; -//import com.guardsquare.gui.Gui; import proguard.classfile.Clazz; import proguard.classfile.attribute.CodeAttribute; import proguard.optimize.inline.lambda_locator.LambdaLocator; diff --git a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java index c3e9c54ad..b8019243f 100644 --- a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java +++ b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java @@ -14,7 +14,7 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; -public class LocalUsageRemover implements MemberVisitor { +public class LocalUsageRemover implements MemberVisitor, InstructionVisitor, AttributeVisitor { private final CodeAttributeEditor codeAttributeEditor; private final int argumentIndex; @@ -25,32 +25,32 @@ public LocalUsageRemover(CodeAttributeEditor codeAttributeEditor, int argumentIn @Override public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - programMethod.accept(programClass, new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - codeAttributeEditor.reset(codeAttribute.u4codeLength); - codeAttribute.instructionsAccept(clazz, method, new InstructionVisitor() { - @Override - public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} - - @Override - public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { - if (variableInstruction.variableIndex == argumentIndex) { - if (variableInstruction.isStore()) { - codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_POP)); - } else { - codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_ACONST_NULL)); - } - } else if (variableInstruction.variableIndex > argumentIndex){ - codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(variableInstruction.canonicalOpcode(), variableInstruction.variableIndex - 1, variableInstruction.constant)); - } - } - }); - codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + programMethod.accept(programClass, new AllAttributeVisitor(this)); + } + + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeAttributeEditor.reset(codeAttribute.u4codeLength); + codeAttribute.instructionsAccept(clazz, method, this); + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + } + + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + + @Override + public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { + if (variableInstruction.variableIndex == argumentIndex) { + if (variableInstruction.isStore()) { + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_POP)); + } else { + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(Instruction.OP_ACONST_NULL)); } - })); + } else if (variableInstruction.variableIndex > argumentIndex){ + codeAttributeEditor.replaceInstruction(offset, new VariableInstruction(variableInstruction.canonicalOpcode(), variableInstruction.variableIndex - 1, variableInstruction.constant)); + } } + + @Override + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} } diff --git a/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java deleted file mode 100644 index 2d65b7b90..000000000 --- a/base/src/main/java/proguard/optimize/inline/MethodInstructionVisitor.java +++ /dev/null @@ -1,41 +0,0 @@ -package proguard.optimize.inline; - -import proguard.classfile.Clazz; -import proguard.classfile.Method; -import proguard.classfile.ProgramClass; -import proguard.classfile.ProgramMethod; -import proguard.classfile.attribute.Attribute; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.visitor.AttributeVisitor; -import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.visitor.MemberVisitor; - -public class MethodInstructionVisitor implements MemberVisitor, AttributeVisitor { - private final InstructionVisitor instructionVisitor; - private final int startOffset; - private final int endOffset; - - public MethodInstructionVisitor(int startOffset, int endOffset, InstructionVisitor instructionVisitor) { - this.instructionVisitor = instructionVisitor; - this.startOffset = startOffset; - this.endOffset = endOffset; - } - - public MethodInstructionVisitor(InstructionVisitor instructionVisitor) { - this(-1, -1, instructionVisitor); - } - - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - programMethod.attributesAccept(programClass, this); - } - - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - if (startOffset < 0) codeAttribute.instructionsAccept(clazz, method, instructionVisitor); - else codeAttribute.instructionsAccept(clazz, method, startOffset, endOffset, instructionVisitor); - } -} diff --git a/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java b/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java deleted file mode 100644 index 257f77803..000000000 --- a/base/src/main/java/proguard/optimize/inline/MethodUsageFinder.java +++ /dev/null @@ -1,66 +0,0 @@ -package proguard.optimize.inline; - -import proguard.classfile.Clazz; -import proguard.classfile.Method; -import proguard.classfile.ProgramClass; -import proguard.classfile.ProgramMethod; -import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; -import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.instruction.ConstantInstruction; -import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.visitor.AllInstructionVisitor; -import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; -import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.visitor.ClassVisitor; -import proguard.classfile.visitor.MemberVisitor; - -/** - * A class that searches the entire class pool for calls to a specific method. - */ -public class MethodUsageFinder implements ClassVisitor, MemberVisitor, InstructionVisitor, ConstantVisitor { - private final Method targetMethod; - private final UsingMethodHandler usageHandler; - - public MethodUsageFinder(Method targetMethod, UsingMethodHandler usageHandler) { - this.targetMethod = targetMethod; - this.usageHandler = usageHandler; - } - - @Override - public void visitAnyClass(Clazz clazz) {} - - @Override - public void visitProgramClass(ProgramClass programClass) { - programClass.methodsAccept(this); - } - - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - programMethod.accept(programClass, - new AllAttributeVisitor( - new AllInstructionVisitor( - new InstructionOpCodeFilter( - new int[] { - Instruction.OP_INVOKESTATIC, - Instruction.OP_INVOKEVIRTUAL, - Instruction.OP_INVOKESPECIAL - }, - this - ))) - ); - } - - @Override - public void visitConstantInstruction(Clazz consumingCallClazz, Method possibleMethodUser, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { - Method referencedMethod = new RefMethodFinder(consumingCallClazz).findReferencedMethod(constantInstruction); - if (referencedMethod != null && referencedMethod.equals(targetMethod)) { - System.out.println("Found a user, method " + possibleMethodUser.getName(consumingCallClazz) + " in class " + consumingCallClazz.getName()); - usageHandler.handle(consumingCallClazz, possibleMethodUser, constantInstruction, offset); - } - } - - public interface UsingMethodHandler { - void handle(Clazz clazz, Method method, ConstantInstruction constantInstruction, int offset); - } -} diff --git a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java index d0108e5d7..ae5bbc294 100644 --- a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java @@ -43,10 +43,8 @@ public NullCheckRemover(int argumentIndex) { } public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { - //System.out.println(instruction.toString(clazz, offset)); instruction.accept(clazz, method, codeAttribute, offset, insSeqMatcher); if (insSeqMatcher.isMatching() && insSeqMatcher.matchedConstantIndex(X) == argumentIndex) { - //System.out.println(" -> matching sequence starting at [" + insSeqMatcher.matchedInstructionOffset(0) + "]"); insSeqMatcher.matchedConstantIndex(Y); clazz.constantPoolEntryAccept(insSeqMatcher.matchedArgument(Y), new ConstantVisitor() { diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index d02258fcb..ba3707375 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -1,22 +1,21 @@ package proguard.optimize.inline; -import proguard.optimize.inline.lambda_locator.LambdaLocator; import proguard.AppView; -import proguard.classfile.*; +import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.ProgramClass; +import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.Attribute; import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.constant.MethodrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.ClassEditor; -import proguard.classfile.editor.CodeAttributeEditor; -import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.VariableInstruction; import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; -import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index 3a5b1d418..febfcdfb7 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -7,10 +7,7 @@ import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; -import proguard.classfile.instruction.visitor.AllInstructionVisitor; -import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.InternalTypeEnumeration; -import proguard.classfile.visitor.ClassPrinter; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.evaluation.value.InstructionOffsetValue; diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java index 690daa83a..634c8b0e6 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -2,10 +2,9 @@ import proguard.backport.LambdaExpression; import proguard.backport.LambdaExpressionCollector; -//import proguard.classfile.*; +import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.Method; -import proguard.classfile.ClassPool; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; import proguard.classfile.attribute.CodeAttribute; @@ -20,10 +19,14 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; public class LambdaLocator implements InstructionVisitor, ConstantVisitor, MemberVisitor { - private final Map>> classLambdas = new HashMap<>(); private final List staticLambdas = new ArrayList<>(); private final Map staticLambdaMap = new HashMap<>(); private final Set lambdaClasses = new HashSet<>(); @@ -75,10 +78,6 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { } } - public Map>> getLambdasByClass() { - return classLambdas; - } - public List getStaticLambdas() { return staticLambdas; } @@ -116,10 +115,6 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { if (lambdaClasses.contains(referencedClazz)) { System.out.println("Found a lambda invocation " + constantInstruction); - classLambdas.putIfAbsent(clazz, new HashMap<>()); - classLambdas.get(clazz).putIfAbsent(method, new HashSet<>()); - classLambdas.get(clazz).get(method).add(new Lambda(clazz, method, codeAttribute, offset, constantInstruction)); - Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); staticLambdas.add(lambda); staticLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); From cf74760194259f6fc62084772d6ad6d3fe057ced Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 14:49:21 +0200 Subject: [PATCH 15/72] Cleaned up the while loop in the LambdaUsageFinder by using the new IterativeInstructionVisitor class --- .../inline/IterativeInstructionVisitor.java | 39 +++++++ .../optimize/inline/LambdaInliner.java | 2 +- .../optimize/inline/LambdaUsageFinder.java | 107 ++++++------------ 3 files changed, 72 insertions(+), 76 deletions(-) create mode 100644 base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java diff --git a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java new file mode 100644 index 000000000..8a2b86f0b --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java @@ -0,0 +1,39 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.instruction.Instruction; +import proguard.classfile.instruction.InstructionFactory; +import proguard.classfile.instruction.visitor.InstructionVisitor; + +public class IterativeInstructionVisitor { + private boolean changed; + + /** + * This helper method does something similar to codeAttribute.instructionsAccept but has the ability to stop and + * restart visiting the instructions, this is useful for when you change the code during iteration. + */ + public void instructionsAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, InstructionVisitor instructionVisitor) { + int offset = 0; + while (offset < codeAttribute.u4codeLength) { + Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); + int instructionLength = instruction.length(offset); + changed = false; + instruction.accept(clazz, method, codeAttribute, offset, instructionVisitor); + if (changed) { + offset = 0; + continue; + } + offset += instructionLength; + } + } + + public boolean isChanged() { + return changed; + } + + public void setChanged(boolean changed) { + this.changed = changed; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 6f5130178..6093434ef 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -40,7 +40,7 @@ public void execute(AppView appView) { inlinedAllUsages = true; InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), - new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), appView, + new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), new LambdaUsageHandler(appView, remainder) ) ); diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 7b52d15e6..a3f2cd4dc 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -1,6 +1,5 @@ package proguard.optimize.inline; -import proguard.AppView; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; @@ -10,8 +9,8 @@ import proguard.classfile.constant.visitor.ConstantVisitor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; -import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.SimpleInstruction; +import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassUtil; import proguard.classfile.visitor.ClassPrinter; @@ -20,8 +19,6 @@ import proguard.optimize.inline.lambda_locator.Lambda; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.function.Function; @@ -31,76 +28,34 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, private final Lambda targetLambda; private PartialEvaluator partialEvaluator; private final Map lambdaMap; - private final AppView appView; private final LambdaInliner.LambdaUsageHandler lambdaUsageHandler; public MethodrefConstant methodrefConstant; public FieldrefConstant referencedFieldConstant; - private final boolean inlineFromFields; - private final boolean inlineFromMethods; - private boolean changed = false; - private final int[] typedReturnInstructions = new int[] { - Instruction.OP_IRETURN, - Instruction.OP_LRETURN, - Instruction.OP_FRETURN, - Instruction.OP_DRETURN, - Instruction.OP_ARETURN - }; - - public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, boolean inlineFromFields, boolean inlineFromMethods, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { + private final IterativeInstructionVisitor iterativeInstructionVisitor; + + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { this.targetLambda = targetLambda; this.partialEvaluator = new PartialEvaluator(); this.lambdaMap = lambdaMap; - this.appView = appView; this.lambdaUsageHandler = lambdaUsageHandler; - this.inlineFromFields = inlineFromFields; - this.inlineFromMethods = inlineFromMethods; - } - - public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, AppView appView, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { - this(targetLambda, lambdaMap, appView, false, false, lambdaUsageHandler); + this.iterativeInstructionVisitor = new IterativeInstructionVisitor(); } @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); - /*codeAttribute.instructionsAccept(clazz, method, - new InstructionOpCodeFilter( - new int[] { - Instruction.OP_INVOKESTATIC, - Instruction.OP_INVOKEVIRTUAL, - Instruction.OP_IRETURN, - Instruction.OP_LRETURN, - Instruction.OP_FRETURN, - Instruction.OP_DRETURN, - Instruction.OP_ARETURN - }, - this - ) - );*/ - long lastTime = System.currentTimeMillis(); - int offset = 0; - while (offset < codeAttribute.u4codeLength) - { - // Note that the instruction is only volatile. - Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); - int instructionLength = instruction.length(offset); - HashSet opcodes = new HashSet<>(Arrays.asList(Instruction.OP_INVOKESTATIC, Instruction.OP_INVOKEVIRTUAL)); - if (opcodes.contains(instruction.opcode)) { - changed = false; - instruction.accept(clazz, method, codeAttribute, offset, this); - if (changed) { - System.out.println("Start another iteration"); - //visitCodeAttribute(clazz, method, codeAttribute); - codeAttribute.accept(clazz, method, this); - break; - } - } - if (System.currentTimeMillis() > lastTime + 2000) { - System.out.printf("Progress %s/%s\n", offset, codeAttribute.u4codeLength); - lastTime = System.currentTimeMillis(); - } - offset += instructionLength; - } + iterativeInstructionVisitor.instructionsAccept( + clazz, + method, + codeAttribute, + new InstructionOpCodeFilter( + new int[] { + Instruction.OP_INVOKESTATIC, + Instruction.OP_INVOKEVIRTUAL + }, + this + ) + ); } @Override @@ -160,19 +115,21 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, } if (match) { - changed = lambdaUsageHandler.handle( - targetLambda, - methodrefConstant.referencedClass, - methodrefConstant.referencedMethod, - offset, - clazz, - method, - codeAttribute, - trace, - leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { - ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); - return lambdaMap.get(getStaticInstruction.constantIndex); - }).collect(Collectors.toList()) + iterativeInstructionVisitor.setChanged( + lambdaUsageHandler.handle( + targetLambda, + methodrefConstant.referencedClass, + methodrefConstant.referencedMethod, + offset, + clazz, + method, + codeAttribute, + trace, + leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { + ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); + return lambdaMap.get(getStaticInstruction.constantIndex); + }).collect(Collectors.toList()) + ) ); } } From 6ae5fc320584b11c4f5e1d26e23134aed4863ac6 Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 15:01:55 +0200 Subject: [PATCH 16/72] Removed unused class and method added a logger in lambda locator --- .../optimize/inline/InstructionAtOffset.java | 22 ------------- .../optimize/inline/RefMethodFinder.java | 32 ------------------- .../inline/lambda_locator/LambdaLocator.java | 6 +++- 3 files changed, 5 insertions(+), 55 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/inline/RefMethodFinder.java diff --git a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java index ec5ddacb9..4f40851ea 100644 --- a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java +++ b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java @@ -20,26 +20,4 @@ public Instruction instruction() { public int offset() { return offset; } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - InstructionAtOffset that = (InstructionAtOffset) obj; - return Objects.equals(this.instruction, that.instruction) && - this.offset == that.offset; - } - - @Override - public int hashCode() { - return Objects.hash(instruction, offset); - } - - @Override - public String toString() { - return "InstructionAtOffset[" + - "instruction=" + instruction + ", " + - "offset=" + offset + ']'; - } - } diff --git a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java deleted file mode 100644 index 4f3a38c9a..000000000 --- a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java +++ /dev/null @@ -1,32 +0,0 @@ -package proguard.optimize.inline; - -import proguard.classfile.Clazz; -import proguard.classfile.Member; -import proguard.classfile.Method; -import proguard.classfile.ProgramClass; -import proguard.classfile.ProgramMethod; -import proguard.classfile.instruction.ConstantInstruction; -import proguard.classfile.visitor.MemberVisitor; -import proguard.classfile.visitor.ReferencedMemberVisitor; - -public class RefMethodFinder { - private final Clazz clazz; - private Method foundMethod; - public RefMethodFinder(Clazz clazz) { - this.clazz = clazz; - this.foundMethod = null; - } - - public Method findReferencedMethod(ConstantInstruction constantInstruction) { - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new ReferencedMemberVisitor(new MemberVisitor() { - @Override - public void visitAnyMember(Clazz clazz, Member member) {} - - @Override - public void visitProgramMethod(ProgramClass programClass, ProgramMethod programMethod) { - foundMethod = programMethod; - } - })); - return foundMethod; - } -} diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java index 634c8b0e6..a3a80ebef 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -1,5 +1,8 @@ package proguard.optimize.inline.lambda_locator; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.ConfigurationVerifier; import proguard.backport.LambdaExpression; import proguard.backport.LambdaExpressionCollector; import proguard.classfile.ClassPool; @@ -31,6 +34,7 @@ public class LambdaLocator implements InstructionVisitor, ConstantVisitor, Membe private final Map staticLambdaMap = new HashMap<>(); private final Set lambdaClasses = new HashSet<>(); private final ClassPool classPool; + private static final Logger logger = LogManager.getLogger(LambdaLocator.class); public LambdaLocator(ClassPool classPool, String classNameFilter) { this.classPool = classPool; @@ -113,7 +117,7 @@ public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { classPool.classAccept(utf8Constant.getString(), referencedClazz -> { if (lambdaClasses.contains(referencedClazz)) { - System.out.println("Found a lambda invocation " + constantInstruction); + logger.info("Found a lambda invocation " + constantInstruction); Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); staticLambdas.add(lambda); From 33aa83dc9f39095801325625f33a8b04d4c3985c Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 15:39:51 +0200 Subject: [PATCH 17/72] Add more comments to the RecursiveInliner --- .../optimize/inline/RecursiveInliner.java | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 3ff3ed37f..f68d1d0e7 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -29,6 +29,9 @@ /** * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. + *

+ * If we s ee a lambda is used by a method being called from within the consuming method we will currently abort the + * inlining process. */ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor, ConstantVisitor { private Clazz consumingClazz; @@ -45,6 +48,10 @@ public RecursiveInliner() { @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + /** + * Given a code attribute, look at all the call instructions and see if they call a method that uses the lambda we + * are currently attempting to inline. + */ @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { codeAttribute.accept(clazz, method, new ClassPrinter()); @@ -83,7 +90,11 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; - + /* + * If the source instruction was an aload_x instruction we will compare the x with the index of the + * lambda that we are currently inlining. Because arguments can sometimes take up 2 slots on the stack + * we have to adjust this index accordingly. + */ String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); @@ -103,6 +114,11 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM programMethod.attributesAccept(programClass, this); } + /** + * This class visits call instructions and visits the source instructions of each argument of each call using the + * sourceInstructionVisitor. The sourceInstructionVisitor can then check if the argument is in fact the lambda we + * are currently inlining. + */ private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor, AttributeVisitor { private final InstructionVisitor sourceInstructionVisitor; private TracedStack tracedStack; @@ -111,10 +127,24 @@ private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor private CodeAttribute codeAttribute; private int callOffset; + /** + * @param sourceInstructionVisitor This is the visitor that will visit the source instructions for the arguments + * of method calls. The source instructions are the instructions that originally + * produced the value used in the argument. In the context of recursive inlining + * this could be something like aload_1 which loads the first argument in a + * virtual function, we also know which index the lambda argument is at and + * that allows us to see if this method makes use of the lambda argument of the + * consuming method or not. + */ public CalledMethodHandler(InstructionVisitor sourceInstructionVisitor) { this.sourceInstructionVisitor = sourceInstructionVisitor; } + /** + * This method will visit all the call instructions, it will visit the constantIndex using a constant visitor + * to see which method is referenced depending on if it's an invokeinterface/invokedynamic/invokestatic/... + * call. + */ @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int callOffset, ConstantInstruction constantInstruction) { this.clazz = clazz; @@ -127,6 +157,7 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c if (tracedStack.size() <= 0) return; + // Handle the referenced method for all call instructions by visiting the constantIndex. clazz.constantPoolEntryAccept(constantInstruction.constantIndex, this); } @@ -153,9 +184,16 @@ public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConst handleCalledMethod(interfaceMethodrefConstant.referencedClass, interfaceMethodrefConstant.referencedMethod); } - public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { - // There can be null checks on the lambda parameter, this doesn't stop us from inlining because we will - // remove these later. + /** + * This is a helper method that handles the case where a referencedMethod is called from the consuming method. + * @param referencedClass The class in which the method that is called from within the consuming method resides. + * @param referencedMethod The method that is called from within the consuming method. + */ + private void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { + /* + * Null checks can be on the lambda parameter, these are also function calls but they shouldn't stop us from + * inlining because we will remove these later. + */ if (referencedClass.getName().equals("kotlin/jvm/internal/Intrinsics") && referencedMethod.getName(referencedClass).equals("checkNotNullParameter") && referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) From 1d49b49c864433330e440b2027f2dbb159c6781c Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 15:43:22 +0200 Subject: [PATCH 18/72] removed print and added logging --- .../src/main/java/proguard/Configuration.java | 2 +- .../optimize/inline/BaseLambdaInliner.java | 1 - .../optimize/inline/LambdaUsageFinder.java | 22 ++++++++++--------- .../optimize/inline/RecursiveInliner.java | 15 +++++-------- .../java/proguard/optimize/inline/Util.java | 12 ++++++---- .../inline/lambda_locator/LambdaLocator.java | 6 ++--- 6 files changed, 30 insertions(+), 28 deletions(-) diff --git a/base/src/main/java/proguard/Configuration.java b/base/src/main/java/proguard/Configuration.java index 5804247a4..2787bcd17 100644 --- a/base/src/main/java/proguard/Configuration.java +++ b/base/src/main/java/proguard/Configuration.java @@ -203,7 +203,7 @@ public class Configuration /** * Specifies whether lambdas should be inlined. */ - public boolean lambdaInlining = false; + public boolean lambdaInlining = true; /////////////////////////////////////////////////////////////////////////// // Obfuscation options. diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index d5d8137ca..78c9f5622 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -178,7 +178,6 @@ public void visitAnyMember(Clazz clazz, Member member) { lambda.clazz().constantPoolEntryAccept(lambda.constantInstruction().constantIndex, new ConstantVisitor() { @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { - System.out.println(fieldrefConstant); ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); int lambdaInstanceFieldIndex = constantPoolEditor.addFieldrefConstant(fieldrefConstant.referencedClass, fieldrefConstant.referencedField); diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index a3f2cd4dc..d802accb8 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -1,5 +1,7 @@ package proguard.optimize.inline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; @@ -13,7 +15,6 @@ import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassUtil; -import proguard.classfile.visitor.ClassPrinter; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.optimize.inline.lambda_locator.Lambda; @@ -32,6 +33,8 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, public MethodrefConstant methodrefConstant; public FieldrefConstant referencedFieldConstant; private final IterativeInstructionVisitor iterativeInstructionVisitor; + private static final Logger logger = LogManager.getLogger(LambdaUsageFinder.class); + public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { this.targetLambda = targetLambda; @@ -84,17 +87,17 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, return; if (methodrefConstant.referencedMethod == null) - System.out.println(methodrefConstant.getClassName(clazz) + "#" + methodrefConstant.getName(clazz)); + logger.debug(methodrefConstant.getClassName(clazz) + "#" + methodrefConstant.getName(clazz)); - System.out.println(methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass)); + logger.debug(methodrefConstant.referencedMethod.getDescriptor(methodrefConstant.referencedClass)); - System.out.println("--------Start----------"); - System.out.println(consumingMethodCallInstruction); + logger.debug("--------Start----------"); + logger.debug(consumingMethodCallInstruction); partialEvaluator = new PartialEvaluator(); partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); TracedStack tracedStack = partialEvaluator.getStackBefore(offset); - System.out.println(tracedStack); + logger.debug(tracedStack); for (int argIndex = 0; argIndex < argCount; argIndex++) { int sizeAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); @@ -105,10 +108,9 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, Util.traceParameterTree(partialEvaluator, codeAttribute, traceOffset, leafNodes); boolean match = false; for (InstructionAtOffset tracedInstructionAtOffset : leafNodes) { - System.out.println("Argument " + argIndex + " source " + tracedInstructionAtOffset); + logger.debug("Argument " + argIndex + " source " + tracedInstructionAtOffset); if (condition.apply(tracedInstructionAtOffset)) { - codeAttribute.accept(clazz, method, new ClassPrinter()); - System.out.println("Lambda " + targetLambda + " consumed by " + methodrefConstant.referencedMethod.getName(methodrefConstant.referencedClass)); + logger.debug("Lambda " + targetLambda + " consumed by " + methodrefConstant.referencedMethod.getName(methodrefConstant.referencedClass)); match = true; break; } @@ -133,7 +135,7 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, ); } } - System.out.println("---------End-----------"); + logger.debug("---------End-----------"); } private boolean isTargetLambda(InstructionAtOffset instructionAtOffset) { diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 3ff3ed37f..d70133700 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -1,5 +1,7 @@ package proguard.optimize.inline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; import proguard.classfile.Method; @@ -21,7 +23,6 @@ import proguard.classfile.instruction.visitor.InstructionOpCodeFilter; import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.ClassUtil; -import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; @@ -35,6 +36,8 @@ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, M private Method copiedConsumingMethod; private boolean isStatic; private final PartialEvaluator partialEvaluator; + private static final Logger logger = LogManager.getLogger(RecursiveInliner.class); + public RecursiveInliner() { this.consumingClazz = null; @@ -47,7 +50,6 @@ public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - codeAttribute.accept(clazz, method, new ClassPrinter()); partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); codeAttribute.instructionsAccept(clazz, method, new InstructionOpCodeFilter( @@ -73,14 +75,13 @@ public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAt @Override public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { - System.out.println("Value from " + variableInstruction + " " + variableInstruction.variableIndex); - + logger.debug("Value from " + variableInstruction + " " + variableInstruction.variableIndex); Instruction sourceInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); if (sourceInstruction == null) { throw new CannotInlineException("Argument has multiple source locations, cannot inline lambda!"); } - System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); + logger.debug(variableInstruction + " gets it's value from " + sourceInstruction); if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; @@ -109,7 +110,6 @@ private class CalledMethodHandler implements InstructionVisitor, ConstantVisitor private Clazz clazz; private Method method; private CodeAttribute codeAttribute; - private int callOffset; public CalledMethodHandler(InstructionVisitor sourceInstructionVisitor) { this.sourceInstructionVisitor = sourceInstructionVisitor; @@ -120,10 +120,8 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c this.clazz = clazz; this.method = method; this.codeAttribute = codeAttribute; - this.callOffset = callOffset; this.tracedStack = partialEvaluator.getStackBefore(callOffset); - System.out.println("Stack size " + tracedStack.size()); if (tracedStack.size() <= 0) return; @@ -166,7 +164,6 @@ public void handleCalledMethod(Clazz referencedClass, Method referencedMethod) { boolean referencedMethodStatic = (referencedMethod.getAccessFlags() & AccessConstants.STATIC) != 0; // We will now check if any of the arguments has a value that comes from the lambda parameters for (int argIndex = 0; argIndex < argCount; argIndex++) { - System.out.println("Stack before offset: " + partialEvaluator.getStackBefore(callOffset)); int sizeAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argIndex); int stackEntryIndex = tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, referencedMethodStatic, argCount) + sizeAdjustedIndex; int traceOffset = tracedStack.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue().instructionOffset(0); diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index b215286a8..d793eddaf 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -1,5 +1,7 @@ package proguard.optimize.inline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; @@ -17,6 +19,8 @@ import java.util.List; public class Util { + private static final Logger logger = LogManager.getLogger(Util.class); + /** * Given an offset of an instruction, trace the source producer value. */ @@ -49,9 +53,9 @@ public static List traceParameterOffset(PartialEvaluator pa currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && currentInstruction.opcode != Instruction.OP_GETFIELD ) { - System.out.println(currentInstruction.toString(currentOffset)); + logger.debug(currentInstruction.toString(currentOffset)); currentTracedStack = partialEvaluator.getStackBefore(currentOffset); - System.out.println(currentTracedStack); + logger.debug(currentTracedStack); // There is no stack value, it's coming from the variables if (isLoad(currentInstruction)) { @@ -94,9 +98,9 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && currentInstruction.opcode != Instruction.OP_GETFIELD ) { - System.out.println(currentInstruction.toString(offset)); + logger.debug(currentInstruction.toString(offset)); currentTracedStack = partialEvaluator.getStackBefore(offset); - System.out.println(currentTracedStack); + logger.debug(currentTracedStack); // There is no stack value, it's coming from the variables InstructionOffsetValue offsetValue; diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java index a3a80ebef..fbf67a511 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java @@ -2,7 +2,6 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.ConfigurationVerifier; import proguard.backport.LambdaExpression; import proguard.backport.LambdaExpressionCollector; import proguard.classfile.ClassPool; @@ -35,7 +34,6 @@ public class LambdaLocator implements InstructionVisitor, ConstantVisitor, Membe private final Set lambdaClasses = new HashSet<>(); private final ClassPool classPool; private static final Logger logger = LogManager.getLogger(LambdaLocator.class); - public LambdaLocator(ClassPool classPool, String classNameFilter) { this.classPool = classPool; @@ -53,6 +51,8 @@ public LambdaLocator(ClassPool classPool, String classNameFilter) { clazz.methodsAccept(this); }); + + logger.info("Number of lambdas found : " + staticLambdas.size()); } @Override @@ -117,7 +117,7 @@ public void visitClassConstant(Clazz clazz, ClassConstant classConstant) { public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { classPool.classAccept(utf8Constant.getString(), referencedClazz -> { if (lambdaClasses.contains(referencedClazz)) { - logger.info("Found a lambda invocation " + constantInstruction); + logger.debug("Found a lambda invocation " + constantInstruction); Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); staticLambdas.add(lambda); From cb60c132b39007d6b46505d8002f4ba37663e437 Mon Sep 17 00:00:00 2001 From: timothy Date: Mon, 7 Aug 2023 15:54:34 +0200 Subject: [PATCH 19/72] refactored test and added performance tests --- .../kotlin/proguard/lambdaInline/TestUtil.kt | 6 +- .../{ => correctness}/AdvancedTest.kt | 2 +- .../{ => correctness}/FieldInliningTest.kt | 2 +- .../{ => correctness}/LambdaReturnTest.kt | 2 +- .../{ => correctness}/OneLambdaInArgsTest.kt | 2 +- .../ThreeLambdasInArgsTest.kt | 2 +- .../{ => correctness}/TwoLambdasInArgsTest.kt | 2 +- .../{ => correctness}/TypeTest.kt | 2 +- .../performance/PerformanceTest.kt | 366 ++++++++++++++++++ 9 files changed, 376 insertions(+), 10 deletions(-) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/AdvancedTest.kt (99%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/FieldInliningTest.kt (96%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/LambdaReturnTest.kt (93%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/OneLambdaInArgsTest.kt (99%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/ThreeLambdasInArgsTest.kt (98%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/TwoLambdasInArgsTest.kt (99%) rename base/src/test/kotlin/proguard/lambdaInline/{ => correctness}/TypeTest.kt (99%) create mode 100644 base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt diff --git a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt index 3650199f9..a249b9569 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt @@ -327,7 +327,7 @@ fun testPerf(code: TestSource, inlineCode: TestSource, cleanUp: Boolean): Triple //test val originalTimes = mutableListOf() var i = 0 - while (i < 10000) { + while (i < 100) { i++ originalTimes.add(measureTimeMillis { ProcessBuilder("java", "-jar", "test-files/original.jar") @@ -340,7 +340,7 @@ fun testPerf(code: TestSource, inlineCode: TestSource, cleanUp: Boolean): Triple val originalInlinedTimes = mutableListOf() i = 0 - while (i < 10000) { + while (i < 100) { i++ originalInlinedTimes.add(measureTimeMillis { ProcessBuilder("java", "-jar", "test-files/originalInlined.jar") @@ -353,7 +353,7 @@ fun testPerf(code: TestSource, inlineCode: TestSource, cleanUp: Boolean): Triple val generatedTimes = mutableListOf() i = 0 - while (i < 10000) { + while (i < 100) { i++ generatedTimes.add(measureTimeMillis { ProcessBuilder("java", "-jar", "test-files/result.jar") diff --git a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt index 46976dd55..a70a2dae9 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt similarity index 96% rename from base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt index c779b0b50..9ad8808fd 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/FieldInliningTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import io.kotest.core.spec.style.FreeSpec import onlyTestRunning diff --git a/base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt similarity index 93% rename from base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt index 05d7b49eb..21c5760d5 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/LambdaReturnTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import io.kotest.core.spec.style.FreeSpec import onlyTestRunning diff --git a/base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt index 489676771..7f504d64b 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/OneLambdaInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt similarity index 98% rename from base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt index ffd5650d2..28907895b 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/ThreeLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt index f1e68de35..0d9296fde 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TwoLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt rename to base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt index eeca8bc9a..1502ba45b 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TypeTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline +package proguard.lambdaInline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt b/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt new file mode 100644 index 000000000..158082b22 --- /dev/null +++ b/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt @@ -0,0 +1,366 @@ +package performance + +import io.kotest.core.spec.style.FreeSpec +import proguard.testutils.KotlinSource +import testPerf + +class PerformanceTest: FreeSpec ({ + "basic case" - { + "10" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 10) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 10) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + + """ + ) + println("easy 10") + + testPerf(code, inlineCode, true) + } + "1000" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 1000) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 1000) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + + """ + ) + println("easy 1000") + + testPerf(code, inlineCode, true) + } + "100000" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 100000) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) } + + fun main() { + var index = 0 + while (index < 100000) { + index += 1 + test { a: Int -> (a * a) + 1 } + } + } + + """ + ) + println("easy 100000") + + testPerf(code, inlineCode, true) + } + } + + "hard case" - { + "1" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) + } + fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 1) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) + } + inline fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + inline fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 1) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + + """ + ) + println("hard 1") + + testPerf(code, inlineCode, false) + } + "10" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) + } + fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 10) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) + } + inline fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + inline fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 10) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + + """ + ) + println("hard 10") + testPerf(code, inlineCode, true) + } + "100" { + //setup + val code = KotlinSource( //create java file + "Main.kt", + """ + fun test(a: (Int) -> Int) { + a(12) + } + fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 100) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + """ + ) + + val inlineCode = KotlinSource( + "Main.kt", + """ + inline fun test(a: (Int) -> Int) { + a(12) + } + inline fun a(b: (String) -> Int) { + b("Hello World!") + 1 + } + + inline fun test1(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { + a(3) + b("Hello", true, 0) + c(5) + } + + + fun main() { + var index = 0 + while (index < 100) { + index += 1 + test { a: Int -> (a * a) + 1 } + a { b: String -> + var cnt = 0 + var i = 0 + while (i < b.length) { + cnt += b[i].code + i++ + } + cnt + } + test1({ a: Int -> a * a }, { a : String, b : Boolean, c : Int -> a.uppercase(); b}, {a : Int -> a+a}) + } + } + + """ + ) + + println("hard 100") + testPerf(code, inlineCode, true) + } + } +}) \ No newline at end of file From 3d3bf8f9cd07010c8e621036ef8e04e76647b93f Mon Sep 17 00:00:00 2001 From: MaartenS Date: Mon, 7 Aug 2023 16:42:20 +0200 Subject: [PATCH 20/72] Only stop inlining if we actually have the current lambda in one of the arguments --- .../inline/IterativeInstructionVisitor.java | 2 +- .../optimize/inline/LambdaUsageFinder.java | 6 +++ .../optimize/inline/RecursiveInliner.java | 38 +++++++++---------- .../java/proguard/optimize/inline/Util.java | 13 ++++++- 4 files changed, 38 insertions(+), 21 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java index 8a2b86f0b..420d0b62b 100644 --- a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java @@ -29,7 +29,7 @@ public void instructionsAccept(Clazz clazz, Method method, CodeAttribute codeAtt } } - public boolean isChanged() { + public boolean codeHasChanged() { return changed; } diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index a3f2cd4dc..dacb90a4f 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -131,6 +131,12 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, }).collect(Collectors.toList()) ) ); + + // We can't continue the loop because we already changed the code, the offset of the instruction we + // are currently operating on might have changed resulting in strange behaviour. + if (iterativeInstructionVisitor.codeHasChanged()) { + break; + } } } System.out.println("---------End-----------"); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index f68d1d0e7..655d7d7f3 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -26,6 +26,8 @@ import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; +import java.util.List; + /** * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. @@ -82,25 +84,23 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { System.out.println("Value from " + variableInstruction + " " + variableInstruction.variableIndex); - Instruction sourceInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); - if (sourceInstruction == null) { - throw new CannotInlineException("Argument has multiple source locations, cannot inline lambda!"); - } - - System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); - if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { - VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; - /* - * If the source instruction was an aload_x instruction we will compare the x with the index of the - * lambda that we are currently inlining. Because arguments can sometimes take up 2 slots on the stack - * we have to adjust this index accordingly. - */ - String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); - int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); - int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); - - if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { - throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); + List sourceInstructions = Util.traceParameterSources(partialEvaluator, codeAttribute, offset); + for (Instruction sourceInstruction : sourceInstructions) { + System.out.println(variableInstruction + " gets it's value from " + sourceInstruction); + if (sourceInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { + VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; + /* + * If the source instruction was an aload_x instruction we will compare the x with the index of the + * lambda that we are currently inlining. Because arguments can sometimes take up 2 slots on the stack + * we have to adjust this index accordingly. + */ + String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); + int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); + int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); + + if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { + throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); + } } } } diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index b215286a8..a3c546fe0 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -15,6 +15,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class Util { /** @@ -27,6 +28,15 @@ public static Instruction traceParameter(PartialEvaluator partialEvaluator, Code return trace.get(trace.size() - 1).instruction(); } + /** + * Given an offset of an instruction, trace the source producer values. + */ + public static List traceParameterSources(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { + ArrayList leafNodes = new ArrayList<>(); + traceParameterTree(partialEvaluator, codeAttribute, offset, leafNodes); + return leafNodes.stream().map(InstructionAtOffset::instruction).collect(Collectors.toList()); + } + public static List traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { List trace = new ArrayList<>(); TracedStack currentTracedStack; @@ -107,7 +117,8 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt offsetValue = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue(); } for (int i = 0; i < offsetValue.instructionOffsetCount(); i++) { - root.children.add(traceParameterTree(partialEvaluator, codeAttribute, offsetValue.instructionOffset(i), leafNodes)); + if (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(i)) + root.children.add(traceParameterTree(partialEvaluator, codeAttribute, offsetValue.instructionOffset(i), leafNodes)); } } else { From 8c354abec655e6aae9e3e91605241b81a4481eb2 Mon Sep 17 00:00:00 2001 From: timothy Date: Tue, 8 Aug 2023 10:44:22 +0200 Subject: [PATCH 21/72] refactored util --- .../optimize/inline/BaseLambdaInliner.java | 22 ++++++-- .../inline/FirstLambdaParameterFinder.java | 21 ++++++++ .../optimize/inline/LambdaUsageFinder.java | 4 +- .../optimize/inline/RecursiveInliner.java | 6 ++- .../inline/{Util.java => SourceTracer.java} | 54 +++++-------------- 5 files changed, 60 insertions(+), 47 deletions(-) create mode 100644 base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java rename base/src/main/java/proguard/optimize/inline/{Util.java => SourceTracer.java} (77%) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 78c9f5622..48e412691 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -31,11 +31,13 @@ import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.optimize.peephole.MethodInliner; import java.util.ArrayList; import java.util.List; import static java.lang.Math.max; +import static proguard.optimize.inline.FirstLambdaParameterFinder.findFirstLambdaParameter; public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; @@ -63,7 +65,7 @@ public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consuming this.lambda = lambda; this.codeAttributeEditor = new CodeAttributeEditor(); this.isStatic = (consumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - this.calledLambdaIndex = Util.findFirstLambdaParameter(consumingMethod.getDescriptor(consumingClass), isStatic); + this.calledLambdaIndex = findFirstLambdaParameter(consumingMethod.getDescriptor(consumingClass), isStatic); this.sizeAdjustedLamdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, calledLambdaIndex); this.partialEvaluator = new PartialEvaluator(); this.invokeMethodCallOffsets = new ArrayList<>(); @@ -151,7 +153,7 @@ public void visitAnyMember(Clazz clazz, Member member) { ); // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. - Util.inlineMethodInClass(consumingClass, staticInvokeMethod); + inlineMethodInClass(consumingClass, staticInvokeMethod); // Remove the static invoke method once it has been inlined ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); @@ -238,7 +240,7 @@ public LambdaInvokeUsageReplacer(int possibleLambdaInvokeCallOffset) { @Override public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { // Uses same codeAttributeEditor as the casting remover - Instruction tracedInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); + Instruction tracedInstruction = SourceTracer.traceParameter(partialEvaluator, codeAttribute, offset); if (tracedInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { variableInstruction = (VariableInstruction) tracedInstruction; @@ -316,4 +318,18 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt })); return length[0]; } + + /** + * Inline a specified method in the locations it is called in the current clazz. + * @param clazz The clazz in which we want to inline the targetMethod + * @param targetMethod The method we want to inline. + */ + private void inlineMethodInClass(Clazz clazz, Method targetMethod) { + clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, 20000, true, false, null) { + @Override + protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { + return method.equals(targetMethod); + } + })); + } } diff --git a/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java b/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java new file mode 100644 index 000000000..31e8f8ded --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java @@ -0,0 +1,21 @@ +package proguard.optimize.inline; + +import proguard.classfile.util.InternalTypeEnumeration; + +public class FirstLambdaParameterFinder { + public static int findFirstLambdaParameter(String descriptor) { + return findFirstLambdaParameter(descriptor, true); + } + + public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); + int index = 0; + while (internalTypeEnumeration.hasMoreTypes()) { + if (internalTypeEnumeration.nextType().startsWith("Lkotlin/jvm/functions/Function")) { + break; + } + index ++; + } + return index + (isStatic ? 0 : 1); + } +} diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index d802accb8..e70535361 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -103,9 +103,9 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, int sizeAdjustedIndex = ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argIndex); int stackEntryIndex = tracedStack.size() - ClassUtil.internalMethodVariableIndex(methodDescriptor, true, argCount) + sizeAdjustedIndex; int traceOffset = tracedStack.getBottomActualProducerValue(stackEntryIndex).instructionOffsetValue().instructionOffset(0); - List trace = Util.traceParameterOffset(partialEvaluator, codeAttribute, traceOffset); + List trace = SourceTracer.traceParameterOffset(partialEvaluator, codeAttribute, traceOffset); List leafNodes = new ArrayList<>(); - Util.traceParameterTree(partialEvaluator, codeAttribute, traceOffset, leafNodes); + SourceTracer.traceParameterTree(partialEvaluator, codeAttribute, traceOffset, leafNodes); boolean match = false; for (InstructionAtOffset tracedInstructionAtOffset : leafNodes) { logger.debug("Argument " + argIndex + " source " + tracedInstructionAtOffset); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index d70133700..1203c1a65 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -27,6 +27,8 @@ import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; +import static proguard.optimize.inline.FirstLambdaParameterFinder.findFirstLambdaParameter; + /** * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. @@ -76,7 +78,7 @@ public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAt public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, VariableInstruction variableInstruction) { if (variableInstruction.canonicalOpcode() == Instruction.OP_ALOAD) { logger.debug("Value from " + variableInstruction + " " + variableInstruction.variableIndex); - Instruction sourceInstruction = Util.traceParameter(partialEvaluator, codeAttribute, offset); + Instruction sourceInstruction = SourceTracer.traceParameter(partialEvaluator, codeAttribute, offset); if (sourceInstruction == null) { throw new CannotInlineException("Argument has multiple source locations, cannot inline lambda!"); } @@ -86,7 +88,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c VariableInstruction variableSourceInstruction = (VariableInstruction) sourceInstruction; String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); - int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); + int calledLambdaRealIndex = findFirstLambdaParameter(consumingMethodDescriptor); int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/SourceTracer.java similarity index 77% rename from base/src/main/java/proguard/optimize/inline/Util.java rename to base/src/main/java/proguard/optimize/inline/SourceTracer.java index d793eddaf..23dd66fd0 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/SourceTracer.java @@ -2,24 +2,19 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.classfile.Clazz; -import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; -import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; -import proguard.classfile.util.InternalTypeEnumeration; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.evaluation.value.InstructionOffsetValue; -import proguard.optimize.peephole.MethodInliner; import java.util.ArrayList; import java.util.List; -public class Util { - private static final Logger logger = LogManager.getLogger(Util.class); +public class SourceTracer { + private static final Logger logger = LogManager.getLogger(SourceTracer.class); /** * Given an offset of an instruction, trace the source producer value. @@ -31,6 +26,9 @@ public static Instruction traceParameter(PartialEvaluator partialEvaluator, Code return trace.get(trace.size() - 1).instruction(); } + /** + * Given an offset of an instruction, trace the source producer value. + */ public static List traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { List trace = new ArrayList<>(); TracedStack currentTracedStack; @@ -38,12 +36,15 @@ public static List traceParameterOffset(PartialEvaluator pa Instruction currentInstruction = InstructionFactory.create(codeAttribute.code, currentOffset); trace.add(new InstructionAtOffset(currentInstruction, currentOffset)); - // Maybe make use of stackPopCount, if an instruction doesn't pop anything from the stack then it is the producer? + // We stop when we found the source instruction while ( (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && currentInstruction.opcode != Instruction.OP_ACONST_NULL && currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_DCONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_FCONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_LCONST_0 && currentInstruction.opcode != Instruction.OP_LDC && currentInstruction.opcode != Instruction.OP_LDC_W && currentInstruction.opcode != Instruction.OP_LDC2_W && @@ -60,7 +61,7 @@ public static List traceParameterOffset(PartialEvaluator pa // There is no stack value, it's coming from the variables if (isLoad(currentInstruction)) { InstructionOffsetValue offsetValue = partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue(); - // There are multiple sources, we don't know which one! We could in the future return a tree instead of just null if that would be useful. + // There are multiple sources, we don't know which one! if (offsetValue.instructionOffsetCount() > 1) { return null; } @@ -83,12 +84,15 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt Instruction currentInstruction = InstructionFactory.create(codeAttribute.code, offset); Node root = new Node(new InstructionAtOffset(currentInstruction, offset), new ArrayList<>()); - // Maybe make use of stackPopCount, if an instruction doesn't pop anything from the stack then it is the producer? + // We stop when we found the source instruction if ( (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && currentInstruction.opcode != Instruction.OP_ACONST_NULL && currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_DCONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_FCONST_0 && + currentInstruction.canonicalOpcode() != Instruction.OP_LCONST_0 && currentInstruction.opcode != Instruction.OP_LDC && currentInstruction.opcode != Instruction.OP_LDC_W && currentInstruction.opcode != Instruction.OP_LDC2_W && @@ -120,34 +124,4 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt } return root; } - - public static int findFirstLambdaParameter(String descriptor) { - return findFirstLambdaParameter(descriptor, true); - } - - public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { - InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); - int index = 0; - while (internalTypeEnumeration.hasMoreTypes()) { - if (internalTypeEnumeration.nextType().startsWith("Lkotlin/jvm/functions/Function")) { - break; - } - index ++; - } - return index + (isStatic ? 0 : 1); - } - - /** - * Inline a specified method in the locations it is called in the current clazz. - * @param clazz The clazz in which we want to inline the targetMethod - * @param targetMethod The method we want to inline. - */ - public static void inlineMethodInClass(Clazz clazz, Method targetMethod) { - clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, 20000, true, false, null) { - @Override - protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { - return method.equals(targetMethod); - } - })); - } } From 813be34161a28c8ac1a3d5c070846b0e2f8c4a76 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 13:59:48 +0200 Subject: [PATCH 22/72] Use argument index from LambdaUsageFinder instead of searching in the descriptor This is better because it fixes the case where a lambda is attempted to be inlined into a method that takes object as argument which is then later casted back into a lambda. --- .../optimize/inline/BaseLambdaInliner.java | 24 +++++++---- .../optimize/inline/CastPatternRemover.java | 7 ++-- .../optimize/inline/LambdaInliner.java | 4 +- .../optimize/inline/LambdaUsageFinder.java | 1 + .../java/proguard/optimize/inline/Util.java | 41 +++++++++++-------- 5 files changed, 48 insertions(+), 29 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index d5d8137ca..df0212132 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -15,7 +15,12 @@ import proguard.classfile.constant.FieldrefConstant; import proguard.classfile.constant.InterfaceMethodrefConstant; import proguard.classfile.constant.visitor.ConstantVisitor; -import proguard.classfile.editor.*; +import proguard.classfile.editor.AccessFixer; +import proguard.classfile.editor.ClassEditor; +import proguard.classfile.editor.CodeAttributeEditor; +import proguard.classfile.editor.ConstantPoolEditor; +import proguard.classfile.editor.MethodCopier; +import proguard.classfile.editor.PeepholeEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; @@ -27,6 +32,7 @@ import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InternalTypeEnumeration; +import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; @@ -56,15 +62,15 @@ public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, Con private InterfaceMethodrefConstant referencedInterfaceConstant; private final List invokeMethodCallOffsets; - public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, Lambda lambda) { + public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { this.consumingClass = consumingClass; this.consumingMethod = consumingMethod; this.appView = appView; this.lambda = lambda; this.codeAttributeEditor = new CodeAttributeEditor(); this.isStatic = (consumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; - this.calledLambdaIndex = Util.findFirstLambdaParameter(consumingMethod.getDescriptor(consumingClass), isStatic); - this.sizeAdjustedLamdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, calledLambdaIndex); + this.calledLambdaIndex = calledLambdaIndex + (isStatic ? 0 : 1); + this.sizeAdjustedLamdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, this.calledLambdaIndex); this.partialEvaluator = new PartialEvaluator(); this.invokeMethodCallOffsets = new ArrayList<>(); } @@ -136,15 +142,15 @@ public void visitAnyMember(Clazz clazz, Member member) { new AllInstructionVisitor( new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); - // Remove return value's casting from staticInvokeMethod - staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new AllInstructionVisitor(new CastPatternRemover()))); - if (referencedInterfaceConstant != null) { // Remove casting before and after invoke method call // Uses same codeAttributeEditor as LambdaInvokeReplacer copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PrePostCastRemover(invokeMethodDescriptor))); } + // Remove return value's casting from staticInvokeMethod + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); + // Important for inlining, we need this so that method invocations have non-null referenced methods. appView.programClassPool.classesAccept( new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) @@ -168,6 +174,8 @@ public void visitAnyMember(Clazz clazz, Member member) { while(internalTypeEnumeration.hasMoreTypes()) { list.add(internalTypeEnumeration.nextType()); } + // We adjust the index to not take the this parameter into account because this is not visible in the + // method descriptor. list.remove(calledLambdaIndex - (isStatic ? 0 : 1)); return ClassUtil.internalMethodDescriptorFromInternalTypes(internalTypeEnumeration.returnType(), list); @@ -285,6 +293,8 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt for (int invokeMethodCallOffset : invokeMethodCallOffsets) { int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); int endOffset = invokeMethodCallOffset + 8 + 1; + // If the next instruction is pop then we are not using the return value so we don't need to remove + // casts on the return value. if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode == Instruction.OP_POP) { endOffset = invokeMethodCallOffset; } diff --git a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java index 9081f22b3..b1cb648a1 100644 --- a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java @@ -21,20 +21,20 @@ public class CastPatternRemover implements InstructionVisitor { private final Logger logger = LogManager.getLogger(this.getClass()); private final InstructionSequenceMatcher insSeqMatcher; + private final CodeAttributeEditor codeAttributeEditor; - public CastPatternRemover() { + public CastPatternRemover(CodeAttributeEditor codeAttributeEditor) { InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(); Constant[] constants = ____.constants(); Instruction[] pattern = ____.invokestatic(InstructionSequenceMatcher.X).areturn().__(); this.insSeqMatcher = new InstructionSequenceMatcher(constants, pattern); + this.codeAttributeEditor = codeAttributeEditor; } @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { instruction.accept(clazz, method, codeAttribute, offset, insSeqMatcher); if (insSeqMatcher.isMatching()) { - CodeAttributeEditor codeAttributeEditor = new CodeAttributeEditor(); - codeAttributeEditor.reset(codeAttribute.u4codeLength); int constantIndex = insSeqMatcher.matchedConstantIndex(InstructionSequenceMatcher.X); clazz.constantPoolEntryAccept(constantIndex, new ConstantVisitor() { @Override @@ -50,7 +50,6 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst } } }); - codeAttribute.accept(clazz, method, codeAttributeEditor); } } } diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 6093434ef..2c0d461e4 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -72,9 +72,9 @@ public LambdaUsageHandler(AppView appView, Set remainder) { this.remainder = remainder; } - boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { + boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int lambdaArgumentIndex, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { // Try inlining the lambda in consumingMethod - BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClazz, consumingMethod, lambda); + BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); Method inlinedLambamethod = baseLambdaInliner.inline(); // We didn't inline anything so no need to change any call instructions. diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index dacb90a4f..43b725638 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -120,6 +120,7 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, targetLambda, methodrefConstant.referencedClass, methodrefConstant.referencedMethod, + argIndex, offset, clazz, method, diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index a3c546fe0..619f03064 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -91,18 +91,18 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt // Maybe make use of stackPopCount, if an instruction doesn't pop anything from the stack then it is the producer? if ( - (!isLoad(currentInstruction) || - !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && - currentInstruction.opcode != Instruction.OP_ACONST_NULL && - currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && - currentInstruction.opcode != Instruction.OP_LDC && - currentInstruction.opcode != Instruction.OP_LDC_W && - currentInstruction.opcode != Instruction.OP_LDC2_W && - currentInstruction.opcode != Instruction.OP_NEW && - currentInstruction.opcode != Instruction.OP_GETSTATIC && - currentInstruction.opcode != Instruction.OP_INVOKESTATIC && - currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && - currentInstruction.opcode != Instruction.OP_GETFIELD + /*(!isLoad(currentInstruction) || + !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) &&*/ + currentInstruction.opcode != Instruction.OP_ACONST_NULL && + currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + currentInstruction.opcode != Instruction.OP_LDC && + currentInstruction.opcode != Instruction.OP_LDC_W && + currentInstruction.opcode != Instruction.OP_LDC2_W && + currentInstruction.opcode != Instruction.OP_NEW && + currentInstruction.opcode != Instruction.OP_GETSTATIC && + currentInstruction.opcode != Instruction.OP_INVOKESTATIC && + currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && + currentInstruction.opcode != Instruction.OP_GETFIELD ) { System.out.println(currentInstruction.toString(offset)); currentTracedStack = partialEvaluator.getStackBefore(offset); @@ -119,6 +119,8 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt for (int i = 0; i < offsetValue.instructionOffsetCount(); i++) { if (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(i)) root.children.add(traceParameterTree(partialEvaluator, codeAttribute, offsetValue.instructionOffset(i), leafNodes)); + else + leafNodes.add(root.value); } } else { @@ -128,10 +130,13 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt return root; } - public static int findFirstLambdaParameter(String descriptor) { - return findFirstLambdaParameter(descriptor, true); - } - + /** + * Given a descriptor, find the index of the first argument that has a lambda type. + * @param descriptor The descriptor of the method. + * @param isStatic A boolean describing if the method is static or not, if not static we need to shift the arguments + * by one because the first argument would be "this". + * @return The index of the lambda argument. + */ public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); int index = 0; @@ -144,6 +149,10 @@ public static int findFirstLambdaParameter(String descriptor, boolean isStatic) return index + (isStatic ? 0 : 1); } + public static int findFirstLambdaParameter(String descriptor) { + return findFirstLambdaParameter(descriptor, true); + } + /** * Inline a specified method in the locations it is called in the current clazz. * @param clazz The clazz in which we want to inline the targetMethod From f1551072649901a45de515850aba5361a98ab9c3 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 14:29:42 +0200 Subject: [PATCH 23/72] RecursiveInliner should also use the argument index from the lambda usage finder --- .../java/proguard/optimize/inline/BaseLambdaInliner.java | 2 +- .../java/proguard/optimize/inline/RecursiveInliner.java | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index df0212132..26622e9b6 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -124,7 +124,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Don't inline if the lamdba is passed to another method try { - copiedConsumingMethod.accept(consumingClass, new RecursiveInliner()); + copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(calledLambdaIndex)); } catch(CannotInlineException cie) { ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(copiedConsumingMethod); diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index 655d7d7f3..c442650a8 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -40,11 +40,13 @@ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, M private Method copiedConsumingMethod; private boolean isStatic; private final PartialEvaluator partialEvaluator; + private final int lambdaConsumingMethodArgIndex; - public RecursiveInliner() { + public RecursiveInliner(int lambdaConsumingMethodArgIndex) { this.consumingClazz = null; this.copiedConsumingMethod = null; this.partialEvaluator = new PartialEvaluator(); + this.lambdaConsumingMethodArgIndex = lambdaConsumingMethodArgIndex; } @Override @@ -95,8 +97,8 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c * we have to adjust this index accordingly. */ String consumingMethodDescriptor = copiedConsumingMethod.getDescriptor(consumingClazz); - int calledLambdaRealIndex = Util.findFirstLambdaParameter(consumingMethodDescriptor); - int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, isStatic, calledLambdaRealIndex); + // We use true for isStatic here because lambdaConsumingMethodArgIndex already keeps in mind if the method was static or not + int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, true, lambdaConsumingMethodArgIndex); if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); From 7d82d2d392e84f495999c24787304b20b5751414 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 14:31:47 +0200 Subject: [PATCH 24/72] Remove findFirstLambdaParameter method since it is no longer needed --- .../java/proguard/optimize/inline/Util.java | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/Util.java b/base/src/main/java/proguard/optimize/inline/Util.java index 619f03064..23a1ad9b7 100644 --- a/base/src/main/java/proguard/optimize/inline/Util.java +++ b/base/src/main/java/proguard/optimize/inline/Util.java @@ -7,7 +7,6 @@ import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.VariableInstruction; -import proguard.classfile.util.InternalTypeEnumeration; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; import proguard.evaluation.value.InstructionOffsetValue; @@ -130,29 +129,6 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt return root; } - /** - * Given a descriptor, find the index of the first argument that has a lambda type. - * @param descriptor The descriptor of the method. - * @param isStatic A boolean describing if the method is static or not, if not static we need to shift the arguments - * by one because the first argument would be "this". - * @return The index of the lambda argument. - */ - public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { - InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); - int index = 0; - while (internalTypeEnumeration.hasMoreTypes()) { - if (internalTypeEnumeration.nextType().startsWith("Lkotlin/jvm/functions/Function")) { - break; - } - index ++; - } - return index + (isStatic ? 0 : 1); - } - - public static int findFirstLambdaParameter(String descriptor) { - return findFirstLambdaParameter(descriptor, true); - } - /** * Inline a specified method in the locations it is called in the current clazz. * @param clazz The clazz in which we want to inline the targetMethod From 047719caa11eed24dd41eec776905e6e250b8289 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 14:32:17 +0200 Subject: [PATCH 25/72] WIP commit (please amend) --- base/src/main/java/proguard/ClassPath.java | 8 ++--- .../optimize/inline/BaseLambdaInliner.java | 29 ++++++++++++++++++- .../optimize/inline/LambdaInliner.java | 2 ++ .../proguard/lambdaInline/AdvancedTest.kt | 19 +++++++++++- 4 files changed, 52 insertions(+), 6 deletions(-) diff --git a/base/src/main/java/proguard/ClassPath.java b/base/src/main/java/proguard/ClassPath.java index db3a33c7b..0f3e84592 100644 --- a/base/src/main/java/proguard/ClassPath.java +++ b/base/src/main/java/proguard/ClassPath.java @@ -30,7 +30,7 @@ */ public class ClassPath { - private final List classPathEntries = new ArrayList(); + private final List classPathEntries = new ArrayList(); /** @@ -40,7 +40,7 @@ public boolean hasOutput() { for (int index = 0; index < classPathEntries.size(); index++) { - if (((ClassPathEntry)classPathEntries.get(index)).isOutput()) + if (classPathEntries.get(index).isOutput()) { return true; } @@ -74,12 +74,12 @@ public boolean addAll(ClassPath classPath) public ClassPathEntry get(int index) { - return (ClassPathEntry)classPathEntries.get(index); + return classPathEntries.get(index); } public ClassPathEntry remove(int index) { - return (ClassPathEntry)classPathEntries.remove(index); + return classPathEntries.remove(index); } public boolean isEmpty() diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 26622e9b6..381c54653 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -291,6 +291,7 @@ private PrePostCastRemover(String invokeMethodDescriptor) { @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { for (int invokeMethodCallOffset : invokeMethodCallOffsets) { + System.out.println("Removing casts from " + invokeMethodCallOffset); int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); int endOffset = invokeMethodCallOffset + 8 + 1; // If the next instruction is pop then we are not using the return value so we don't need to remove @@ -299,11 +300,37 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt endOffset = invokeMethodCallOffset; } + /*LimitedInstructionCollector collector = new LimitedInstructionCollector(invokeMethodCallOffset, nbrArgs * 2 + 1); + codeAttribute.accept(clazz, method, collector); + + System.out.println(collector.getLastNInstructions()); + + System.out.println("startOffset " + startOffset); + startOffset = collector.getLastNInstructions().get(0).offset();*/ codeAttribute.instructionsAccept(consumingClass, method, startOffset, endOffset, - new CastRemover(codeAttributeEditor, keepList) + new CastRemover(codeAttributeEditor, keepList) ); + + /*int offset = startOffset; + try { + while (offset < endOffset) + { + // Note that the instruction is only volatile. + Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); + int instructionLength = instruction.length(offset); + instruction.accept(clazz, method, codeAttribute, offset, new CastRemover(codeAttributeEditor, keepList)); + offset += instructionLength; + } + } catch(IllegalArgumentException iae) { + codeAttribute.accept(clazz, method, new ClassPrinter()); + System.out.println(offset); + throw iae; + }*/ } + codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); + System.out.println("After removing casts"); + codeAttribute.accept(clazz, method, new ClassPrinter()); } @Override diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 2c0d461e4..23a1f782b 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -20,6 +20,7 @@ import java.util.Set; public class LambdaInliner implements Pass { + private int count = 0; private final String classNameFilter; private boolean inlinedAllUsages; public LambdaInliner(String classNameFilter) { @@ -118,6 +119,7 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); + System.out.println("Inlined a lambda " + ++count); return true; } } diff --git a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt index 46976dd55..940599022 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/AdvancedTest.kt @@ -405,4 +405,21 @@ class AdvancedTest: FreeSpec ({ compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) } -}) \ No newline at end of file + + "Inline in a method that doesn't use the lambda type in it's descriptor" { + val code = KotlinSource( + "Main.kt", + """ + fun test(f: Any) { + println((f as (Int) -> Int).invoke(8)) + } + + fun main() { + test { it: Int -> it * 3 } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) + } +}) From 54233a5716f056081422a693772da4876d795750 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 14:58:18 +0200 Subject: [PATCH 26/72] Put stop condition that was duplicated into a method --- .../optimize/inline/SourceTracer.java | 54 +++++++++---------- 1 file changed, 24 insertions(+), 30 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/SourceTracer.java b/base/src/main/java/proguard/optimize/inline/SourceTracer.java index 0f512d94a..e52c9fa9d 100644 --- a/base/src/main/java/proguard/optimize/inline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/inline/SourceTracer.java @@ -46,20 +46,8 @@ public static List traceParameterOffset(PartialEvaluator pa // We stop when we found the source instruction while ( (!isLoad(currentInstruction) || - !partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && - currentInstruction.opcode != Instruction.OP_ACONST_NULL && - currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_DCONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_FCONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_LCONST_0 && - currentInstruction.opcode != Instruction.OP_LDC && - currentInstruction.opcode != Instruction.OP_LDC_W && - currentInstruction.opcode != Instruction.OP_LDC2_W && - currentInstruction.opcode != Instruction.OP_NEW && - currentInstruction.opcode != Instruction.OP_GETSTATIC && - currentInstruction.opcode != Instruction.OP_INVOKESTATIC && - currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && - currentInstruction.opcode != Instruction.OP_GETFIELD + !partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && + isSourceInstruction(currentInstruction) ) { logger.debug(currentInstruction.toString(currentOffset)); currentTracedStack = partialEvaluator.getStackBefore(currentOffset); @@ -92,22 +80,7 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt Node root = new Node(new InstructionAtOffset(currentInstruction, offset), new ArrayList<>()); // We stop when we found the source instruction - if ( - /*(!isLoad(currentInstruction) || - !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) &&*/ - currentInstruction.opcode != Instruction.OP_ACONST_NULL && - currentInstruction.canonicalOpcode() != Instruction.OP_ICONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_DCONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_FCONST_0 && - currentInstruction.canonicalOpcode() != Instruction.OP_LCONST_0 && - currentInstruction.opcode != Instruction.OP_LDC && - currentInstruction.opcode != Instruction.OP_LDC_W && - currentInstruction.opcode != Instruction.OP_LDC2_W && - currentInstruction.opcode != Instruction.OP_NEW && - currentInstruction.opcode != Instruction.OP_GETSTATIC && - currentInstruction.opcode != Instruction.OP_INVOKESTATIC && - currentInstruction.opcode != Instruction.OP_INVOKEVIRTUAL && - currentInstruction.opcode != Instruction.OP_GETFIELD) { + if (isSourceInstruction(currentInstruction)) { logger.debug(currentInstruction.toString(offset)); currentTracedStack = partialEvaluator.getStackBefore(offset); logger.debug(currentTracedStack); @@ -133,4 +106,25 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt } return root; } + + /** + * Checks if an instruction is considered a source instruction, this is an instruction that produces a value by + * itself, without having to look further up the chain of instructions. We use this as a stopping condition when we + * are tracing the source of an instruction. + */ + private static boolean isSourceInstruction(Instruction instruction) { + return instruction.opcode != Instruction.OP_ACONST_NULL && + instruction.canonicalOpcode() != Instruction.OP_ICONST_0 && + instruction.canonicalOpcode() != Instruction.OP_DCONST_0 && + instruction.canonicalOpcode() != Instruction.OP_FCONST_0 && + instruction.canonicalOpcode() != Instruction.OP_LCONST_0 && + instruction.opcode != Instruction.OP_LDC && + instruction.opcode != Instruction.OP_LDC_W && + instruction.opcode != Instruction.OP_LDC2_W && + instruction.opcode != Instruction.OP_NEW && + instruction.opcode != Instruction.OP_GETSTATIC && + instruction.opcode != Instruction.OP_INVOKESTATIC && + instruction.opcode != Instruction.OP_INVOKEVIRTUAL && + instruction.opcode != Instruction.OP_GETFIELD; + } } From ef37241adec64e2ccbee77419ea1dd4dbc39e152 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 15:11:06 +0200 Subject: [PATCH 27/72] Don't hardcode the MethodInliner max resulting code size --- .../optimize/inline/BaseLambdaInliner.java | 2 +- .../optimize/peephole/MethodInliner.java | 29 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 18326e905..de19553aa 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -333,7 +333,7 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt * @param targetMethod The method we want to inline. */ private void inlineMethodInClass(Clazz clazz, Method targetMethod) { - clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, 20000, true, false, null) { + clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, true, false, null) { @Override protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { return method.equals(targetMethod); diff --git a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java index 7ca66d5e5..e807b0dd0 100644 --- a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java +++ b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java @@ -161,6 +161,35 @@ public MethodInliner(boolean microEdition, 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 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, + boolean allowAccessModification, + boolean usesOptimizationInfo, + InstructionVisitor extraInlinedInvocationVisitor) + { + this.microEdition = microEdition; + this.android = android; + this.maxResultingCodeLength = defaultMaxResultingCodeLength(microEdition); + this.allowAccessModification = allowAccessModification; + this.usesOptimizationInfo = usesOptimizationInfo; + this.extraInlinedInvocationVisitor = extraInlinedInvocationVisitor; + } + /** * Creates a new MethodInliner. From 343df22d19c160356fd502a4321b45c9ffe15437 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 17:34:03 +0200 Subject: [PATCH 28/72] Fixed cast removal so it works in all cases --- .../optimize/inline/BaseLambdaInliner.java | 39 +++---------- .../proguard/optimize/inline/CastRemover.java | 13 ++--- .../lambdaInline/correctness/AdvancedTest.kt | 55 +++++++++++++++++-- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 2b4eaf19a..1d631f7bb 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -32,6 +32,7 @@ import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InternalTypeEnumeration; +import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; @@ -41,8 +42,6 @@ import java.util.ArrayList; import java.util.List; -import static java.lang.Math.max; - public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; private final Method consumingMethod; @@ -108,6 +107,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Copy the invoke method String invokeMethodDescriptor = method.getDescriptor(consumingClass); + System.out.println("Method descriptor " + invokeMethodDescriptor); DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); staticInvokeMethod = descriptorModifier.modify(method, originalDescriptor -> { @@ -290,8 +290,6 @@ private PrePostCastRemover(String invokeMethodDescriptor) { @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { for (int invokeMethodCallOffset : invokeMethodCallOffsets) { - System.out.println("Removing casts from " + invokeMethodCallOffset); - int startOffset = max((invokeMethodCallOffset -(6 * nbrArgs)), 0); int endOffset = invokeMethodCallOffset + 8 + 1; // If the next instruction is pop then we are not using the return value so we don't need to remove // casts on the return value. @@ -299,37 +297,16 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt endOffset = invokeMethodCallOffset; } - /*LimitedInstructionCollector collector = new LimitedInstructionCollector(invokeMethodCallOffset, nbrArgs * 2 + 1); - codeAttribute.accept(clazz, method, collector); - - System.out.println(collector.getLastNInstructions()); + for (int i = 0; i < nbrArgs; i++) { + int offset = partialEvaluator.getStackBefore(invokeMethodCallOffset).getTopActualProducerValue(nbrArgs - i - 1).instructionOffsetValue().instructionOffset(0); + codeAttribute.instructionAccept(clazz, method, offset, new CastRemover(codeAttributeEditor, keepList, i)); + } - System.out.println("startOffset " + startOffset); - startOffset = collector.getLastNInstructions().get(0).offset();*/ - codeAttribute.instructionsAccept(consumingClass, method, startOffset, endOffset, - new CastRemover(codeAttributeEditor, keepList) + codeAttribute.instructionsAccept(consumingClass, method, invokeMethodCallOffset, endOffset, + new CastRemover(codeAttributeEditor) ); - - /*int offset = startOffset; - try { - while (offset < endOffset) - { - // Note that the instruction is only volatile. - Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); - int instructionLength = instruction.length(offset); - instruction.accept(clazz, method, codeAttribute, offset, new CastRemover(codeAttributeEditor, keepList)); - offset += instructionLength; - } - } catch(IllegalArgumentException iae) { - codeAttribute.accept(clazz, method, new ClassPrinter()); - System.out.println(offset); - throw iae; - }*/ } - codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); - System.out.println("After removing casts"); - codeAttribute.accept(clazz, method, new ClassPrinter()); } @Override diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java index b3fb9465a..191977288 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -20,20 +20,19 @@ public class CastRemover implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; private final List keepList; - private int argIndex; + private final int argIndex; // The names of all method taking a boxed type variable and returning the variable with the unboxed type private final Set castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); - - public CastRemover(CodeAttributeEditor codeAttributeEditor, List keepList) { + public CastRemover(CodeAttributeEditor codeAttributeEditor, List keepList, int argIndex) { this.codeAttributeEditor = codeAttributeEditor; - this.argIndex = 0; + this.argIndex = argIndex; this.keepList = keepList; } public CastRemover(CodeAttributeEditor codeAttributeEditor) { - this(codeAttributeEditor, new ArrayList<>()); + this(codeAttributeEditor, new ArrayList<>(), -1); } @Override @@ -56,7 +55,6 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c if (!keepList.contains(argIndex)) { codeAttributeEditor.deleteInstruction(offset); } - argIndex++; } } else if (constantInstruction.opcode == Instruction.OP_INVOKEVIRTUAL) { if (castingMethodNames.contains(getInvokedMethodName(clazz, constantInstruction))) { @@ -66,8 +64,7 @@ public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute c } @Override - public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) { - } + public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} private String getInvokedMethodName(Clazz clazz, ConstantInstruction constantInstruction) { final String[] invokedMethodName = new String[1]; diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt index 6af53e533..aa5e6dfa0 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt @@ -406,16 +406,63 @@ class AdvancedTest: FreeSpec ({ compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) } - "Inline in a method that doesn't use the lambda type in it's descriptor" { + "Test the cast remover on a harder case" { val code = KotlinSource( "Main.kt", """ - fun test(f: Any) { - println((f as (Int) -> Int).invoke(8)) + fun test(f: (Any) -> Any) { + // The following sequence of code was created with the idea of breaking the inliner if it is guessing + // instruction offsets, if offsets are guessed it might potentially see -20 as the opcode which is + // invalid. + val x = -20 shl 8 + println(f("Hello world!")) } fun main() { - test { it: Int -> it * 3 } + test { it } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) + } + + "Test the cast remover on an even harder case" { + val code = KotlinSource( + "Main.kt", + """ + fun test(f: (String, String, String, Int) -> String) { + val x: Any = 5 + println(f("Test", "Hello world!", "Something else", 3)) + other(x) + } + + fun other(x: Any) {} + + fun main() { + test { a, _, _, _ -> a } + } + """ + ) + + compareOutputAndMainInstructions(code, listOf("invokestatic", "return"), true) + } + + "Test the cast remover on an even harder case with a calculation directly in the arguments" { + val code = KotlinSource( + "Main.kt", + """ + fun test(f: (String, String, String, Int, Int) -> String) { + val y = 7 + val x: Any = 5 + println(f("Test", "Hello world!", "Something else", 7, 3 + y + 5 + 7 + 26)) + other(x) + } + + fun other(x: Any) {} + + fun main() { + test { a, _, _, _, _ -> a } } """ ) From 71213fe388180feccb3d84ba7f2990a3dcea84d1 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 17:41:25 +0200 Subject: [PATCH 29/72] Reverted some minor accidental changes --- base/src/main/java/proguard/ClassPath.java | 8 ++++---- .../java/proguard/optimize/inline/BaseLambdaInliner.java | 1 - .../main/java/proguard/optimize/inline/LambdaInliner.java | 2 -- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/ClassPath.java b/base/src/main/java/proguard/ClassPath.java index 0f3e84592..db3a33c7b 100644 --- a/base/src/main/java/proguard/ClassPath.java +++ b/base/src/main/java/proguard/ClassPath.java @@ -30,7 +30,7 @@ */ public class ClassPath { - private final List classPathEntries = new ArrayList(); + private final List classPathEntries = new ArrayList(); /** @@ -40,7 +40,7 @@ public boolean hasOutput() { for (int index = 0; index < classPathEntries.size(); index++) { - if (classPathEntries.get(index).isOutput()) + if (((ClassPathEntry)classPathEntries.get(index)).isOutput()) { return true; } @@ -74,12 +74,12 @@ public boolean addAll(ClassPath classPath) public ClassPathEntry get(int index) { - return classPathEntries.get(index); + return (ClassPathEntry)classPathEntries.get(index); } public ClassPathEntry remove(int index) { - return classPathEntries.remove(index); + return (ClassPathEntry)classPathEntries.remove(index); } public boolean isEmpty() diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 1d631f7bb..945814da4 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -107,7 +107,6 @@ public void visitAnyMember(Clazz clazz, Member member) { // Copy the invoke method String invokeMethodDescriptor = method.getDescriptor(consumingClass); - System.out.println("Method descriptor " + invokeMethodDescriptor); DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); staticInvokeMethod = descriptorModifier.modify(method, originalDescriptor -> { diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 23a1f782b..2c0d461e4 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -20,7 +20,6 @@ import java.util.Set; public class LambdaInliner implements Pass { - private int count = 0; private final String classNameFilter; private boolean inlinedAllUsages; public LambdaInliner(String classNameFilter) { @@ -119,7 +118,6 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); - System.out.println("Inlined a lambda " + ++count); return true; } } From 42493c63ee9d55345b027055cc8b7a8e056781a9 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Tue, 8 Aug 2023 17:54:25 +0200 Subject: [PATCH 30/72] Cleaned up a bit --- .../optimize/inline/BaseLambdaInliner.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 945814da4..cac31f5c0 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -289,21 +289,22 @@ private PrePostCastRemover(String invokeMethodDescriptor) { @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { for (int invokeMethodCallOffset : invokeMethodCallOffsets) { - int endOffset = invokeMethodCallOffset + 8 + 1; // If the next instruction is pop then we are not using the return value so we don't need to remove // casts on the return value. - if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode == Instruction.OP_POP) { - endOffset = invokeMethodCallOffset; + if (InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset + InstructionFactory.create(codeAttribute.code, invokeMethodCallOffset).length(invokeMethodCallOffset)).opcode != Instruction.OP_POP) { + int endOffset = invokeMethodCallOffset + 8 + 1; + codeAttribute.instructionsAccept(consumingClass, method, invokeMethodCallOffset, endOffset, + new CastRemover(codeAttributeEditor) + ); } + // Remove casts on the arguments, because arguments can have things like calculations in them, we use + // the partial evaluator, but because we already ran it on this code attribute in a previous operation + // it doesn't really cost us much at all. for (int i = 0; i < nbrArgs; i++) { int offset = partialEvaluator.getStackBefore(invokeMethodCallOffset).getTopActualProducerValue(nbrArgs - i - 1).instructionOffsetValue().instructionOffset(0); codeAttribute.instructionAccept(clazz, method, offset, new CastRemover(codeAttributeEditor, keepList, i)); } - - codeAttribute.instructionsAccept(consumingClass, method, invokeMethodCallOffset, endOffset, - new CastRemover(codeAttributeEditor) - ); } codeAttributeEditor.visitCodeAttribute(clazz, method, codeAttribute); } From cff65658e5f9f0fd18d9babc56bdc740a1a4c420 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 10:34:16 +0200 Subject: [PATCH 31/72] Conditionally inline lambdas --- .../optimize/inline/BaseLambdaInliner.java | 29 ++++----------- .../optimize/inline/LambdaInliner.java | 2 +- .../optimize/inline/MethodLengthFinder.java | 34 +++++++++++++++++ .../optimize/inline/ShortLambdaInliner.java | 37 +++++++++++++++++++ 4 files changed, 80 insertions(+), 22 deletions(-) create mode 100644 base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java create mode 100644 base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index cac31f5c0..47418ba96 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -32,7 +32,6 @@ import proguard.classfile.util.ClassReferenceInitializer; import proguard.classfile.util.ClassUtil; import proguard.classfile.util.InternalTypeEnumeration; -import proguard.classfile.visitor.ClassPrinter; import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; @@ -42,7 +41,7 @@ import java.util.ArrayList; import java.util.List; -public class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { +public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; private final Method consumingMethod; private final AppView appView; @@ -91,6 +90,10 @@ public Method inline() { this.lambdaInvokeMethod = programMethod; this.bridgeDescriptor = bridgeDescriptor; + if (!shouldInline(consumingClass, consumingMethod, programClass, lambdaInvokeMethod)) { + return; + } + // First we copy the method from the anonymous lambda class into the class where it is used. MethodCopier copier = new MethodCopier(programClass, programMethod, BaseLambdaInliner.this); consumingClass.accept(copier); @@ -99,6 +102,8 @@ public Method inline() { return inlinedLambdaMethod; } + protected abstract boolean shouldInline(Clazz consumingClass, Method consumingMethod, Clazz lambdaClass, Method lambdaImplMethod); + @Override public void visitAnyMember(Clazz clazz, Member member) { ProgramMethod method = (ProgramMethod) member; @@ -135,7 +140,7 @@ public void visitAnyMember(Clazz clazz, Member member) { invokeMethodCallOffsets.clear(); // Replace invokeinterface call to invoke method with invokestatic to staticInvokeMethod - codeAttributeEditor.reset(getMethodLength(copiedConsumingMethod, consumingClass)); + codeAttributeEditor.reset(MethodLengthFinder.getMethodCodeLength(consumingClass, copiedConsumingMethod)); copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor( new AllInstructionVisitor( @@ -313,24 +318,6 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} } - /** - * @param method A Method object from which we'll get the length. - * @param clazz The class in which the method is. - * @return The length of the method. - */ - private int getMethodLength(Method method, Clazz clazz) { - final int[] length = new int[1]; - method.accept(clazz, new AllAttributeVisitor(new AttributeVisitor() { - @Override - public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - length[0] = codeAttribute.u4codeLength; - } - })); - return length[0]; - } - /** * Inline a specified method in the locations it is called in the current clazz. * @param clazz The clazz in which we want to inline the targetMethod diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 2c0d461e4..3465c44a2 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -74,7 +74,7 @@ public LambdaUsageHandler(AppView appView, Set remainder) { boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int lambdaArgumentIndex, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { // Try inlining the lambda in consumingMethod - BaseLambdaInliner baseLambdaInliner = new BaseLambdaInliner(appView, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); + BaseLambdaInliner baseLambdaInliner = new ShortLambdaInliner(appView, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); Method inlinedLambamethod = baseLambdaInliner.inline(); // We didn't inline anything so no need to change any call instructions. diff --git a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java new file mode 100644 index 000000000..a1d81f538 --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java @@ -0,0 +1,34 @@ +package proguard.optimize.inline; + +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.classfile.attribute.Attribute; +import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AllAttributeVisitor; +import proguard.classfile.attribute.visitor.AttributeVisitor; + +/** + * A simple utility class that gets the length of a method, if the method has no code attribute it will return -1 as the + * length. + */ +public class MethodLengthFinder { + private static int codeLength; + + /** + * @param method A Method object from which we'll get the length. + * @param clazz The class in which the method is. + * @return The length of the method. + */ + public static int getMethodCodeLength(Clazz clazz, Method method) { + codeLength = -1; // If a method has no codeAttribute we don't want to return the previous method length value. + method.accept(clazz, new AllAttributeVisitor(new AttributeVisitor() { + @Override + public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + codeLength = codeAttribute.u4codeLength; + } + })); + return codeLength; + } +} diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java new file mode 100644 index 000000000..b228d938d --- /dev/null +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -0,0 +1,37 @@ +package proguard.optimize.inline; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import proguard.AppView; +import proguard.classfile.Clazz; +import proguard.classfile.Method; +import proguard.optimize.inline.lambda_locator.Lambda; + +/** + * This class is an implementation of the {@link proguard.optimize.inline.BaseLambdaInliner BaseLambdaInliner } that + * inlines lambdas depending on the length of the lambda implementation method and the length of the consuming method. + * The length of the consuming method is taken into account because the consuming method will be copied when inlining + * this lambda because one method can take multiple different lambdas as an input. + */ +public class ShortLambdaInliner extends BaseLambdaInliner { + private final Logger logger = LogManager.getLogger(this.getClass()); + private static final int MAXIMUM_CONSUMING_METHOD_LENGTH = 2000; + private static final int MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH = 64; + + public ShortLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { + super(appView, consumingClass, consumingMethod, calledLambdaIndex, lambda); + } + + @Override + protected boolean shouldInline(Clazz consumingClass, Method consumingMethod, Clazz lambdaClass, Method lambdaImplMethod) { + int consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, consumingMethod); + int lambdaImplMethodLength = MethodLengthFinder.getMethodCodeLength(lambdaClass, lambdaImplMethod); + + boolean inline = lambdaImplMethodLength < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength < MAXIMUM_CONSUMING_METHOD_LENGTH; + logger.info("Consuming method length = " + consumingMethodLength + ", lambda implementation method length = " + lambdaImplMethodLength); + if (!inline) { + logger.info("Will not attempt inlining because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); + } + return inline; + } +} From 5644a9dd2ac74db6bed4a95c7fdbf8756cc6dde7 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 10:56:09 +0200 Subject: [PATCH 32/72] Added logging when a lambda has been inlined and only print method length when decided to not inline the lambda --- .../main/java/proguard/optimize/inline/LambdaInliner.java | 4 ++++ .../java/proguard/optimize/inline/ShortLambdaInliner.java | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 3465c44a2..69b263324 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -1,5 +1,7 @@ package proguard.optimize.inline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.classfile.Clazz; import proguard.classfile.attribute.CodeAttribute; import proguard.optimize.inline.lambda_locator.LambdaLocator; @@ -20,6 +22,7 @@ import java.util.Set; public class LambdaInliner implements Pass { + private final Logger logger = LogManager.getLogger(); private final String classNameFilter; private boolean inlinedAllUsages; public LambdaInliner(String classNameFilter) { @@ -118,6 +121,7 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); + logger.info("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); return true; } } diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java index b228d938d..1bd6cb06d 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -14,7 +14,7 @@ * this lambda because one method can take multiple different lambdas as an input. */ public class ShortLambdaInliner extends BaseLambdaInliner { - private final Logger logger = LogManager.getLogger(this.getClass()); + private final Logger logger = LogManager.getLogger(); private static final int MAXIMUM_CONSUMING_METHOD_LENGTH = 2000; private static final int MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH = 64; @@ -28,9 +28,9 @@ protected boolean shouldInline(Clazz consumingClass, Method consumingMethod, Cla int lambdaImplMethodLength = MethodLengthFinder.getMethodCodeLength(lambdaClass, lambdaImplMethod); boolean inline = lambdaImplMethodLength < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength < MAXIMUM_CONSUMING_METHOD_LENGTH; - logger.info("Consuming method length = " + consumingMethodLength + ", lambda implementation method length = " + lambdaImplMethodLength); if (!inline) { - logger.info("Will not attempt inlining because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); + logger.info("Will not attempt inlining lambda because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); + logger.info("Consuming method length = {}, lambda implementation method length = {}", consumingMethodLength, lambdaImplMethodLength); } return inline; } From 48c52e5c134cc89972f94503c7094bc565361006 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 11:10:44 +0200 Subject: [PATCH 33/72] Log the consuming method and invoke method so the use knows which method is not inlined when it is too long --- .../main/java/proguard/optimize/inline/ShortLambdaInliner.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java index 1bd6cb06d..c4e9a4301 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -30,6 +30,8 @@ protected boolean shouldInline(Clazz consumingClass, Method consumingMethod, Cla boolean inline = lambdaImplMethodLength < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength < MAXIMUM_CONSUMING_METHOD_LENGTH; if (!inline) { logger.info("Will not attempt inlining lambda because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); + logger.info("Consuming method = {}#{}{}", consumingClass.getName(), consumingMethod.getName(consumingClass), consumingMethod.getDescriptor(consumingClass)); + logger.info("Lambda implementation method = {}#{}{}", lambdaClass.getName(), lambdaImplMethod.getName(lambdaClass), lambdaImplMethod.getDescriptor(lambdaClass)); logger.info("Consuming method length = {}, lambda implementation method length = {}", consumingMethodLength, lambdaImplMethodLength); } return inline; From 61e1e9812ea271832c3f1fe8f9c9b85228ea190f Mon Sep 17 00:00:00 2001 From: Maarten Steevens Date: Wed, 9 Aug 2023 11:16:23 +0200 Subject: [PATCH 34/72] Fixed typo in comment Co-authored-by: rubenpieters --- .../main/java/proguard/optimize/inline/RecursiveInliner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java index fa7b72c95..0f564f39e 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java @@ -32,7 +32,7 @@ * Recursively inline functions that make use of the lambda parameter in the arguments of the current function. * The first step, is finding out who actually uses our lambda parameter, we do this using the partial evaluator. *

- * If we s ee a lambda is used by a method being called from within the consuming method we will currently abort the + * If we see a lambda is used by a method being called from within the consuming method we will currently abort the * inlining process. */ public class RecursiveInliner implements AttributeVisitor, InstructionVisitor, MemberVisitor, ConstantVisitor { From 00664b266593725c5223df4232a060ad4ed67ed1 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 11:31:18 +0200 Subject: [PATCH 35/72] Removed some unnecessary comments from the tests --- .../lambdaInline/correctness/AdvancedTest.kt | 17 +++--- .../correctness/FieldInliningTest.kt | 6 +-- .../correctness/OneLambdaInArgsTest.kt | 53 ++++++++----------- .../correctness/ThreeLambdasInArgsTest.kt | 12 ++--- .../correctness/TwoLambdasInArgsTest.kt | 20 +++---- .../lambdaInline/correctness/TypeTest.kt | 38 ++++++------- .../performance/PerformanceTest.kt | 12 ++--- 7 files changed, 67 insertions(+), 91 deletions(-) diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt index aa5e6dfa0..42b4ad9d4 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt @@ -6,8 +6,7 @@ import proguard.testutils.KotlinSource class AdvancedTest: FreeSpec ({ "three lambda functions with two of same size and one different" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -64,7 +63,7 @@ class AdvancedTest: FreeSpec ({ } "switch case, unit lambda, multiple call to the same lambda" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Unit) { @@ -89,7 +88,7 @@ class AdvancedTest: FreeSpec ({ } "Casting basic types after the lambda call" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -110,7 +109,7 @@ class AdvancedTest: FreeSpec ({ } "Casting types before the lambda call" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -144,7 +143,7 @@ class AdvancedTest: FreeSpec ({ } "Casting types after the lambda call" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -178,7 +177,7 @@ class AdvancedTest: FreeSpec ({ } "Casting types between lambda calls" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -214,7 +213,7 @@ class AdvancedTest: FreeSpec ({ } "Casting types inside lambda call" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Any) -> Int) { @@ -235,7 +234,7 @@ class AdvancedTest: FreeSpec ({ } "Casting types inside lambda call + other arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Any, Int) -> Int) { diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt index 9ad8808fd..ebf78cb28 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt @@ -6,8 +6,7 @@ import proguard.testutils.KotlinSource class FieldInliningTest: FreeSpec ({ "call lambda from field in method" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ class MainKtWithAField { @@ -29,8 +28,7 @@ class FieldInliningTest: FreeSpec ({ } "call lambda from a non-final field in method" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ class MainKtWithAField { diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt index 7f504d64b..c55686977 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt @@ -7,8 +7,7 @@ import proguard.testutils.KotlinSource class OneLambdaInArgsTest: FreeSpec({ "One lambda in args, basic case with int" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -25,8 +24,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in args, basic case with boolean" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Boolean) -> Boolean) { @@ -43,8 +41,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in args, basic case with char" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Char) -> Char) { @@ -61,8 +58,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in args and instruction before lambda function calls in main" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -84,8 +80,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in args with if" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Boolean) { @@ -102,8 +97,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but in a class companion object" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -127,8 +121,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but in a different class" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -153,8 +146,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in a class + other arguments" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun main() { @@ -183,8 +175,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in a class + other arguments + nested + private" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun main() { @@ -215,8 +206,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in a class with a call to a function in another class" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun main() { @@ -244,8 +234,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda in a class with a call to a function in another class that calls a function in yet another class" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun main() { @@ -284,7 +273,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but one other argument after lambda arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -305,7 +294,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but one other argument before lambda arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -326,7 +315,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but one other argument before and after lambda arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -347,7 +336,7 @@ class OneLambdaInArgsTest: FreeSpec({ compareOutputAndMainInstructions(code, listOf("iconst_1", "bipush", "invokestatic", "return")) } "One lambda but multiple other arguments before and after lambda arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -374,7 +363,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but the lambda is consumed by a function called from the consuming method" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(f: (Int) -> Int) { @@ -395,7 +384,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but the lambda is consumed by a function called from the consuming method LONG" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(b: Long, f: (Int) -> Int, a: Long) { @@ -417,7 +406,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but the lambda is consumed by multiple other methods from the original consuming method" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(f: (Int) -> Int) { @@ -443,7 +432,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but the lambda at even more levels" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(f: (Int) -> Int) { @@ -473,7 +462,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "One lambda but the lambda at even more levels + private" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(f: (Int) -> Int) { @@ -674,7 +663,7 @@ class OneLambdaInArgsTest: FreeSpec({ } "Not using lambda return value" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt index 28907895b..0a22698fd 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt @@ -6,8 +6,7 @@ import proguard.testutils.KotlinSource class ThreeLambdasInArgsTest: FreeSpec({ "Three lambda in args, basic case" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int, Int) -> Int, c: (String, Boolean, Int) -> Boolean) { @@ -25,8 +24,7 @@ class ThreeLambdasInArgsTest: FreeSpec({ } "Three lambdas in args and instruction before lambda function calls in main" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int, Int) -> Int, c: (String, Boolean, Int) -> Boolean) { @@ -64,8 +62,7 @@ class ThreeLambdasInArgsTest: FreeSpec({ ) } "Three lambdas in args and two are the same types" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int) -> Int, c: (String, Boolean, Int) -> Boolean) { @@ -84,8 +81,7 @@ class ThreeLambdasInArgsTest: FreeSpec({ } "Three lambdas in args and two are the same types shuffled" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (String, Boolean, Int) -> Boolean, c: (Int) -> Int) { diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt index 0d9296fde..c41dbcbb2 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt @@ -7,8 +7,7 @@ import proguard.testutils.KotlinSource class TwoLambdasInArgsTest: FreeSpec({ "Two lambda in args, basic case" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int, Int) -> Int) { @@ -26,8 +25,7 @@ class TwoLambdasInArgsTest: FreeSpec({ } "Two lambdas in args and instruction before lambda function calls in main" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int, Int) -> Int) { @@ -50,8 +48,7 @@ class TwoLambdasInArgsTest: FreeSpec({ } "Twice the same lambda types in args" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int) -> Int) { @@ -70,8 +67,7 @@ class TwoLambdasInArgsTest: FreeSpec({ } "Twice the same lambda types in args + one lambda with one arg" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int, b: (Int) -> Int) { @@ -96,7 +92,7 @@ class TwoLambdasInArgsTest: FreeSpec({ } "Two lambdas but multiple other arguments before and after lambda arg" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ @@ -124,8 +120,7 @@ class TwoLambdasInArgsTest: FreeSpec({ "Twice the same lambda types in args multiple levels" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(a: (Int) -> Int, b: (Int) -> Int) { @@ -147,8 +142,7 @@ class TwoLambdasInArgsTest: FreeSpec({ } "Twice the same lambda types in args multiple levels with one non lambda before the lambdas" { - //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun level1(i1: Int, a: (Int) -> Int, b: (Int) -> Int) { diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt b/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt index 1502ba45b..fde37fc4c 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt @@ -6,7 +6,7 @@ import proguard.testutils.KotlinSource class TypeTest: FreeSpec ({ "Byte" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Byte) -> Byte) { @@ -22,7 +22,7 @@ class TypeTest: FreeSpec ({ } "Short" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Short) ->Short) { @@ -38,7 +38,7 @@ class TypeTest: FreeSpec ({ } "Int" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -55,7 +55,7 @@ class TypeTest: FreeSpec ({ "Long" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Long) -> Long) { @@ -71,7 +71,7 @@ class TypeTest: FreeSpec ({ } "Long in consumingMethod args" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(c:Long, a: (Long) -> Long, b: Long) { @@ -89,7 +89,7 @@ class TypeTest: FreeSpec ({ } "Float" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Float) -> Float) { @@ -105,7 +105,7 @@ class TypeTest: FreeSpec ({ } "Double" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Double) -> Double) { @@ -121,7 +121,7 @@ class TypeTest: FreeSpec ({ } "Boolean" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Boolean) -> Boolean) { @@ -137,7 +137,7 @@ class TypeTest: FreeSpec ({ } "Char" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Char) -> Char) { @@ -153,7 +153,7 @@ class TypeTest: FreeSpec ({ } "String" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (String) -> String) { @@ -169,7 +169,7 @@ class TypeTest: FreeSpec ({ } "ByteArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (ByteArray) -> ByteArray) { @@ -185,7 +185,7 @@ class TypeTest: FreeSpec ({ } "ShortArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (ShortArray) -> ShortArray) { @@ -201,7 +201,7 @@ class TypeTest: FreeSpec ({ } "IntArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (IntArray) -> IntArray) { @@ -217,7 +217,7 @@ class TypeTest: FreeSpec ({ } "LongArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (LongArray) -> LongArray) { @@ -233,7 +233,7 @@ class TypeTest: FreeSpec ({ } "FloatArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (FloatArray) -> FloatArray) { @@ -249,7 +249,7 @@ class TypeTest: FreeSpec ({ } "DoubleArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (DoubleArray) -> DoubleArray) { @@ -265,7 +265,7 @@ class TypeTest: FreeSpec ({ } "CharArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (CharArray) -> CharArray) { @@ -281,7 +281,7 @@ class TypeTest: FreeSpec ({ } "StringArray" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Array) -> Array) { @@ -297,7 +297,7 @@ class TypeTest: FreeSpec ({ } "Any" { - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Any) -> Any) { diff --git a/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt b/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt index 158082b22..7fe951e1c 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt +++ b/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt @@ -8,7 +8,7 @@ class PerformanceTest: FreeSpec ({ "basic case" - { "10" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -46,7 +46,7 @@ class PerformanceTest: FreeSpec ({ } "1000" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -84,7 +84,7 @@ class PerformanceTest: FreeSpec ({ } "100000" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -125,7 +125,7 @@ class PerformanceTest: FreeSpec ({ "hard case" - { "1" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -205,7 +205,7 @@ class PerformanceTest: FreeSpec ({ } "10" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { @@ -284,7 +284,7 @@ class PerformanceTest: FreeSpec ({ } "100" { //setup - val code = KotlinSource( //create java file + val code = KotlinSource( "Main.kt", """ fun test(a: (Int) -> Int) { From fca2cde4bc24e0e93f3135b19d97def8b8aad4ca Mon Sep 17 00:00:00 2001 From: Maarten Steevens Date: Wed, 9 Aug 2023 11:33:53 +0200 Subject: [PATCH 36/72] Fixed typo in comment Co-authored-by: rubenpieters --- base/src/main/java/proguard/optimize/inline/CastRemover.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java index 191977288..3115411a4 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -22,7 +22,7 @@ public class CastRemover implements InstructionVisitor { private final List keepList; private final int argIndex; - // The names of all method taking a boxed type variable and returning the variable with the unboxed type + // The names of all methods taking a boxed type variable and returning the variable with the unboxed type private final Set castingMethodNames = new HashSet<>(Arrays.asList("intValue", "booleanValue", "byteValue", "shortValue", "longValue", "floatValue", "doubleValue", "charValue")); public CastRemover(CodeAttributeEditor codeAttributeEditor, List keepList, int argIndex) { From f8f115584be303e6ed9e69bb9c640a9dc401062b Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 11:35:56 +0200 Subject: [PATCH 37/72] Rename package to lambdainline instead of lambdaInline --- .../proguard/{lambdaInline => lambdainline}/TestUtil.kt | 4 ---- .../correctness/AdvancedTest.kt | 2 +- .../correctness/FieldInliningTest.kt | 2 +- .../correctness/LambdaReturnTest.kt | 2 +- .../correctness/OneLambdaInArgsTest.kt | 2 +- .../correctness/ThreeLambdasInArgsTest.kt | 2 +- .../correctness/TwoLambdasInArgsTest.kt | 2 +- .../{lambdaInline => lambdainline}/correctness/TypeTest.kt | 2 +- .../performance/PerformanceTest.kt | 0 9 files changed, 7 insertions(+), 11 deletions(-) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/TestUtil.kt (99%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/AdvancedTest.kt (99%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/FieldInliningTest.kt (96%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/LambdaReturnTest.kt (93%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/OneLambdaInArgsTest.kt (99%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/ThreeLambdasInArgsTest.kt (98%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/TwoLambdasInArgsTest.kt (98%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/correctness/TypeTest.kt (99%) rename base/src/test/kotlin/proguard/{lambdaInline => lambdainline}/performance/PerformanceTest.kt (100%) diff --git a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt rename to base/src/test/kotlin/proguard/lambdainline/TestUtil.kt index a249b9569..c4d92ba71 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/TestUtil.kt +++ b/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt @@ -1,9 +1,6 @@ import io.kotest.assertions.withClue import io.kotest.matchers.shouldBe import proguard.AppView -import proguard.Configuration -import proguard.ConfigurationParser -import proguard.ProGuard import proguard.classfile.ClassPool import proguard.classfile.Clazz import proguard.classfile.Method @@ -29,7 +26,6 @@ import proguard.optimize.info.ProgramMemberOptimizationInfoSetter import proguard.optimize.inline.LambdaInliner import proguard.optimize.peephole.LineNumberLinearizer import proguard.preverify.CodePreverifier -import proguard.retrace.ReTrace import proguard.testutils.ClassPoolBuilder import proguard.testutils.PartialEvaluatorUtil import proguard.testutils.TestSource diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/AdvancedTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/AdvancedTest.kt index 42b4ad9d4..b5c66b07f 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/AdvancedTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/AdvancedTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/FieldInliningTest.kt similarity index 96% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/FieldInliningTest.kt index ebf78cb28..7472af59f 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/FieldInliningTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/FieldInliningTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import io.kotest.core.spec.style.FreeSpec import onlyTestRunning diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/LambdaReturnTest.kt similarity index 93% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/LambdaReturnTest.kt index 21c5760d5..b26e6b819 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/LambdaReturnTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/LambdaReturnTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import io.kotest.core.spec.style.FreeSpec import onlyTestRunning diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/OneLambdaInArgsTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/OneLambdaInArgsTest.kt index c55686977..53eafb2c6 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/OneLambdaInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/OneLambdaInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/ThreeLambdasInArgsTest.kt similarity index 98% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/ThreeLambdasInArgsTest.kt index 0a22698fd..db7880c3a 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/ThreeLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/ThreeLambdasInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/TwoLambdasInArgsTest.kt similarity index 98% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/TwoLambdasInArgsTest.kt index c41dbcbb2..684244578 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/TwoLambdasInArgsTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/TwoLambdasInArgsTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt b/base/src/test/kotlin/proguard/lambdainline/correctness/TypeTest.kt similarity index 99% rename from base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt rename to base/src/test/kotlin/proguard/lambdainline/correctness/TypeTest.kt index fde37fc4c..3b548e623 100644 --- a/base/src/test/kotlin/proguard/lambdaInline/correctness/TypeTest.kt +++ b/base/src/test/kotlin/proguard/lambdainline/correctness/TypeTest.kt @@ -1,4 +1,4 @@ -package proguard.lambdaInline.correctness +package proguard.lambdainline.correctness import compareOutputAndMainInstructions import io.kotest.core.spec.style.FreeSpec diff --git a/base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt b/base/src/test/kotlin/proguard/lambdainline/performance/PerformanceTest.kt similarity index 100% rename from base/src/test/kotlin/proguard/lambdaInline/performance/PerformanceTest.kt rename to base/src/test/kotlin/proguard/lambdainline/performance/PerformanceTest.kt From 2710ff3d7dfa90dfac5eb625f03b468fb39581f9 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 11:43:22 +0200 Subject: [PATCH 38/72] Rename lambda_locator package to lambdalocator --- .../main/java/proguard/optimize/inline/BaseLambdaInliner.java | 2 +- .../src/main/java/proguard/optimize/inline/LambdaInliner.java | 4 ++-- .../main/java/proguard/optimize/inline/LambdaUsageFinder.java | 2 +- .../java/proguard/optimize/inline/ShortLambdaInliner.java | 2 +- .../inline/{lambda_locator => lambdalocator}/Lambda.java | 2 +- .../{lambda_locator => lambdalocator}/LambdaLocator.java | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) rename base/src/main/java/proguard/optimize/inline/{lambda_locator => lambdalocator}/Lambda.java (97%) rename base/src/main/java/proguard/optimize/inline/{lambda_locator => lambdalocator}/LambdaLocator.java (99%) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 47418ba96..a2f5650e7 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -35,7 +35,7 @@ import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; -import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.optimize.inline.lambdalocator.Lambda; import proguard.optimize.peephole.MethodInliner; import java.util.ArrayList; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 69b263324..4993bd236 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -4,7 +4,7 @@ import org.apache.logging.log4j.Logger; import proguard.classfile.Clazz; import proguard.classfile.attribute.CodeAttribute; -import proguard.optimize.inline.lambda_locator.LambdaLocator; +import proguard.optimize.inline.lambdalocator.LambdaLocator; import proguard.AppView; import proguard.classfile.AccessConstants; import proguard.classfile.Method; @@ -14,7 +14,7 @@ import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.InitializationUtil; -import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.optimize.inline.lambdalocator.Lambda; import proguard.pass.Pass; import java.util.HashSet; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 56068bc84..5e0a3b58a 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -17,7 +17,7 @@ import proguard.classfile.util.ClassUtil; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; -import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.optimize.inline.lambdalocator.Lambda; import java.util.ArrayList; import java.util.List; diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java index c4e9a4301..385f5fcfc 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -5,7 +5,7 @@ import proguard.AppView; import proguard.classfile.Clazz; import proguard.classfile.Method; -import proguard.optimize.inline.lambda_locator.Lambda; +import proguard.optimize.inline.lambdalocator.Lambda; /** * This class is an implementation of the {@link proguard.optimize.inline.BaseLambdaInliner BaseLambdaInliner } that diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java similarity index 97% rename from base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java rename to base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java index fdc1f24d1..2bcb693ce 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/Lambda.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline.lambda_locator; +package proguard.optimize.inline.lambdalocator; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java rename to base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java index fbf67a511..30bcfb939 100644 --- a/base/src/main/java/proguard/optimize/inline/lambda_locator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline.lambda_locator; +package proguard.optimize.inline.lambdalocator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; From 49bd302f91514ac705e27c5714593c5149622ff6 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 11:50:50 +0200 Subject: [PATCH 39/72] Added newline to Lambda class and removed some unneeded methods --- .../optimize/inline/lambdalocator/Lambda.java | 23 ++----------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java index 2bcb693ce..a54d38e43 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java @@ -5,8 +5,6 @@ import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.ConstantInstruction; -import java.util.Objects; - public final class Lambda { private final Clazz clazz; private final Method method; @@ -14,7 +12,7 @@ public final class Lambda { private final int offset; private final ConstantInstruction constantInstruction; - Lambda(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + public Lambda(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { this.clazz = clazz; this.method = method; this.codeAttribute = codeAttribute; @@ -42,25 +40,8 @@ public ConstantInstruction constantInstruction() { return constantInstruction; } - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - Lambda that = (Lambda) obj; - return Objects.equals(this.clazz, that.clazz) && - Objects.equals(this.method, that.method) && - Objects.equals(this.codeAttribute, that.codeAttribute) && - this.offset == that.offset && - Objects.equals(this.constantInstruction, that.constantInstruction); - } - - @Override - public int hashCode() { - return Objects.hash(clazz, method, codeAttribute, offset, constantInstruction); - } - @Override public String toString() { return constantInstruction.toString(); } -} \ No newline at end of file +} From 55b4f0e1ccf1a8f1de3ff7b33886b33f68187871 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 12:16:57 +0200 Subject: [PATCH 40/72] Use result of lambdaUsageHandler.handle() directly instead of storing and then reading it again from the iterativeInstructionVisitor --- .../inline/IterativeInstructionVisitor.java | 2 +- .../optimize/inline/LambdaUsageFinder.java | 39 +++++++++---------- 2 files changed, 19 insertions(+), 22 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java index 420d0b62b..34803c432 100644 --- a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java @@ -33,7 +33,7 @@ public boolean codeHasChanged() { return changed; } - public void setChanged(boolean changed) { + public void setCodeChanged(boolean changed) { this.changed = changed; } } diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 5e0a3b58a..143785ed8 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -117,27 +117,24 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, } if (match) { - iterativeInstructionVisitor.setChanged( - lambdaUsageHandler.handle( - targetLambda, - methodrefConstant.referencedClass, - methodrefConstant.referencedMethod, - argIndex, - offset, - clazz, - method, - codeAttribute, - trace, - leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { - ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); - return lambdaMap.get(getStaticInstruction.constantIndex); - }).collect(Collectors.toList()) - ) - ); - - // We can't continue the loop because we already changed the code, the offset of the instruction we - // are currently operating on might have changed resulting in strange behaviour. - if (iterativeInstructionVisitor.codeHasChanged()) { + if(lambdaUsageHandler.handle( + targetLambda, + methodrefConstant.referencedClass, + methodrefConstant.referencedMethod, + argIndex, + offset, + clazz, + method, + codeAttribute, + trace, + leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { + ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); + return lambdaMap.get(getStaticInstruction.constantIndex); + }).collect(Collectors.toList()) + )) { + // We can't continue the loop because we already changed the code, the offset of the instruction we + // are currently operating on might have changed resulting in strange behaviour. + iterativeInstructionVisitor.setCodeChanged(true); break; } } From 7613999cc3fcf176e7face373d1548ac25a31f26 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 12:22:05 +0200 Subject: [PATCH 41/72] This doesn't have to be a nested if --- .../optimize/inline/LambdaUsageFinder.java | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 143785ed8..0a86dc5e6 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -116,27 +116,25 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, } } - if (match) { - if(lambdaUsageHandler.handle( - targetLambda, - methodrefConstant.referencedClass, - methodrefConstant.referencedMethod, - argIndex, - offset, - clazz, - method, - codeAttribute, - trace, - leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { - ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); - return lambdaMap.get(getStaticInstruction.constantIndex); - }).collect(Collectors.toList()) - )) { - // We can't continue the loop because we already changed the code, the offset of the instruction we - // are currently operating on might have changed resulting in strange behaviour. - iterativeInstructionVisitor.setCodeChanged(true); - break; - } + if (match && lambdaUsageHandler.handle( + targetLambda, + methodrefConstant.referencedClass, + methodrefConstant.referencedMethod, + argIndex, + offset, + clazz, + method, + codeAttribute, + trace, + leafNodes.stream().filter(it -> it.instruction().opcode == Instruction.OP_GETSTATIC).map(it -> { + ConstantInstruction getStaticInstruction = (ConstantInstruction) it.instruction(); + return lambdaMap.get(getStaticInstruction.constantIndex); + }).collect(Collectors.toList()) + )) { + // We can't continue the loop because we already changed the code, the offset of the instruction we + // are currently operating on might have changed resulting in strange behaviour. + iterativeInstructionVisitor.setCodeChanged(true); + break; } } logger.debug("---------End-----------"); From ee3e3ab4ee6f9946430f2fb839a13e4cab2f6fef Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 12:45:56 +0200 Subject: [PATCH 42/72] Added more javadoc comments to the IterativeInstructionVisitor --- .../inline/IterativeInstructionVisitor.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java index 34803c432..69c6211b1 100644 --- a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java @@ -7,6 +7,16 @@ import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.visitor.InstructionVisitor; +/** + * This class allows the programmer to visit the instructions of a given code attribute but if the code is changed + * restart the iteration of the instructions so that the program can operate on these new instructions. + *

+ * You just use instructionsAccept to start iterating over the instructions, you provide an instructionVisitor and it + * will visit all the instructions in the code attribute. + *

+ * By using setCodeChanged you can tell this class that you changed the code, it will when fetching the next instruction + * jump back to the first one in the updated code attribute. + */ public class IterativeInstructionVisitor { private boolean changed; @@ -29,10 +39,18 @@ public void instructionsAccept(Clazz clazz, Method method, CodeAttribute codeAtt } } + /** + * Allows checking if the code was reported to be changed, this makes it possible to skip certain operations that + * normally execute before restarting the iteration. + */ public boolean codeHasChanged() { return changed; } + /** + * This method notifies this class that the instruction sequence it is visiting has been changed and that it should + * restart. + */ public void setCodeChanged(boolean changed) { this.changed = changed; } From f1ba773baa0e9f4e7393a4600720634978f71e4d Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 13:02:04 +0200 Subject: [PATCH 43/72] Added more javadoc strings and fixed a typo --- .../optimize/inline/BaseLambdaInliner.java | 35 +++++++++++++++---- .../optimize/inline/lambdalocator/Lambda.java | 3 ++ .../inline/lambdalocator/LambdaLocator.java | 6 ++++ 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index a2f5650e7..8ee73d194 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -41,6 +41,11 @@ import java.util.ArrayList; import java.util.List; +/** + * A class that given a method that consumes a lambda is able to create a new method which has this lambda inlined. The + * caller will still need to replace the call instruction and handle removing arguments provided to the original call + * instruction if this class manages to inline the lambda. + */ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; private final Method consumingMethod; @@ -49,7 +54,7 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVis private final CodeAttributeEditor codeAttributeEditor; private final boolean isStatic; private final int calledLambdaIndex; - private final int sizeAdjustedLamdaIndex; + private final int sizeAdjustedLambdaIndex; private final PartialEvaluator partialEvaluator; private Clazz interfaceClass; private Clazz lambdaClass; @@ -68,7 +73,7 @@ public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consuming this.codeAttributeEditor = new CodeAttributeEditor(); this.isStatic = (consumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; this.calledLambdaIndex = calledLambdaIndex + (isStatic ? 0 : 1); - this.sizeAdjustedLamdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, this.calledLambdaIndex); + this.sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethod.getDescriptor(consumingClass), true, this.calledLambdaIndex); this.partialEvaluator = new PartialEvaluator(); this.invokeMethodCallOffsets = new ArrayList<>(); } @@ -77,7 +82,11 @@ public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consuming * Idea: Inline a lambda by just giving it a consuming method and class in which it has to inline the particular * lambda, a reference to the newly created function with the inlined lambda is returned, the caller can then * replace the call instruction with a call to this newly created function. This way we don't need to think about - * how it is called exactly, it can be used as a higher abstraction in other places. + * how it is called exactly, it can be used as a higher abstraction in other places such as in the recursive + * inliner. + * + * @return Returns a new method which has the lambda inlined in it, the lambda argument is also removed. If this + * class was unable to inline the lambda into the method it will return null. */ public Method inline() { if (consumingMethod instanceof LibraryMethod) @@ -102,6 +111,16 @@ public Method inline() { return inlinedLambdaMethod; } + /** + * A method that allows to conditionally inline lambdas based on information about the consuming method and the + * lambda implementation method. + * @param consumingClass The consuming class, this is the class that contains the consuming method. + * @param consumingMethod The consuming method (method taking the lambda as an argument). + * @param lambdaClass The class containing the implementation method of the lambda. + * @param lambdaImplMethod The implementation method of the lambda. + * @return Returns true if it should attempt to inline this lambda, if it's false it will stop and this lambda will + * not be inlined. + */ protected abstract boolean shouldInline(Clazz consumingClass, Method consumingMethod, Clazz lambdaClass, Method lambdaImplMethod); @Override @@ -169,7 +188,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Remove checkNotNullParameter() call because arguments that are lambdas will be removed InstructionCounter removedNullCheckInstrCounter = new InstructionCounter(); - copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLamdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLambdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); //remove inlined lambda from arguments through the descriptor Method methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { @@ -199,7 +218,7 @@ public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant Instruction replacementInstruction = removedNullCheckInstrCounter.getCount() != 0 ? new VariableInstruction(Instruction.OP_ACONST_NULL) : new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); - methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLamdaIndex, replacementInstruction)); + methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); } }); appView.programClassPool.classesAccept(new AccessFixer()); @@ -255,7 +274,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c variableInstruction = (VariableInstruction) tracedInstruction; // Check if the aload index is the same as the current lambda index in the arguments - if (variableInstruction.variableIndex == sizeAdjustedLamdaIndex) { + if (variableInstruction.variableIndex == sizeAdjustedLambdaIndex) { // Replace call to lambda invoke method by a static call to the copied static invoke method ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) clazz); int constantIndex = constantPoolEditor.addMethodrefConstant(clazz, staticInvokeMethod); @@ -272,6 +291,10 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c } } + /** + * A class that removes casts before and after the invoke call. The casts before are for the arguments the casts + * after are for the return value. + */ private class PrePostCastRemover implements AttributeVisitor{ private final int nbrArgs; private final List keepList; diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java index a54d38e43..3ffe0a5f6 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java @@ -5,6 +5,9 @@ import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.instruction.ConstantInstruction; +/** + * A simple class containing information about a lambda. + */ public final class Lambda { private final Clazz clazz; private final Method method; diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java index 30bcfb939..81f699df1 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java @@ -28,6 +28,12 @@ import java.util.Map; import java.util.Set; +/** + * A class that in a program locates the starting point of a potential Kotlin lambda (sometimes called static lambdas + * in the code) usage. The starting point means that it looks for getstatic instructions that get a + * reference to an INSTANCE field of a lambda class. A lambda class is a class that implements the + * kotlin/jvm/internal/Lambda interface. + */ public class LambdaLocator implements InstructionVisitor, ConstantVisitor, MemberVisitor { private final List staticLambdas = new ArrayList<>(); private final Map staticLambdaMap = new HashMap<>(); From 7d602c96e7d8334a7080da99a3be48a25921b00a Mon Sep 17 00:00:00 2001 From: timothy Date: Wed, 9 Aug 2023 15:11:49 +0200 Subject: [PATCH 44/72] Renamed isSourceInstruction to isNotSourceInstruction Resolved infinite loop when tracing source instruction of unreachable code --- .../optimize/inline/SourceTracer.java | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/SourceTracer.java b/base/src/main/java/proguard/optimize/inline/SourceTracer.java index e52c9fa9d..63855ea14 100644 --- a/base/src/main/java/proguard/optimize/inline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/inline/SourceTracer.java @@ -47,7 +47,7 @@ public static List traceParameterOffset(PartialEvaluator pa while ( (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(currentOffset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(0)) && - isSourceInstruction(currentInstruction) + isNotSourceInstruction(currentInstruction) ) { logger.debug(currentInstruction.toString(currentOffset)); currentTracedStack = partialEvaluator.getStackBefore(currentOffset); @@ -62,7 +62,11 @@ public static List traceParameterOffset(PartialEvaluator pa } currentOffset = offsetValue.instructionOffset(0); } else { - currentOffset = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue().instructionOffset(0); + int newOffset = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue().instructionOffset(0); + if (newOffset == currentOffset) { + return null; + } + currentOffset = newOffset; } currentInstruction = InstructionFactory.create(codeAttribute.code, currentOffset); trace.add(new InstructionAtOffset(currentInstruction, currentOffset)); @@ -80,7 +84,7 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt Node root = new Node(new InstructionAtOffset(currentInstruction, offset), new ArrayList<>()); // We stop when we found the source instruction - if (isSourceInstruction(currentInstruction)) { + if (isNotSourceInstruction(currentInstruction)) { logger.debug(currentInstruction.toString(offset)); currentTracedStack = partialEvaluator.getStackBefore(offset); logger.debug(currentTracedStack); @@ -94,10 +98,16 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt offsetValue = currentTracedStack.getTopActualProducerValue(0).instructionOffsetValue(); } for (int i = 0; i < offsetValue.instructionOffsetCount(); i++) { - if (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(i)) - root.children.add(traceParameterTree(partialEvaluator, codeAttribute, offsetValue.instructionOffset(i), leafNodes)); - else + if (!isLoad(currentInstruction) || !partialEvaluator.getVariablesBefore(offset).getProducerValue(((VariableInstruction) currentInstruction).variableIndex).instructionOffsetValue().isMethodParameter(i)) { + int newOffset = offsetValue.instructionOffset(i); + if (newOffset == offset) { + return root; + } + root.children.add(traceParameterTree(partialEvaluator, codeAttribute, newOffset, leafNodes)); + } + else { leafNodes.add(root.value); + } } } else { @@ -112,7 +122,7 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt * itself, without having to look further up the chain of instructions. We use this as a stopping condition when we * are tracing the source of an instruction. */ - private static boolean isSourceInstruction(Instruction instruction) { + private static boolean isNotSourceInstruction(Instruction instruction) { return instruction.opcode != Instruction.OP_ACONST_NULL && instruction.canonicalOpcode() != Instruction.OP_ICONST_0 && instruction.canonicalOpcode() != Instruction.OP_DCONST_0 && From 2648338bf5acc9d3146a913cff7941b069784e10 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 15:45:39 +0200 Subject: [PATCH 45/72] Added more javadoc comments --- .../inline/CannotInlineException.java | 4 ++ .../optimize/inline/CastPatternRemover.java | 4 ++ .../proguard/optimize/inline/CastRemover.java | 5 ++ .../optimize/inline/DescriptorModifier.java | 5 ++ .../inline/FirstLambdaParameterFinder.java | 21 --------- .../optimize/inline/InstructionAtOffset.java | 6 ++- .../inline/LambdaImplementationVisitor.java | 4 ++ .../optimize/inline/LambdaInliner.java | 3 ++ .../optimize/inline/LambdaUsageFinder.java | 4 ++ .../optimize/inline/LocalUsageRemover.java | 17 +++++++ .../optimize/inline/MethodLengthFinder.java | 5 +- .../java/proguard/optimize/inline/Node.java | 26 +++------- .../optimize/inline/NullCheckRemover.java | 21 +++++++-- .../optimize/inline/RefMethodFinder.java | 5 +- .../optimize/inline/SourceTracer.java | 47 +++++++++++++++++-- 15 files changed, 122 insertions(+), 55 deletions(-) delete mode 100644 base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java diff --git a/base/src/main/java/proguard/optimize/inline/CannotInlineException.java b/base/src/main/java/proguard/optimize/inline/CannotInlineException.java index 9701d74dd..cac10ea9a 100644 --- a/base/src/main/java/proguard/optimize/inline/CannotInlineException.java +++ b/base/src/main/java/proguard/optimize/inline/CannotInlineException.java @@ -1,5 +1,9 @@ package proguard.optimize.inline; +/** + * An exception thrown by the RecursiveInliner that is caught by the BaseLambdaInliner, this allows it to jump out of + * the visitor it is currently in and abort the inlining process. + */ public class CannotInlineException extends RuntimeException { public CannotInlineException(String reason) { super(reason); diff --git a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java index b1cb648a1..2866080f0 100644 --- a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java @@ -18,6 +18,10 @@ import java.io.PrintWriter; import java.io.StringWriter; +/** + * This class removes the casting at the end of a lambda invoke method, it does this through pattern matching. It + * replaces the pattern invokestatic valueOf, return with just return. + */ public class CastPatternRemover implements InstructionVisitor { private final Logger logger = LogManager.getLogger(this.getClass()); private final InstructionSequenceMatcher insSeqMatcher; diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/inline/CastRemover.java index 3115411a4..a55d910b2 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/inline/CastRemover.java @@ -17,6 +17,11 @@ import java.util.List; import java.util.Set; +/** + * A general cast remover class used for removing the casts before and after a bridge method invoke call. It has a + * keepList which is a list of argument indexes for which it should keep the cast. The argIndex indicates the current + * argument it is processing. + */ public class CastRemover implements InstructionVisitor { private final CodeAttributeEditor codeAttributeEditor; private final List keepList; diff --git a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java index 28761addd..1266e8c94 100644 --- a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java +++ b/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java @@ -13,6 +13,11 @@ import java.util.function.Function; +/** + * A class that takes a method and creates a new method with a different name that has an updated descriptor. The + * descriptor is made using a lambda that takes in the old descriptor and then returns the new descriptor. Optionally + * the original method can be deleted. + */ public class DescriptorModifier { private final ClassBuilder classBuilder; private final NameFactory nameFactory; diff --git a/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java b/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java deleted file mode 100644 index 31e8f8ded..000000000 --- a/base/src/main/java/proguard/optimize/inline/FirstLambdaParameterFinder.java +++ /dev/null @@ -1,21 +0,0 @@ -package proguard.optimize.inline; - -import proguard.classfile.util.InternalTypeEnumeration; - -public class FirstLambdaParameterFinder { - public static int findFirstLambdaParameter(String descriptor) { - return findFirstLambdaParameter(descriptor, true); - } - - public static int findFirstLambdaParameter(String descriptor, boolean isStatic) { - InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(descriptor); - int index = 0; - while (internalTypeEnumeration.hasMoreTypes()) { - if (internalTypeEnumeration.nextType().startsWith("Lkotlin/jvm/functions/Function")) { - break; - } - index ++; - } - return index + (isStatic ? 0 : 1); - } -} diff --git a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java index 4f40851ea..b97f95e1c 100644 --- a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java +++ b/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java @@ -2,8 +2,10 @@ import proguard.classfile.instruction.Instruction; -import java.util.Objects; - +/** + * A class that holds an instruction and offset at which it was found, this is useful in some cases where we need to + * return both of them form somewhere. + */ public final class InstructionAtOffset { private final Instruction instruction; private final int offset; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java index 92c8f7415..596bddca5 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java @@ -12,6 +12,10 @@ import proguard.classfile.visitor.AllMethodVisitor; import proguard.classfile.visitor.MemberVisitor; +/** + * A visitor that can given a field reference used in getstatic for example to obtain a lambda instance + * find the referenced class and invoke method that contains the lambda implementation. + */ public class LambdaImplementationVisitor implements ConstantVisitor, MemberVisitor { private final InvokeMethodVisitor invokeMethodVisitor; private Clazz lambdaImplementationClass; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 4993bd236..619189f51 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -21,6 +21,9 @@ import java.util.List; import java.util.Set; +/** + * This class implements the lambda inlining pass that operates on the entire program. + */ public class LambdaInliner implements Pass { private final Logger logger = LogManager.getLogger(); private final String classNameFilter; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 0a86dc5e6..1445103ec 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -25,6 +25,10 @@ import java.util.function.Function; import java.util.stream.Collectors; +/** + * This class will try to find method calls that consume the lambda, these are then passed to a lambdaUsageHandler which + * can then decide to inline the lambda into the method that was found to be using the lambda as an argument. + */ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, ConstantVisitor { private final Lambda targetLambda; private PartialEvaluator partialEvaluator; diff --git a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java index 172d98427..60d9180b8 100644 --- a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java +++ b/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java @@ -14,6 +14,23 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.visitor.MemberVisitor; +/** + * This class can be used to remove the usage of a local variable from a method. The class accomplishes that by shifting + * usages of load and store. + *

+ * So for example we want to remove the usage of local variable 2. We will then keep all the load and stores to variable + * 0 and 1, but we will shift load and store instructions that are higher than 2 down so aload_3 becomes + * aload_2 and so on. Instructions such as aload_2 that might still be inside the + * codeAttribute will be replaced with the replacementInstruction which by default is aconst_null. So it + * still pushes something onto the stack just like before we removed the usage. In the case of astore_2 we + * would replace that with a pop instruction, so it still removes one element form the stack just like + * before. + *

+ * This class is used when inlining lambdas, we inline the lambda and afterward we don't need the local that originally + * stored the lambda anymore. In that case we can use this class to remove the local from the method. In some cases + * null checks are done on the lambda in this case there is still a usage and replacing it with aconst_null + * would result in incorrect behaviour, in that case we will use a different replacementInstruction. + */ public class LocalUsageRemover implements MemberVisitor, InstructionVisitor, AttributeVisitor { private final CodeAttributeEditor codeAttributeEditor; private final int argumentIndex; diff --git a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java index a1d81f538..724462774 100644 --- a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java +++ b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java @@ -8,8 +8,7 @@ import proguard.classfile.attribute.visitor.AttributeVisitor; /** - * A simple utility class that gets the length of a method, if the method has no code attribute it will return -1 as the - * length. + * A simple utility class that can be used to easily obtain the length of a method. */ public class MethodLengthFinder { private static int codeLength; @@ -17,7 +16,7 @@ public class MethodLengthFinder { /** * @param method A Method object from which we'll get the length. * @param clazz The class in which the method is. - * @return The length of the method. + * @return The length of the method, if the method has no code attribute it will return -1. */ public static int getMethodCodeLength(Clazz clazz, Method method) { codeLength = -1; // If a method has no codeAttribute we don't want to return the previous method length value. diff --git a/base/src/main/java/proguard/optimize/inline/Node.java b/base/src/main/java/proguard/optimize/inline/Node.java index 09e07c5e4..d0d1e3c72 100644 --- a/base/src/main/java/proguard/optimize/inline/Node.java +++ b/base/src/main/java/proguard/optimize/inline/Node.java @@ -1,8 +1,13 @@ package proguard.optimize.inline; import java.util.List; -import java.util.Objects; +/** + * A very simple DTO that contains Node objects used to build a tree of InstructionAtOffset objects used in the + * SourceTracer. A tree is used because an argument of a method call can have multiple source locations. The program + * will at runtime only get the value form one location of course, but we cannot predict which branches the code will + * take at compile time, because of this we have a tree that contains all the options. + */ public final class Node { public InstructionAtOffset value; public List children; @@ -11,23 +16,4 @@ public final class Node { this.value = value; this.children = children; } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - Node that = (Node) obj; - return Objects.equals(this.value, that.value); - } - - @Override - public int hashCode() { - return Objects.hash(value); - } - - @Override - public String toString() { - return "Node[" + - "value=" + value + ']'; - } } diff --git a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java index 6325f6ede..c20a22a44 100644 --- a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java @@ -13,7 +13,19 @@ import proguard.classfile.instruction.visitor.InstructionVisitor; import proguard.classfile.util.InstructionSequenceMatcher; -class NullCheckRemover implements InstructionVisitor { +/** + * This class removes Kotlin null checks that make use of the checkNotNullParameter this is useful for + * when removing an argument of a method, in that case you also want to remove the null check for that argument. + *

+ * Example bytecode: + *

{@code
+ * [0] aload_0 v0
+ * [1] ldc #10 = String("argumentName")
+ * [3] invokestatic #16 = Methodref(kotlin/jvm/internal/Intrinsics.checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V)
+ * }
+ * If the argumentIndex is set to 0 then these 3 instructions will be removed. + */ +public class NullCheckRemover implements InstructionVisitor { private final InstructionSequenceMatcher insSeqMatcher; private final int X = InstructionSequenceMatcher.X; private final int Y = InstructionSequenceMatcher.Y; @@ -28,9 +40,10 @@ public NullCheckRemover(int argumentIndex, CodeAttributeEditor codeAttributeEdit InstructionSequenceBuilder ____ = new InstructionSequenceBuilder(); - pattern = ____.aload(X) - .ldc_(C) - .invokestatic(Y).__(); + pattern = + ____.aload(X) + .ldc_(C) + .invokestatic(Y).__(); constants = ____.constants(); diff --git a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java index 8b5bf9617..daa53b868 100644 --- a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java +++ b/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java @@ -1,4 +1,3 @@ - package proguard.optimize.inline; import proguard.classfile.Clazz; @@ -6,10 +5,12 @@ import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; -import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.visitor.MemberVisitor; import proguard.classfile.visitor.ReferencedMemberVisitor; +/** + * A utility class that can be used to easily obtain the referenced method when given a constant index. + */ public class RefMethodFinder { private final Clazz clazz; private Method foundMethod; diff --git a/base/src/main/java/proguard/optimize/inline/SourceTracer.java b/base/src/main/java/proguard/optimize/inline/SourceTracer.java index 63855ea14..914df4043 100644 --- a/base/src/main/java/proguard/optimize/inline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/inline/SourceTracer.java @@ -14,6 +14,23 @@ import java.util.List; import java.util.stream.Collectors; +/** + * This class has a few static methods which can be used to trace the source instruction of another instruction. + *

+ * For example, you have a method call that uses an argument, the argument is pushed onto the stack at offset 18. In + * this case you can use the traceParameter to find which instruction was originally responsible for + * creating this value. For example the argument was loaded using iload_3, the previous instruction was + * istore_3, the one before that was iload_2, the one before that was istore_2 + * and the instruction before that was bipush 10. In this case bipush 10 would be considered + * the source instruction. It is the original producer of the value. + *

+ * The methods provided here only look in the same method, so if there was no bipush 10 but the last + * instruction we found was iload_2 then we know that the source of this instruction is argument 2. + *

+ * This is useful for when you are for example, trying to inline a lambda, you can see which method call in a method + * consumes a certain argument that we know is a lambda. In that case we can try to inline that lambda into that + * particular method call. + */ public class SourceTracer { private static final Logger logger = LogManager.getLogger(SourceTracer.class); @@ -36,6 +53,15 @@ public static List traceParameterSources(PartialEvaluator partialEv return leafNodes.stream().map(InstructionAtOffset::instruction).collect(Collectors.toList()); } + /** + * This method will return a chain of instructions going from the original starting instruction to the source + * instruction. It traces backwards so instructions with a bigger offset will appear before instructions with a + * smaller offset. + * @param partialEvaluator The partial evaluator that has visited this code attribute. + * @param codeAttribute The code attribute in which we are searching. + * @param offset The starting offset of the instruction for which we want to trace the source value. + * @return A chain of instructions going from the instruction at offset to the source instruction. + */ public static List traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) { List trace = new ArrayList<>(); TracedStack currentTracedStack; @@ -78,6 +104,21 @@ public static boolean isLoad(Instruction instruction) { return instruction.getClass().equals(VariableInstruction.class) && ((VariableInstruction) instruction).isLoad(); } + /** + * Similar to {@link #traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset)} + * but this method will build a tree instead of just a list. A list is not always possible because sometimes a + * instruction can have multiple source values. For example when one of the source values is in an if statement. + * We don't know if the branch will be taken or not so we have 2 potential source values. In this case the tree + * structure is very useful. + * @param partialEvaluator The partial evaluator that has visited this code attribute. + * @param codeAttribute The code attribute in which we are searching. + * @param offset The offset of the instruction we are trying to find the source values for. + * @param leafNodes The source nodes of the tree, this method will add all possible source instructions to this list + * @return A node which is the root node of the tree, it starts at the offset and then goes up in the code attribute + * so just like the method that creates a chain this one goes from higher instruction offsets to lower + * instruction offsets. + * @see #traceParameterOffset(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset) + */ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAttribute codeAttribute, int offset, List leafNodes) { TracedStack currentTracedStack; Instruction currentInstruction = InstructionFactory.create(codeAttribute.code, offset); @@ -118,9 +159,9 @@ public static Node traceParameterTree(PartialEvaluator partialEvaluator, CodeAtt } /** - * Checks if an instruction is considered a source instruction, this is an instruction that produces a value by - * itself, without having to look further up the chain of instructions. We use this as a stopping condition when we - * are tracing the source of an instruction. + * Checks if an instruction is not considered a source instruction, a source instruction is an instruction that + * produces a value by itself, without having to look further up the chain of instructions. We use this as a + * stopping condition when we are tracing the source of an instruction. */ private static boolean isNotSourceInstruction(Instruction instruction) { return instruction.opcode != Instruction.OP_ACONST_NULL && From b431cb7a61ca0d5b3ebcf8ae036f941a28c95a79 Mon Sep 17 00:00:00 2001 From: Maarten Steevens Date: Wed, 9 Aug 2023 16:45:52 +0200 Subject: [PATCH 46/72] Apply suggestions from code review Co-authored-by: rubenpieters --- .../optimize/inline/BaseLambdaInliner.java | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 8ee73d194..12ac0e124 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -148,7 +148,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Don't inline if the lamdba is passed to another method try { copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(calledLambdaIndex)); - } catch(CannotInlineException cie) { + } catch (CannotInlineException cie) { ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(copiedConsumingMethod); classEditor.removeMethod(staticInvokeMethod); @@ -171,7 +171,7 @@ public void visitAnyMember(Clazz clazz, Member member) { copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PrePostCastRemover(invokeMethodDescriptor))); } - // Remove return value's casting from staticInvokeMethod + // Remove return value's casting from staticInvokeMethod. staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); // Important for inlining, we need this so that method invocations have non-null referenced methods. @@ -182,19 +182,19 @@ public void visitAnyMember(Clazz clazz, Member member) { // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. inlineMethodInClass(consumingClass, staticInvokeMethod); - // Remove the static invoke method once it has been inlined + // Remove the static invoke method once it has been inlined. ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(staticInvokeMethod); - // Remove checkNotNullParameter() call because arguments that are lambdas will be removed + // Remove checkNotNullParameter() call because arguments that are lambdas will be removed. InstructionCounter removedNullCheckInstrCounter = new InstructionCounter(); copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLambdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); - //remove inlined lambda from arguments through the descriptor + // Remove inlined lambda from arguments through the descriptor. Method methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { List list = new ArrayList<>(); InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(desc); - while(internalTypeEnumeration.hasMoreTypes()) { + while (internalTypeEnumeration.hasMoreTypes()) { list.add(internalTypeEnumeration.nextType()); } // We adjust the index to not take the this parameter into account because this is not visible in the @@ -305,7 +305,7 @@ private PrePostCastRemover(String invokeMethodDescriptor) { InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(invokeMethodDescriptor); int i = 0; - while(internalTypeEnumeration.hasMoreTypes()) { + while (internalTypeEnumeration.hasMoreTypes()) { if (internalTypeEnumeration.nextType().equals("Ljava/lang/Object;")) { // Argument i is object, we should keep the cast for this argument keepList.add(i); From 11f21f1fc2eaeacc834ce94ab665de7a9049420a Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 17:29:18 +0200 Subject: [PATCH 47/72] Use Optional in MethodLengthFinder so the programmer is made aware that some methods don't have a length --- .../optimize/inline/BaseLambdaInliner.java | 5 ++++- .../optimize/inline/MethodLengthFinder.java | 13 +++++++----- .../optimize/inline/ShortLambdaInliner.java | 21 +++++++++++++++---- 3 files changed, 29 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 8ee73d194..6d5af755a 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -40,6 +40,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; /** * A class that given a method that consumes a lambda is able to create a new method which has this lambda inlined. The @@ -159,7 +160,9 @@ public void visitAnyMember(Clazz clazz, Member member) { invokeMethodCallOffsets.clear(); // Replace invokeinterface call to invoke method with invokestatic to staticInvokeMethod - codeAttributeEditor.reset(MethodLengthFinder.getMethodCodeLength(consumingClass, copiedConsumingMethod)); + Optional consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, copiedConsumingMethod); + assert consumingMethodLength.isPresent(); + codeAttributeEditor.reset(consumingMethodLength.get()); copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor( new AllInstructionVisitor( diff --git a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java index 724462774..d76017e4b 100644 --- a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java +++ b/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java @@ -7,25 +7,28 @@ import proguard.classfile.attribute.visitor.AllAttributeVisitor; import proguard.classfile.attribute.visitor.AttributeVisitor; +import java.util.Optional; + /** * A simple utility class that can be used to easily obtain the length of a method. */ public class MethodLengthFinder { - private static int codeLength; + private static Optional codeLength; /** * @param method A Method object from which we'll get the length. * @param clazz The class in which the method is. - * @return The length of the method, if the method has no code attribute it will return -1. + * @return The length of the method, will return an empty optional if there is no code attribute. */ - public static int getMethodCodeLength(Clazz clazz, Method method) { - codeLength = -1; // If a method has no codeAttribute we don't want to return the previous method length value. + public static Optional getMethodCodeLength(Clazz clazz, Method method) { + // If a method has no codeAttribute we don't want to return the previous method length value. + codeLength = Optional.empty(); method.accept(clazz, new AllAttributeVisitor(new AttributeVisitor() { @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} @Override public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - codeLength = codeAttribute.u4codeLength; + codeLength = Optional.of(codeAttribute.u4codeLength); } })); return codeLength; diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java index 385f5fcfc..8f26b9d97 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -7,6 +7,8 @@ import proguard.classfile.Method; import proguard.optimize.inline.lambdalocator.Lambda; +import java.util.Optional; + /** * This class is an implementation of the {@link proguard.optimize.inline.BaseLambdaInliner BaseLambdaInliner } that * inlines lambdas depending on the length of the lambda implementation method and the length of the consuming method. @@ -24,15 +26,26 @@ public ShortLambdaInliner(AppView appView, Clazz consumingClass, Method consumin @Override protected boolean shouldInline(Clazz consumingClass, Method consumingMethod, Clazz lambdaClass, Method lambdaImplMethod) { - int consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, consumingMethod); - int lambdaImplMethodLength = MethodLengthFinder.getMethodCodeLength(lambdaClass, lambdaImplMethod); + Optional consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, consumingMethod); + Optional lambdaImplMethodLength = MethodLengthFinder.getMethodCodeLength(lambdaClass, lambdaImplMethod); + + if (!consumingMethodLength.isPresent()) { + logger.error("Will not attempt to inline lambda because of error:"); + logger.error("The consuming method of a lambda has to have an implementation. Consuming method = {}#{}{}", consumingClass.getName(), consumingMethod.getName(consumingClass), consumingMethod.getDescriptor(consumingClass)); + return false; + } + if (!lambdaImplMethodLength.isPresent()) { + logger.error("Will not attempt to inline lambda because of error:"); + logger.error("The lambda implementation method has to have an implementation. Lambda implementation method = {}#{}{}", lambdaClass.getName(), lambdaImplMethod.getName(lambdaClass), lambdaImplMethod.getDescriptor(lambdaClass)); + return false; + } - boolean inline = lambdaImplMethodLength < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength < MAXIMUM_CONSUMING_METHOD_LENGTH; + boolean inline = lambdaImplMethodLength.get() < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength.get() < MAXIMUM_CONSUMING_METHOD_LENGTH; if (!inline) { logger.info("Will not attempt inlining lambda because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); logger.info("Consuming method = {}#{}{}", consumingClass.getName(), consumingMethod.getName(consumingClass), consumingMethod.getDescriptor(consumingClass)); logger.info("Lambda implementation method = {}#{}{}", lambdaClass.getName(), lambdaImplMethod.getName(lambdaClass), lambdaImplMethod.getDescriptor(lambdaClass)); - logger.info("Consuming method length = {}, lambda implementation method length = {}", consumingMethodLength, lambdaImplMethodLength); + logger.info("Consuming method length = {}, lambda implementation method length = {}", consumingMethodLength.get(), lambdaImplMethodLength.get()); } return inline; } From 0cc53cd4f3e6320148c5a5838974a61b90f7d4f3 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 17:54:27 +0200 Subject: [PATCH 48/72] Return ProgramMethod instead of just Method --- .../java/proguard/optimize/inline/BaseLambdaInliner.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index ab678a42c..13e8b67e4 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -61,7 +61,7 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVis private Clazz lambdaClass; private Method lambdaInvokeMethod; private String bridgeDescriptor; - private Method inlinedLambdaMethod; + private ProgramMethod inlinedLambdaMethod; private Method staticInvokeMethod; private InterfaceMethodrefConstant referencedInterfaceConstant; private final List invokeMethodCallOffsets; @@ -89,7 +89,7 @@ public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consuming * @return Returns a new method which has the lambda inlined in it, the lambda argument is also removed. If this * class was unable to inline the lambda into the method it will return null. */ - public Method inline() { + public ProgramMethod inline() { if (consumingMethod instanceof LibraryMethod) return null; @@ -194,13 +194,13 @@ public void visitAnyMember(Clazz clazz, Member member) { copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLambdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); // Remove inlined lambda from arguments through the descriptor. - Method methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { + ProgramMethod methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { List list = new ArrayList<>(); InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(desc); while (internalTypeEnumeration.hasMoreTypes()) { list.add(internalTypeEnumeration.nextType()); } - // We adjust the index to not take the this parameter into account because this is not visible in the + // We adjust the index to not take the "this" parameter into account because this is not visible in the // method descriptor. list.remove(calledLambdaIndex - (isStatic ? 0 : 1)); From a440b457c7269a6d5834920e141d7ff00c9a5d16 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 18:01:16 +0200 Subject: [PATCH 49/72] Use the name kotlin lambdas instead of static lambdas in the LambdaLocator --- .../optimize/inline/LambdaInliner.java | 4 +-- .../inline/lambdalocator/LambdaLocator.java | 33 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 619189f51..20c6808ff 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -41,12 +41,12 @@ public LambdaInliner() { public void execute(AppView appView) { LambdaLocator lambdaLocator = new LambdaLocator(appView.programClassPool, classNameFilter); - for (Lambda lambda : lambdaLocator.getStaticLambdas()) { + for (Lambda lambda : lambdaLocator.getKotlinLambdas()) { Set remainder = new HashSet<>(); inlinedAllUsages = true; InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), - new LambdaUsageFinder(lambda, lambdaLocator.getStaticLambdaMap(), + new LambdaUsageFinder(lambda, lambdaLocator.getKotlinLambdaMap(), new LambdaUsageHandler(appView, remainder) ) ); diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java index 81f699df1..e74c8d42d 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java @@ -29,14 +29,13 @@ import java.util.Set; /** - * A class that in a program locates the starting point of a potential Kotlin lambda (sometimes called static lambdas - * in the code) usage. The starting point means that it looks for getstatic instructions that get a - * reference to an INSTANCE field of a lambda class. A lambda class is a class that implements the - * kotlin/jvm/internal/Lambda interface. + * A class that in a program locates the starting point of a potential Kotlin lambda usage. The starting point means + * that it looks for getstatic instructions that get a reference to an INSTANCE field of a lambda class. A + * lambda class is a class that implements the kotlin/jvm/internal/Lambda interface. */ public class LambdaLocator implements InstructionVisitor, ConstantVisitor, MemberVisitor { - private final List staticLambdas = new ArrayList<>(); - private final Map staticLambdaMap = new HashMap<>(); + private final List kotlinLambdas = new ArrayList<>(); + private final Map kotlinLambdaMap = new HashMap<>(); private final Set lambdaClasses = new HashSet<>(); private final ClassPool classPool; private static final Logger logger = LogManager.getLogger(LambdaLocator.class); @@ -48,7 +47,7 @@ public LambdaLocator(ClassPool classPool, String classNameFilter) { clazz.superClassConstantAccept(this); }); - // Find static lambdas + // Find Kotlin lambdas classPool.classesAccept(classNameFilter, clazz -> { HashMap h = new HashMap<>(); @@ -58,7 +57,7 @@ public LambdaLocator(ClassPool classPool, String classNameFilter) { clazz.methodsAccept(this); }); - logger.info("Number of lambdas found : " + staticLambdas.size()); + logger.info("Number of lambdas found : " + kotlinLambdas.size()); } @Override @@ -72,7 +71,7 @@ public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAt @Override public void visitConstantInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { if (constantInstruction.opcode == Instruction.OP_GETSTATIC) { - clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new StaticLambdaFinder(method, codeAttribute, constantInstruction, offset)); + clazz.constantPoolEntryAccept(constantInstruction.constantIndex, new KotlinLambdaFinder(method, codeAttribute, constantInstruction, offset)); } } @@ -88,21 +87,21 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { } } - public List getStaticLambdas() { - return staticLambdas; + public List getKotlinLambdas() { + return kotlinLambdas; } - public Map getStaticLambdaMap() { - return staticLambdaMap; + public Map getKotlinLambdaMap() { + return kotlinLambdaMap; } - private class StaticLambdaFinder implements ConstantVisitor { + private class KotlinLambdaFinder implements ConstantVisitor { private final ConstantInstruction constantInstruction; private final Method method; private final int offset; private final CodeAttribute codeAttribute; - public StaticLambdaFinder(Method method, CodeAttribute codeAttribute, ConstantInstruction constantInstruction, int offset) { + public KotlinLambdaFinder(Method method, CodeAttribute codeAttribute, ConstantInstruction constantInstruction, int offset) { this.method = method; this.codeAttribute = codeAttribute; this.constantInstruction = constantInstruction; @@ -126,8 +125,8 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { logger.debug("Found a lambda invocation " + constantInstruction); Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); - staticLambdas.add(lambda); - staticLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); + kotlinLambdas.add(lambda); + kotlinLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); } }); } From 7e652b35c123b4d8d555fd35baddd797fe164a1a Mon Sep 17 00:00:00 2001 From: MaartenS Date: Wed, 9 Aug 2023 18:28:52 +0200 Subject: [PATCH 50/72] Renamed constantInstruction and offset to be more precise also added javadoc to Lambda class to explain what each argument/field represents --- .../optimize/inline/BaseLambdaInliner.java | 4 +-- .../optimize/inline/lambdalocator/Lambda.java | 30 ++++++++++++------- .../inline/lambdalocator/LambdaLocator.java | 2 +- 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 13e8b67e4..7b27ebef3 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -93,7 +93,7 @@ public ProgramMethod inline() { if (consumingMethod instanceof LibraryMethod) return null; - ConstantInstruction c = lambda.constantInstruction(); + ConstantInstruction c = lambda.getstaticInstruction(); lambda.clazz().constantPoolEntryAccept(c.constantIndex, new LambdaImplementationVisitor((programClass, programMethod, interfaceClass, bridgeDescriptor) -> { this.interfaceClass = interfaceClass; this.lambdaClass = programClass; @@ -209,7 +209,7 @@ public void visitAnyMember(Clazz clazz, Member member) { // Remove one of the arguments - lambda.clazz().constantPoolEntryAccept(lambda.constantInstruction().constantIndex, new ConstantVisitor() { + lambda.clazz().constantPoolEntryAccept(lambda.getstaticInstruction().constantIndex, new ConstantVisitor() { @Override public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java index 3ffe0a5f6..bb326152e 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java @@ -12,15 +12,23 @@ public final class Lambda { private final Clazz clazz; private final Method method; private final CodeAttribute codeAttribute; - private final int offset; - private final ConstantInstruction constantInstruction; - - public Lambda(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, ConstantInstruction constantInstruction) { + private final int getStaticOffset; + private final ConstantInstruction getstaticInstruction; + + /** + * @param clazz The clazz in which the lambda usage was found. + * @param method The method in which the lambda usage was found. + * @param codeAttribute The code attribute in which the lambda usage was found. + * @param getStaticOffset The offset of the getstatic instruction that was used to obtain a reference to the lambda + * instance. + * @param getstaticInstruction The getstatic instruction itself. + */ + public Lambda(Clazz clazz, Method method, CodeAttribute codeAttribute, int getStaticOffset, ConstantInstruction getstaticInstruction) { this.clazz = clazz; this.method = method; this.codeAttribute = codeAttribute; - this.offset = offset; - this.constantInstruction = constantInstruction; + this.getStaticOffset = getStaticOffset; + this.getstaticInstruction = getstaticInstruction; } public Clazz clazz() { @@ -35,16 +43,16 @@ public CodeAttribute codeAttribute() { return codeAttribute; } - public int offset() { - return offset; + public int getstaticOffset() { + return getStaticOffset; } - public ConstantInstruction constantInstruction() { - return constantInstruction; + public ConstantInstruction getstaticInstruction() { + return getstaticInstruction; } @Override public String toString() { - return constantInstruction.toString(); + return getstaticInstruction.toString(); } } diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java index e74c8d42d..f6cfac082 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java @@ -126,7 +126,7 @@ public void visitUtf8Constant(Clazz clazz, Utf8Constant utf8Constant) { Lambda lambda = new Lambda(clazz, method, codeAttribute, offset, constantInstruction); kotlinLambdas.add(lambda); - kotlinLambdaMap.put(lambda.constantInstruction().constantIndex, lambda); + kotlinLambdaMap.put(lambda.getstaticInstruction().constantIndex, lambda); } }); } From 607bf86a591ae806cf5780789ec5260bc2dc2b76 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 09:42:16 +0200 Subject: [PATCH 51/72] Added more info about what "a lambda" really means --- .../java/proguard/optimize/inline/lambdalocator/Lambda.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java index bb326152e..a69ba8f51 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java @@ -6,7 +6,11 @@ import proguard.classfile.instruction.ConstantInstruction; /** - * A simple class containing information about a lambda. + * A simple class containing information about a Kotlin lambda. These are lambdas implemented using a singleton pattern + * the singleton is stored in the "INSTANCE" field. A reference to these lambdas is obtained by using the getstatic + * instruction which obtains a reference to this field. This invokestatic field is what we store in the + * getstaticInstruction field alongside it's offset getStaticOffset. The clazz, method and code attribute in which this + * instruction appears is also stored in this class. */ public final class Lambda { private final Clazz clazz; From f69fd140bae88780e2ec3b0324090b8597de423c Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 10:10:28 +0200 Subject: [PATCH 52/72] Change some things to the FixedPointCodeAttributeVisitor API --- ...va => FixedPointCodeAttributeVisitor.java} | 11 ++++++-- .../optimize/inline/LambdaUsageFinder.java | 26 ++++++++----------- 2 files changed, 20 insertions(+), 17 deletions(-) rename base/src/main/java/proguard/optimize/inline/{IterativeInstructionVisitor.java => FixedPointCodeAttributeVisitor.java} (83%) diff --git a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java b/base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java similarity index 83% rename from base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java rename to base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java index 69c6211b1..f89603a74 100644 --- a/base/src/main/java/proguard/optimize/inline/IterativeInstructionVisitor.java +++ b/base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java @@ -3,6 +3,7 @@ import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.attribute.CodeAttribute; +import proguard.classfile.attribute.visitor.AttributeVisitor; import proguard.classfile.instruction.Instruction; import proguard.classfile.instruction.InstructionFactory; import proguard.classfile.instruction.visitor.InstructionVisitor; @@ -17,14 +18,20 @@ * By using setCodeChanged you can tell this class that you changed the code, it will when fetching the next instruction * jump back to the first one in the updated code attribute. */ -public class IterativeInstructionVisitor { +public class FixedPointCodeAttributeVisitor implements AttributeVisitor { private boolean changed; + private final InstructionVisitor instructionVisitor; + + public FixedPointCodeAttributeVisitor(InstructionVisitor instructionVisitor) { + this.instructionVisitor = instructionVisitor; + } /** * This helper method does something similar to codeAttribute.instructionsAccept but has the ability to stop and * restart visiting the instructions, this is useful for when you change the code during iteration. */ - public void instructionsAccept(Clazz clazz, Method method, CodeAttribute codeAttribute, InstructionVisitor instructionVisitor) { + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { int offset = 0; while (offset < codeAttribute.u4codeLength) { Instruction instruction = InstructionFactory.create(codeAttribute.code, offset); diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java index 1445103ec..35fd676b0 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java @@ -36,35 +36,31 @@ public class LambdaUsageFinder implements InstructionVisitor, AttributeVisitor, private final LambdaInliner.LambdaUsageHandler lambdaUsageHandler; public MethodrefConstant methodrefConstant; public FieldrefConstant referencedFieldConstant; - private final IterativeInstructionVisitor iterativeInstructionVisitor; + private final FixedPointCodeAttributeVisitor fixedPointCodeAttributeVisitor; private static final Logger logger = LogManager.getLogger(LambdaUsageFinder.class); - public LambdaUsageFinder(Lambda targetLambda, Map lambdaMap, LambdaInliner.LambdaUsageHandler lambdaUsageHandler) { this.targetLambda = targetLambda; this.partialEvaluator = new PartialEvaluator(); this.lambdaMap = lambdaMap; this.lambdaUsageHandler = lambdaUsageHandler; - this.iterativeInstructionVisitor = new IterativeInstructionVisitor(); - } - - @Override - public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { - partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); - iterativeInstructionVisitor.instructionsAccept( - clazz, - method, - codeAttribute, + this.fixedPointCodeAttributeVisitor = new FixedPointCodeAttributeVisitor( new InstructionOpCodeFilter( new int[] { - Instruction.OP_INVOKESTATIC, - Instruction.OP_INVOKEVIRTUAL + Instruction.OP_INVOKESTATIC, + Instruction.OP_INVOKEVIRTUAL }, this ) ); } + @Override + public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAttribute) { + partialEvaluator.visitCodeAttribute(clazz, method, codeAttribute); + codeAttribute.accept(clazz, method, fixedPointCodeAttributeVisitor); + } + @Override public void visitAnyInstruction(Clazz clazz, Method method, CodeAttribute codeAttribute, int offset, Instruction instruction) {} @@ -137,7 +133,7 @@ private void findUsage(Clazz clazz, Method method, CodeAttribute codeAttribute, )) { // We can't continue the loop because we already changed the code, the offset of the instruction we // are currently operating on might have changed resulting in strange behaviour. - iterativeInstructionVisitor.setCodeChanged(true); + fixedPointCodeAttributeVisitor.setCodeChanged(true); break; } } From 95b23abbb077f9d1d00e5c8c9b571cd8cd91f2a2 Mon Sep 17 00:00:00 2001 From: timothy Date: Thu, 10 Aug 2023 10:57:21 +0200 Subject: [PATCH 53/72] invokespecial is now considered a potential source for the sourcetracer + added debug messages --- .../proguard/optimize/inline/BaseLambdaInliner.java | 5 +++++ .../java/proguard/optimize/inline/LambdaInliner.java | 12 ++++++++---- .../java/proguard/optimize/inline/SourceTracer.java | 2 ++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 12ac0e124..dc3442adb 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -1,5 +1,7 @@ package proguard.optimize.inline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.AppView; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; @@ -64,6 +66,8 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVis private Method staticInvokeMethod; private InterfaceMethodrefConstant referencedInterfaceConstant; private final List invokeMethodCallOffsets; + private static final Logger logger = LogManager.getLogger(BaseLambdaInliner.class); + public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { this.consumingClass = consumingClass; @@ -89,6 +93,7 @@ public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consuming * class was unable to inline the lambda into the method it will return null. */ public Method inline() { + logger.debug("Entered inline"); if (consumingMethod instanceof LibraryMethod) return null; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 619189f51..3198ef01e 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -2,19 +2,19 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.classfile.Clazz; -import proguard.classfile.attribute.CodeAttribute; -import proguard.optimize.inline.lambdalocator.LambdaLocator; import proguard.AppView; import proguard.classfile.AccessConstants; +import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.classfile.ProgramClass; +import proguard.classfile.attribute.CodeAttribute; import proguard.classfile.editor.CodeAttributeEditor; import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.InitializationUtil; import proguard.optimize.inline.lambdalocator.Lambda; +import proguard.optimize.inline.lambdalocator.LambdaLocator; import proguard.pass.Pass; import java.util.HashSet; @@ -42,6 +42,10 @@ public void execute(AppView appView) { LambdaLocator lambdaLocator = new LambdaLocator(appView.programClassPool, classNameFilter); for (Lambda lambda : lambdaLocator.getStaticLambdas()) { + logger.debug("Inlining : " + lambda); + logger.debug("Class : " + lambda.clazz().getName()); + logger.debug("Method : " + lambda.method().getName(lambda.clazz())); + logger.debug("Descriptor : " + lambda.method().getDescriptor(lambda.clazz())); Set remainder = new HashSet<>(); inlinedAllUsages = true; InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); @@ -124,7 +128,7 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); - logger.info("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); + logger.debug("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); return true; } } diff --git a/base/src/main/java/proguard/optimize/inline/SourceTracer.java b/base/src/main/java/proguard/optimize/inline/SourceTracer.java index 914df4043..9e441e410 100644 --- a/base/src/main/java/proguard/optimize/inline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/inline/SourceTracer.java @@ -176,6 +176,8 @@ private static boolean isNotSourceInstruction(Instruction instruction) { instruction.opcode != Instruction.OP_GETSTATIC && instruction.opcode != Instruction.OP_INVOKESTATIC && instruction.opcode != Instruction.OP_INVOKEVIRTUAL && + instruction.opcode != Instruction.OP_INVOKESPECIAL && + instruction.opcode != Instruction.OP_INVOKEINTERFACE && instruction.opcode != Instruction.OP_GETFIELD; } } From 23d4efe2cc4be0f4bae54c7c616722aff4276ffd Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 11:19:09 +0200 Subject: [PATCH 54/72] Use library and program class pools instead of passing AppView --- .../optimize/inline/BaseLambdaInliner.java | 16 +++++++++------- .../proguard/optimize/inline/LambdaInliner.java | 13 ++++++++----- .../optimize/inline/ShortLambdaInliner.java | 5 +++-- 3 files changed, 20 insertions(+), 14 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 7b27ebef3..65ff5d70b 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -1,7 +1,7 @@ package proguard.optimize.inline; -import proguard.AppView; import proguard.classfile.AccessConstants; +import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.LibraryMethod; import proguard.classfile.Member; @@ -50,7 +50,8 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { private final Clazz consumingClass; private final Method consumingMethod; - private final AppView appView; + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; private final Lambda lambda; private final CodeAttributeEditor codeAttributeEditor; private final boolean isStatic; @@ -66,10 +67,11 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVis private InterfaceMethodrefConstant referencedInterfaceConstant; private final List invokeMethodCallOffsets; - public BaseLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { + public BaseLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { this.consumingClass = consumingClass; this.consumingMethod = consumingMethod; - this.appView = appView; + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; this.lambda = lambda; this.codeAttributeEditor = new CodeAttributeEditor(); this.isStatic = (consumingMethod.getAccessFlags() & AccessConstants.STATIC) != 0; @@ -178,8 +180,8 @@ public void visitAnyMember(Clazz clazz, Member member) { staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); // Important for inlining, we need this so that method invocations have non-null referenced methods. - appView.programClassPool.classesAccept( - new ClassReferenceInitializer(appView.programClassPool, appView.libraryClassPool) + programClassPool.classesAccept( + new ClassReferenceInitializer(programClassPool, libraryClassPool) ); // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. @@ -224,7 +226,7 @@ public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); } }); - appView.programClassPool.classesAccept(new AccessFixer()); + programClassPool.classesAccept(new AccessFixer()); // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace // calls to the old function to calls to this new function. diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 20c6808ff..f17ff7151 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -2,6 +2,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.attribute.CodeAttribute; import proguard.optimize.inline.lambdalocator.LambdaLocator; @@ -47,7 +48,7 @@ public void execute(AppView appView) { InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), new LambdaUsageFinder(lambda, lambdaLocator.getKotlinLambdaMap(), - new LambdaUsageHandler(appView, remainder) + new LambdaUsageHandler(appView.programClassPool, appView.libraryClassPool, remainder) ) ); @@ -70,17 +71,19 @@ public void execute(AppView appView) { public class LambdaUsageHandler { - private final AppView appView; + private final ClassPool programClassPool; + private final ClassPool libraryClassPool; private final Set remainder; - public LambdaUsageHandler(AppView appView, Set remainder) { - this.appView = appView; + public LambdaUsageHandler(ClassPool programClassPool, ClassPool libraryClassPool, Set remainder) { + this.programClassPool = programClassPool; + this.libraryClassPool = libraryClassPool; this.remainder = remainder; } boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int lambdaArgumentIndex, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { // Try inlining the lambda in consumingMethod - BaseLambdaInliner baseLambdaInliner = new ShortLambdaInliner(appView, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); + BaseLambdaInliner baseLambdaInliner = new ShortLambdaInliner(programClassPool, libraryClassPool, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); Method inlinedLambamethod = baseLambdaInliner.inline(); // We didn't inline anything so no need to change any call instructions. diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java index 8f26b9d97..97fe8c136 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java @@ -3,6 +3,7 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import proguard.AppView; +import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.Method; import proguard.optimize.inline.lambdalocator.Lambda; @@ -20,8 +21,8 @@ public class ShortLambdaInliner extends BaseLambdaInliner { private static final int MAXIMUM_CONSUMING_METHOD_LENGTH = 2000; private static final int MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH = 64; - public ShortLambdaInliner(AppView appView, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { - super(appView, consumingClass, consumingMethod, calledLambdaIndex, lambda); + public ShortLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { + super(programClassPool, libraryClassPool, consumingClass, consumingMethod, calledLambdaIndex, lambda); } @Override From 03ae5c69736a689e63c762bd268b8afae23eb7f3 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 11:34:40 +0200 Subject: [PATCH 55/72] Made one of the debug log messages info again because it was accidentally changed --- base/src/main/java/proguard/optimize/inline/LambdaInliner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java index 0ef06a655..b51d257d2 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/LambdaInliner.java @@ -131,7 +131,7 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); - logger.debug("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); + logger.info("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); return true; } } From ad8b5ff2d2f3b157818fbc710c5d2ff4de7b30cd Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 12:34:36 +0200 Subject: [PATCH 56/72] Add large javadoc comment explaining all the steps taken when using the BaseLambdaInliner.inline() method --- .../optimize/inline/BaseLambdaInliner.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 004dd6c9f..4cfb9a726 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -91,6 +91,41 @@ public BaseLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, * replace the call instruction with a call to this newly created function. This way we don't need to think about * how it is called exactly, it can be used as a higher abstraction in other places such as in the recursive * inliner. + *

+ * The inlining uses the following steps: + *

    + *
  • First we will try to find the invoke method that implements this Kotlin lambda. + *
  • We check if we should try inline this based on the condition defined in shouldInline. This could be for + * example, only inline lambdas of a certain length. + *
  • We will copy this invoke method into the consuming class. + *
  • Then we will make it static and change the descriptor to have a fake this parameter, we need + * this parameter because otherwise aload_0 would load the wrong thing, it would load the first actual + * argument instead of this because it is now static. + *
  • We will then make a copy of the consuming method because we will modify it to have this lambda inlined and + * the argument removed. + *
  • Then we check if the consuming method uses the lambda to call another method that also consumes the lambda + * in this slimmed down implementation we just abort the inlining process in that case. + *
  • Then we will try find all calls to the previously mentioned invoke method and replace them with calls to our + * new static invoke method. + *
  • Then we do cast removal, we remove the casts before and after the invoke call, these were previously needed + * because a bridge method was used, and now we use the non type-erased invoke method. We also remove a cast at the + * end of this invoke method if it is present. This cast boxes primitive types. + *
  • After doing that we can use the MethodInliner to actually inline the implementation itself. + *
  • We will then delete this static invoke method, we no longer need it. + *
  • The consuming method has the lambda inlined now but still has the lambda in its arguments, we will remove + * this argument now. + *
  • We first remove the null check that was present for this argument, we no longer need it. Then we will edit + * the descriptor of the consuming method to get rid of the lambda argument. + *
  • With the argument gone the load and store instructions use the wrong indices, we will shift the indices that + * are higher than the lambda index down so that it is correct again. The aload_x instructions with + * x = the lambda + * index will be replaced with aconst_null or if this lambda was nullable, with a getstatic + * to the lambda class instance. The latter is not ideal but nullable lambdas are uncommon. Future optimisation is + * possible here. The usage of the getstatic to obtain a reference to this field might not be allowed + * here, so we use the AccessFixer to make it possible. + *
  • The lambda is now fully inlined into this copy of the consuming method and is returned to the caller. + *
+ * * * @return Returns a new method which has the lambda inlined in it, the lambda argument is also removed. If this * class was unable to inline the lambda into the method it will return null. From 26d98506a9747b69c34058280788c9d866c3edb3 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 12:38:55 +0200 Subject: [PATCH 57/72] Added comment explaining why descriptor is changed to use unboxed types --- .../main/java/proguard/optimize/inline/BaseLambdaInliner.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 4cfb9a726..02c1e4a54 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -179,6 +179,8 @@ public void visitAnyMember(Clazz clazz, Member member) { // The method becomes static String modifiedDescriptor = originalDescriptor.replace("(", "(Ljava/lang/Object;"); // Change return type if it has an effect on the stack size + // We will later remove the cast at the end of the invoke method that boxes primitive types, so we + // change the descriptor here accordingly. modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Double;", ")D"); modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Float;", ")F"); return modifiedDescriptor.replace(")Ljava/lang/Long;", ")J"); From a5ebb5eb106fe07466e82b3739fc2f5d4740227e Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 14:30:22 +0200 Subject: [PATCH 58/72] Move a lot of code from the BaseLambdaInliner into a private inner class because the visitors are not part of the API of the BaseLambdaInliner --- .../optimize/inline/BaseLambdaInliner.java | 270 +++++++++--------- 1 file changed, 135 insertions(+), 135 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 02c1e4a54..650c71a86 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -1,12 +1,9 @@ package proguard.optimize.inline; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.LibraryMethod; -import proguard.classfile.Member; import proguard.classfile.Method; import proguard.classfile.ProgramClass; import proguard.classfile.ProgramMethod; @@ -49,7 +46,7 @@ * caller will still need to replace the call instruction and handle removing arguments provided to the original call * instruction if this class manages to inline the lambda. */ -public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { +public abstract class BaseLambdaInliner { private final Clazz consumingClass; private final Method consumingMethod; private final ClassPool programClassPool; @@ -68,8 +65,6 @@ public abstract class BaseLambdaInliner implements MemberVisitor, InstructionVis private Method staticInvokeMethod; private InterfaceMethodrefConstant referencedInterfaceConstant; private final List invokeMethodCallOffsets; - private static final Logger logger = LogManager.getLogger(BaseLambdaInliner.class); - public BaseLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { this.consumingClass = consumingClass; @@ -145,8 +140,9 @@ public ProgramMethod inline() { return; } - // First we copy the method from the anonymous lambda class into the class where it is used. - MethodCopier copier = new MethodCopier(programClass, programMethod, BaseLambdaInliner.this); + // First we copy the method from the anonymous lambda class into the class where it is used. We will then + // call the inner class InvokeMethodInliner to inline this copied invoke method. + MethodCopier copier = new MethodCopier(programClass, programMethod, new InvokeMethodInliner()); consumingClass.accept(copier); })); @@ -165,17 +161,20 @@ public ProgramMethod inline() { */ protected abstract boolean shouldInline(Clazz consumingClass, Method consumingMethod, Clazz lambdaClass, Method lambdaImplMethod); - @Override - public void visitAnyMember(Clazz clazz, Member member) { - ProgramMethod method = (ProgramMethod) member; - method.u2accessFlags = (method.getAccessFlags() & ~AccessConstants.BRIDGE & ~AccessConstants.SYNTHETIC & ~AccessConstants.PRIVATE) - | AccessConstants.STATIC; - - // Copy the invoke method - String invokeMethodDescriptor = method.getDescriptor(consumingClass); - DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); - staticInvokeMethod = descriptorModifier.modify(method, - originalDescriptor -> { + /** + * A private inner class that hides all the visitors form the main BaseLambdaInliner API. It takes the copied invoke + * method in the visitAnyMember method and it inlines that invoke method into the consuming method. + */ + private class InvokeMethodInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { + @Override + public void visitProgramMethod(ProgramClass programClass, ProgramMethod method) { + method.u2accessFlags = (method.getAccessFlags() & ~AccessConstants.BRIDGE & ~AccessConstants.SYNTHETIC & ~AccessConstants.PRIVATE) + | AccessConstants.STATIC; + + // Copy the invoke method + String invokeMethodDescriptor = method.getDescriptor(consumingClass); + DescriptorModifier descriptorModifier = new DescriptorModifier(consumingClass); + staticInvokeMethod = descriptorModifier.modify(method, originalDescriptor -> { // The method becomes static String modifiedDescriptor = originalDescriptor.replace("(", "(Ljava/lang/Object;"); // Change return type if it has an effect on the stack size @@ -184,125 +183,140 @@ public void visitAnyMember(Clazz clazz, Member member) { modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Double;", ")D"); modifiedDescriptor = modifiedDescriptor.replace(")Ljava/lang/Float;", ")F"); return modifiedDescriptor.replace(")Ljava/lang/Long;", ")J"); - } - , true); + }, true); - ProgramMethod copiedConsumingMethod = descriptorModifier.modify((ProgramMethod) consumingMethod, descriptor -> descriptor); + ProgramMethod copiedConsumingMethod = descriptorModifier.modify((ProgramMethod) consumingMethod, descriptor -> descriptor); - // Don't inline if the lamdba is passed to another method - try { - copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(calledLambdaIndex)); - } catch (CannotInlineException cie) { - ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); - classEditor.removeMethod(copiedConsumingMethod); - classEditor.removeMethod(staticInvokeMethod); - return; - } + // Don't inline if the lamdba is passed to another method + try { + copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(calledLambdaIndex)); + } catch (CannotInlineException cie) { + ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); + classEditor.removeMethod(copiedConsumingMethod); + classEditor.removeMethod(staticInvokeMethod); + return; + } - referencedInterfaceConstant = null; - invokeMethodCallOffsets.clear(); + referencedInterfaceConstant = null; + invokeMethodCallOffsets.clear(); - // Replace invokeinterface call to invoke method with invokestatic to staticInvokeMethod - Optional consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, copiedConsumingMethod); - assert consumingMethodLength.isPresent(); - codeAttributeEditor.reset(consumingMethodLength.get()); - copiedConsumingMethod.accept(consumingClass, + // Replace invokeinterface call to invoke method with invokestatic to staticInvokeMethod + Optional consumingMethodLength = MethodLengthFinder.getMethodCodeLength(consumingClass, copiedConsumingMethod); + assert consumingMethodLength.isPresent(); + codeAttributeEditor.reset(consumingMethodLength.get()); + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor( new AllInstructionVisitor( - new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this)))); + new InstructionOpCodeFilter(new int[] { Instruction.OP_INVOKEINTERFACE }, this) + ))); - if (referencedInterfaceConstant != null) { - // Remove casting before and after invoke method call - // Uses same codeAttributeEditor as LambdaInvokeReplacer - copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PrePostCastRemover(invokeMethodDescriptor))); - } + if (referencedInterfaceConstant != null) { + // Remove casting before and after invoke method call + // Uses same codeAttributeEditor as LambdaInvokeReplacer + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PrePostCastRemover(invokeMethodDescriptor))); + } - // Remove return value's casting from staticInvokeMethod. - staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); + // Remove return value's casting from staticInvokeMethod. + staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); - // Important for inlining, we need this so that method invocations have non-null referenced methods. - programClassPool.classesAccept( - new ClassReferenceInitializer(programClassPool, libraryClassPool) - ); + // Important for inlining, we need this so that method invocations have non-null referenced methods. + programClassPool.classesAccept( + new ClassReferenceInitializer(programClassPool, libraryClassPool) + ); - // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. - inlineMethodInClass(consumingClass, staticInvokeMethod); + // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. + inlineMethodInClass(consumingClass, staticInvokeMethod); - // Remove the static invoke method once it has been inlined. - ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); - classEditor.removeMethod(staticInvokeMethod); + // Remove the static invoke method once it has been inlined. + ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); + classEditor.removeMethod(staticInvokeMethod); - // Remove checkNotNullParameter() call because arguments that are lambdas will be removed. - InstructionCounter removedNullCheckInstrCounter = new InstructionCounter(); - copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLambdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); + // Remove checkNotNullParameter() call because arguments that are lambdas will be removed. + InstructionCounter removedNullCheckInstrCounter = new InstructionCounter(); + copiedConsumingMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new NullCheckRemover(sizeAdjustedLambdaIndex, codeAttributeEditor, removedNullCheckInstrCounter)))); - // Remove inlined lambda from arguments through the descriptor. - ProgramMethod methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { - List list = new ArrayList<>(); - InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(desc); - while (internalTypeEnumeration.hasMoreTypes()) { - list.add(internalTypeEnumeration.nextType()); - } - // We adjust the index to not take the "this" parameter into account because this is not visible in the - // method descriptor. - list.remove(calledLambdaIndex - (isStatic ? 0 : 1)); - - return ClassUtil.internalMethodDescriptorFromInternalTypes(internalTypeEnumeration.returnType(), list); - }, true); - - - // Remove one of the arguments - lambda.clazz().constantPoolEntryAccept(lambda.getstaticInstruction().constantIndex, new ConstantVisitor() { - @Override - public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { - ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); - int lambdaInstanceFieldIndex = constantPoolEditor.addFieldrefConstant(fieldrefConstant.referencedClass, fieldrefConstant.referencedField); - - // If the argument had a null check removed then it was non-nullable, so we can replace usages with null - // just fine because no one will ever null check on it. Even if the programmer did do a null check on it - // the kotlin compiler will remove it. - Instruction replacementInstruction = removedNullCheckInstrCounter.getCount() != 0 ? - new VariableInstruction(Instruction.OP_ACONST_NULL) : - new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); - methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); - } - }); - programClassPool.classesAccept(new AccessFixer()); + // Remove inlined lambda from arguments through the descriptor. + ProgramMethod methodWithoutLambdaParameter = descriptorModifier.modify(copiedConsumingMethod, desc -> { + List list = new ArrayList<>(); + InternalTypeEnumeration internalTypeEnumeration = new InternalTypeEnumeration(desc); + while (internalTypeEnumeration.hasMoreTypes()) { + list.add(internalTypeEnumeration.nextType()); + } + // We adjust the index to not take the "this" parameter into account because this is not visible in the + // method descriptor. + list.remove(calledLambdaIndex - (isStatic ? 0 : 1)); + + return ClassUtil.internalMethodDescriptorFromInternalTypes(internalTypeEnumeration.returnType(), list); + }, true); + + + // Remove one of the arguments + lambda.clazz().constantPoolEntryAccept(lambda.getstaticInstruction().constantIndex, new ConstantVisitor() { + @Override + public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { + ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); + int lambdaInstanceFieldIndex = constantPoolEditor.addFieldrefConstant(fieldrefConstant.referencedClass, fieldrefConstant.referencedField); + + // If the argument had a null check removed then it was non-nullable, so we can replace usages with null + // just fine because no one will ever null check on it. Even if the programmer did do a null check on it + // the kotlin compiler will remove it. + Instruction replacementInstruction = removedNullCheckInstrCounter.getCount() != 0 ? + new VariableInstruction(Instruction.OP_ACONST_NULL) : + new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); + methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); + } + }); + programClassPool.classesAccept(new AccessFixer()); - // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace - // calls to the old function to calls to this new function. - inlinedLambdaMethod = methodWithoutLambdaParameter; - } + // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace + // calls to the old function to calls to this new function. + inlinedLambdaMethod = methodWithoutLambdaParameter; + } - @Override - public void visitConstantInstruction(Clazz consumingClass, Method consumingMethod, CodeAttribute consumingMethodCodeAttribute, int consumingMethodCallOffset, ConstantInstruction constantInstruction) { - consumingClass.constantPoolEntryAccept(constantInstruction.constantIndex, this); - Method invokeInterfaceMethod = referencedInterfaceConstant.referencedMethod; - Clazz invokeInterfaceClass = referencedInterfaceConstant.referencedClass; - - // Check if this is an invokeinterface call to the lambda class invoke method - if (invokeInterfaceClass.getName().equals(interfaceClass.getName()) && - invokeInterfaceMethod.getName(invokeInterfaceClass).equals(lambdaInvokeMethod.getName(lambdaClass)) && - invokeInterfaceMethod.getDescriptor(invokeInterfaceClass).equals(bridgeDescriptor) - ) { - partialEvaluator.visitCodeAttribute(consumingClass, consumingMethod, consumingMethodCodeAttribute); - TracedStack tracedStack = partialEvaluator.getStackBefore(consumingMethodCallOffset); - - // If we have a lambda with n arguments we have to skip those n arguments and then get the instance of the lambda used for calling invokeinterface. - int lambdaInstanceStackIndex = ClassUtil.internalMethodParameterCount(invokeInterfaceMethod.getDescriptor(invokeInterfaceClass)); - - consumingMethodCodeAttribute.instructionAccept( - consumingClass, - consumingMethod, - tracedStack.getTopActualProducerValue(lambdaInstanceStackIndex).instructionOffsetValue().instructionOffset(0), - new LambdaInvokeUsageReplacer(consumingMethodCallOffset) - ); + @Override + public void visitConstantInstruction(Clazz consumingClass, Method consumingMethod, CodeAttribute consumingMethodCodeAttribute, int consumingMethodCallOffset, ConstantInstruction constantInstruction) { + consumingClass.constantPoolEntryAccept(constantInstruction.constantIndex, this); + Method invokeInterfaceMethod = referencedInterfaceConstant.referencedMethod; + Clazz invokeInterfaceClass = referencedInterfaceConstant.referencedClass; + + // Check if this is an invokeinterface call to the lambda class invoke method + if (invokeInterfaceClass.getName().equals(interfaceClass.getName()) && + invokeInterfaceMethod.getName(invokeInterfaceClass).equals(lambdaInvokeMethod.getName(lambdaClass)) && + invokeInterfaceMethod.getDescriptor(invokeInterfaceClass).equals(bridgeDescriptor) + ) { + partialEvaluator.visitCodeAttribute(consumingClass, consumingMethod, consumingMethodCodeAttribute); + TracedStack tracedStack = partialEvaluator.getStackBefore(consumingMethodCallOffset); + + // If we have a lambda with n arguments we have to skip those n arguments and then get the instance of the lambda used for calling invokeinterface. + int lambdaInstanceStackIndex = ClassUtil.internalMethodParameterCount(invokeInterfaceMethod.getDescriptor(invokeInterfaceClass)); + + consumingMethodCodeAttribute.instructionAccept( + consumingClass, + consumingMethod, + tracedStack.getTopActualProducerValue(lambdaInstanceStackIndex).instructionOffsetValue().instructionOffset(0), + new LambdaInvokeUsageReplacer(consumingMethodCallOffset) + ); + } + } + + @Override + public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { + referencedInterfaceConstant = interfaceMethodrefConstant; } - } - @Override - public void visitInterfaceMethodrefConstant(Clazz clazz, InterfaceMethodrefConstant interfaceMethodrefConstant) { - referencedInterfaceConstant = interfaceMethodrefConstant; + /** + * Inline a specified method in the locations it is called in the current clazz. + * @param clazz The clazz in which we want to inline the targetMethod + * @param targetMethod The method we want to inline. + */ + private void inlineMethodInClass(Clazz clazz, Method targetMethod) { + clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, true, false, null) { + @Override + protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { + return method.equals(targetMethod); + } + })); + } } private class LambdaInvokeUsageReplacer implements InstructionVisitor { @@ -386,18 +400,4 @@ public void visitCodeAttribute(Clazz clazz, Method method, CodeAttribute codeAtt @Override public void visitAnyAttribute(Clazz clazz, Attribute attribute) {} } - - /** - * Inline a specified method in the locations it is called in the current clazz. - * @param clazz The clazz in which we want to inline the targetMethod - * @param targetMethod The method we want to inline. - */ - private void inlineMethodInClass(Clazz clazz, Method targetMethod) { - clazz.methodsAccept(new AllAttributeVisitor(new MethodInliner(false, false, true, false, null) { - @Override - protected boolean shouldInline(Clazz clazz, Method method, CodeAttribute codeAttribute) { - return method.equals(targetMethod); - } - })); - } } From 71b63a4c4ea9239fbc788fabafebd05e3fc2f589 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 14:42:39 +0200 Subject: [PATCH 59/72] Fixed incorrect comment mentioning method that no longer exists --- .../main/java/proguard/optimize/inline/BaseLambdaInliner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 650c71a86..8337bd2dd 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -163,7 +163,7 @@ public ProgramMethod inline() { /** * A private inner class that hides all the visitors form the main BaseLambdaInliner API. It takes the copied invoke - * method in the visitAnyMember method and it inlines that invoke method into the consuming method. + * method in the visitProgramMethod method and it inlines that invoke method into the consuming method. */ private class InvokeMethodInliner implements MemberVisitor, InstructionVisitor, ConstantVisitor { @Override From 7da8e01d929bad8f45b280de24998d90c338b77d Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 17:46:42 +0200 Subject: [PATCH 60/72] Disable replacing usages with aconst_null --- .../optimize/inline/BaseLambdaInliner.java | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java index 8337bd2dd..47dddb83b 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java @@ -116,9 +116,10 @@ public BaseLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, * x = the lambda * index will be replaced with aconst_null or if this lambda was nullable, with a getstatic * to the lambda class instance. The latter is not ideal but nullable lambdas are uncommon. Future optimisation is - * possible here. The usage of the getstatic to obtain a reference to this field might not be allowed - * here, so we use the AccessFixer to make it possible. - *
  • The lambda is now fully inlined into this copy of the consuming method and is returned to the caller. + * possible here. Because of safety concerns replacing with aconst_null has been disabled currently. + * The usage of the getstatic to obtain a reference to this field might not be allowed here, so we use + * the AccessFixer to make it possible.
  • The lambda is now fully inlined into this copy of the + * consuming method and is returned to the caller. * * * @@ -256,13 +257,7 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod method) public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant) { ConstantPoolEditor constantPoolEditor = new ConstantPoolEditor((ProgramClass) consumingClass); int lambdaInstanceFieldIndex = constantPoolEditor.addFieldrefConstant(fieldrefConstant.referencedClass, fieldrefConstant.referencedField); - - // If the argument had a null check removed then it was non-nullable, so we can replace usages with null - // just fine because no one will ever null check on it. Even if the programmer did do a null check on it - // the kotlin compiler will remove it. - Instruction replacementInstruction = removedNullCheckInstrCounter.getCount() != 0 ? - new VariableInstruction(Instruction.OP_ACONST_NULL) : - new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); + Instruction replacementInstruction = new ConstantInstruction(Instruction.OP_GETSTATIC, lambdaInstanceFieldIndex); methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); } }); From 751cc7455ba8e513db5fa141acc8af0aa5ebfcf6 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 18:15:01 +0200 Subject: [PATCH 61/72] Add -inlinekotlinlambdas command line option to enable and disable lambda inlining without recompiling --- base/src/main/java/proguard/ConfigurationConstants.java | 1 + base/src/main/java/proguard/ConfigurationParser.java | 1 + 2 files changed, 2 insertions(+) diff --git a/base/src/main/java/proguard/ConfigurationConstants.java b/base/src/main/java/proguard/ConfigurationConstants.java index 121a911d9..631de60e4 100644 --- a/base/src/main/java/proguard/ConfigurationConstants.java +++ b/base/src/main/java/proguard/ConfigurationConstants.java @@ -114,6 +114,7 @@ public class ConfigurationConstants public static final String KEEP_KOTLIN_METADATA = "-keepkotlinmetadata"; public static final String DONT_PROCESS_KOTLIN_METADATA = "-dontprocesskotlinmetadata"; + public static final String INLINE_KOTLIN_LAMBDAS = "-inlinekotlinlambdas"; public static final String OPTIMIZE_AGGRESSIVELY = "-optimizeaggressively"; public static final String ANY_FILE_KEYWORD = "**"; diff --git a/base/src/main/java/proguard/ConfigurationParser.java b/base/src/main/java/proguard/ConfigurationParser.java index efa825d32..da396e0b8 100644 --- a/base/src/main/java/proguard/ConfigurationParser.java +++ b/base/src/main/java/proguard/ConfigurationParser.java @@ -212,6 +212,7 @@ else if (ConfigurationConstants.REPACKAGE_CLASSES_OPTION else if (ConfigurationConstants.ADAPT_RESOURCE_FILE_CONTENTS_OPTION .startsWith(nextWord)) configuration.adaptResourceFileContents = parseCommaSeparatedList("resource file name", true, true, false, true, false, true, false, false, false, configuration.adaptResourceFileContents); else if (ConfigurationConstants.DONT_PROCESS_KOTLIN_METADATA .startsWith(nextWord)) configuration.dontProcessKotlinMetadata = parseNoArgument(true); else if (ConfigurationConstants.KEEP_KOTLIN_METADATA .startsWith(nextWord)) configuration.keepKotlinMetadata = parseKeepKotlinMetadata(); + else if (ConfigurationConstants.INLINE_KOTLIN_LAMBDAS .startsWith(nextWord)) configuration.lambdaInlining = parseNoArgument(true); else if (ConfigurationConstants.DONT_PREVERIFY_OPTION .startsWith(nextWord)) configuration.preverify = parseNoArgument(false); else if (ConfigurationConstants.MICRO_EDITION_OPTION .startsWith(nextWord)) configuration.microEdition = parseNoArgument(true); From f4f84e4d1706b9433db13a33af2e36ae473803f6 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 18:20:01 +0200 Subject: [PATCH 62/72] Moved lambda inlining optimization from inline to lambdainline package so it matches the package name used in the tests and it also is just more informative, the MethodInliner for example was not in there --- base/src/main/java/proguard/ProGuard.java | 2 +- .../{inline => lambdainline}/BaseLambdaInliner.java | 4 ++-- .../{inline => lambdainline}/CannotInlineException.java | 2 +- .../{inline => lambdainline}/CastPatternRemover.java | 2 +- .../optimize/{inline => lambdainline}/CastRemover.java | 2 +- .../{inline => lambdainline}/DescriptorModifier.java | 2 +- .../FixedPointCodeAttributeVisitor.java | 2 +- .../{inline => lambdainline}/InstructionAtOffset.java | 2 +- .../LambdaImplementationVisitor.java | 2 +- .../optimize/{inline => lambdainline}/LambdaInliner.java | 6 +++--- .../{inline => lambdainline}/LambdaUsageFinder.java | 4 ++-- .../{inline => lambdainline}/LocalUsageRemover.java | 2 +- .../{inline => lambdainline}/MethodLengthFinder.java | 2 +- .../proguard/optimize/{inline => lambdainline}/Node.java | 2 +- .../{inline => lambdainline}/NullCheckRemover.java | 2 +- .../{inline => lambdainline}/RecursiveInliner.java | 3 ++- .../optimize/{inline => lambdainline}/RefMethodFinder.java | 2 +- .../{inline => lambdainline}/ShortLambdaInliner.java | 7 +++---- .../optimize/{inline => lambdainline}/SourceTracer.java | 2 +- .../{inline => lambdainline}/lambdalocator/Lambda.java | 2 +- .../lambdalocator/LambdaLocator.java | 2 +- base/src/test/kotlin/proguard/lambdainline/TestUtil.kt | 2 +- 22 files changed, 29 insertions(+), 29 deletions(-) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/BaseLambdaInliner.java (99%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/CannotInlineException.java (89%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/CastPatternRemover.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/CastRemover.java (99%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/DescriptorModifier.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/FixedPointCodeAttributeVisitor.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/InstructionAtOffset.java (93%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/LambdaImplementationVisitor.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/LambdaInliner.java (97%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/LambdaUsageFinder.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/LocalUsageRemover.java (99%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/MethodLengthFinder.java (97%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/Node.java (94%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/NullCheckRemover.java (99%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/RecursiveInliner.java (98%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/RefMethodFinder.java (96%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/ShortLambdaInliner.java (95%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/SourceTracer.java (99%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/lambdalocator/Lambda.java (97%) rename base/src/main/java/proguard/optimize/{inline => lambdainline}/lambdalocator/LambdaLocator.java (99%) diff --git a/base/src/main/java/proguard/ProGuard.java b/base/src/main/java/proguard/ProGuard.java index 58dbdd0f2..952078be4 100644 --- a/base/src/main/java/proguard/ProGuard.java +++ b/base/src/main/java/proguard/ProGuard.java @@ -38,7 +38,7 @@ import proguard.optimize.LineNumberTrimmer; import proguard.optimize.Optimizer; import proguard.optimize.gson.GsonOptimizer; -import proguard.optimize.inline.LambdaInliner; +import proguard.optimize.lambdainline.LambdaInliner; import proguard.optimize.peephole.LineNumberLinearizer; import proguard.pass.PassRunner; import proguard.preverify.PreverificationClearer; diff --git a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java rename to base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java index 47dddb83b..fac411c0b 100644 --- a/base/src/main/java/proguard/optimize/inline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.AccessConstants; import proguard.classfile.ClassPool; @@ -34,7 +34,7 @@ import proguard.classfile.visitor.MemberVisitor; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; -import proguard.optimize.inline.lambdalocator.Lambda; +import proguard.optimize.lambdainline.lambdalocator.Lambda; import proguard.optimize.peephole.MethodInliner; import java.util.ArrayList; diff --git a/base/src/main/java/proguard/optimize/inline/CannotInlineException.java b/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java similarity index 89% rename from base/src/main/java/proguard/optimize/inline/CannotInlineException.java rename to base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java index cac10ea9a..b00e58a32 100644 --- a/base/src/main/java/proguard/optimize/inline/CannotInlineException.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; /** * An exception thrown by the RecursiveInliner that is caught by the BaseLambdaInliner, this allows it to jump out of diff --git a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java b/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/CastPatternRemover.java rename to base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java index 2866080f0..5ad7eea06 100644 --- a/base/src/main/java/proguard/optimize/inline/CastPatternRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/base/src/main/java/proguard/optimize/inline/CastRemover.java b/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/CastRemover.java rename to base/src/main/java/proguard/optimize/lambdainline/CastRemover.java index a55d910b2..1e779a50c 100644 --- a/base/src/main/java/proguard/optimize/inline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java b/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/DescriptorModifier.java rename to base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java index 1266e8c94..d781c8f79 100644 --- a/base/src/main/java/proguard/optimize/inline/DescriptorModifier.java +++ b/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.ProgramClass; diff --git a/base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java b/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java rename to base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java index f89603a74..fbee2a563 100644 --- a/base/src/main/java/proguard/optimize/inline/FixedPointCodeAttributeVisitor.java +++ b/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java b/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java similarity index 93% rename from base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java rename to base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java index b97f95e1c..800b882cf 100644 --- a/base/src/main/java/proguard/optimize/inline/InstructionAtOffset.java +++ b/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.instruction.Instruction; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java rename to base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java index 596bddca5..7ed64bd95 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaImplementationVisitor.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.AccessConstants; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java similarity index 97% rename from base/src/main/java/proguard/optimize/inline/LambdaInliner.java rename to base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java index b51d257d2..42730e013 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -14,8 +14,8 @@ import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; import proguard.classfile.util.InitializationUtil; -import proguard.optimize.inline.lambdalocator.Lambda; -import proguard.optimize.inline.lambdalocator.LambdaLocator; +import proguard.optimize.lambdainline.lambdalocator.Lambda; +import proguard.optimize.lambdainline.lambdalocator.LambdaLocator; import proguard.pass.Pass; import java.util.HashSet; diff --git a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java rename to base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java index 35fd676b0..080b88019 100644 --- a/base/src/main/java/proguard/optimize/inline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -17,7 +17,7 @@ import proguard.classfile.util.ClassUtil; import proguard.evaluation.PartialEvaluator; import proguard.evaluation.TracedStack; -import proguard.optimize.inline.lambdalocator.Lambda; +import proguard.optimize.lambdainline.lambdalocator.Lambda; import java.util.ArrayList; import java.util.List; diff --git a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java rename to base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java index 60d9180b8..976e6a4af 100644 --- a/base/src/main/java/proguard/optimize/inline/LocalUsageRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java b/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java similarity index 97% rename from base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java rename to base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java index d76017e4b..fd9ccca63 100644 --- a/base/src/main/java/proguard/optimize/inline/MethodLengthFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/Node.java b/base/src/main/java/proguard/optimize/lambdainline/Node.java similarity index 94% rename from base/src/main/java/proguard/optimize/inline/Node.java rename to base/src/main/java/proguard/optimize/lambdainline/Node.java index d0d1e3c72..9ec067ea6 100644 --- a/base/src/main/java/proguard/optimize/inline/Node.java +++ b/base/src/main/java/proguard/optimize/lambdainline/Node.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import java.util.List; diff --git a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/NullCheckRemover.java rename to base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java index c20a22a44..ca61eb469 100644 --- a/base/src/main/java/proguard/optimize/inline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java similarity index 98% rename from base/src/main/java/proguard/optimize/inline/RecursiveInliner.java rename to base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java index 0f564f39e..e1078c6ab 100644 --- a/base/src/main/java/proguard/optimize/inline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -100,6 +100,7 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, true, lambdaConsumingMethodArgIndex); if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { + logger.debug("Cannot inline lambdas into functions that call other functions that consume this lambda!"); throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); } } diff --git a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java similarity index 96% rename from base/src/main/java/proguard/optimize/inline/RefMethodFinder.java rename to base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java index daa53b868..6883835ed 100644 --- a/base/src/main/java/proguard/optimize/inline/RefMethodFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import proguard.classfile.Clazz; import proguard.classfile.Member; diff --git a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java similarity index 95% rename from base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java rename to base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java index 97fe8c136..38f860216 100644 --- a/base/src/main/java/proguard/optimize/inline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java @@ -1,17 +1,16 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import proguard.AppView; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; import proguard.classfile.Method; -import proguard.optimize.inline.lambdalocator.Lambda; +import proguard.optimize.lambdainline.lambdalocator.Lambda; import java.util.Optional; /** - * This class is an implementation of the {@link proguard.optimize.inline.BaseLambdaInliner BaseLambdaInliner } that + * This class is an implementation of the {@link proguard.optimize.lambdainline.BaseLambdaInliner BaseLambdaInliner } that * inlines lambdas depending on the length of the lambda implementation method and the length of the consuming method. * The length of the consuming method is taken into account because the consuming method will be copied when inlining * this lambda because one method can take multiple different lambdas as an input. diff --git a/base/src/main/java/proguard/optimize/inline/SourceTracer.java b/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/SourceTracer.java rename to base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java index 9e441e410..9dddf1e81 100644 --- a/base/src/main/java/proguard/optimize/inline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline; +package proguard.optimize.lambdainline; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java similarity index 97% rename from base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java rename to base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java index a69ba8f51..332dc7087 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline.lambdalocator; +package proguard.optimize.lambdainline.lambdalocator; import proguard.classfile.Clazz; import proguard.classfile.Method; diff --git a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java similarity index 99% rename from base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java rename to base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java index f6cfac082..4e18641d0 100644 --- a/base/src/main/java/proguard/optimize/inline/lambdalocator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java @@ -1,4 +1,4 @@ -package proguard.optimize.inline.lambdalocator; +package proguard.optimize.lambdainline.lambdalocator; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; diff --git a/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt b/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt index c4d92ba71..37df62462 100644 --- a/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt +++ b/base/src/test/kotlin/proguard/lambdainline/TestUtil.kt @@ -23,7 +23,7 @@ import proguard.io.* import proguard.io.util.IOUtil import proguard.optimize.info.ProgramClassOptimizationInfoSetter import proguard.optimize.info.ProgramMemberOptimizationInfoSetter -import proguard.optimize.inline.LambdaInliner +import proguard.optimize.lambdainline.LambdaInliner import proguard.optimize.peephole.LineNumberLinearizer import proguard.preverify.CodePreverifier import proguard.testutils.ClassPoolBuilder From 9d080b25720ee4ba718c627bb699c5775102f325 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Thu, 10 Aug 2023 18:26:00 +0200 Subject: [PATCH 63/72] Add CannotInlineException message to debug log --- .../proguard/optimize/lambdainline/BaseLambdaInliner.java | 5 +++++ .../proguard/optimize/lambdainline/RecursiveInliner.java | 1 - 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java index fac411c0b..f7cf306b4 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java @@ -1,5 +1,7 @@ package proguard.optimize.lambdainline; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import proguard.classfile.AccessConstants; import proguard.classfile.ClassPool; import proguard.classfile.Clazz; @@ -47,6 +49,7 @@ * instruction if this class manages to inline the lambda. */ public abstract class BaseLambdaInliner { + private static final Logger logger = LogManager.getLogger(); private final Clazz consumingClass; private final Method consumingMethod; private final ClassPool programClassPool; @@ -192,6 +195,8 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod method) try { copiedConsumingMethod.accept(consumingClass, new RecursiveInliner(calledLambdaIndex)); } catch (CannotInlineException cie) { + logger.debug("Aborting inlining this lambda into this method. Reason:"); + logger.debug(cie.getMessage()); ClassEditor classEditor = new ClassEditor((ProgramClass) consumingClass); classEditor.removeMethod(copiedConsumingMethod); classEditor.removeMethod(staticInvokeMethod); diff --git a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java index e1078c6ab..606522ccf 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java @@ -100,7 +100,6 @@ public void visitVariableInstruction(Clazz clazz, Method method, CodeAttribute c int sizeAdjustedLambdaIndex = ClassUtil.internalMethodVariableIndex(consumingMethodDescriptor, true, lambdaConsumingMethodArgIndex); if (variableSourceInstruction.variableIndex == sizeAdjustedLambdaIndex) { - logger.debug("Cannot inline lambdas into functions that call other functions that consume this lambda!"); throw new CannotInlineException("Cannot inline lambdas into functions that call other functions that consume this lambda!"); } } From b8fbcd087012731ac975eaba3de995771de36be4 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Fri, 11 Aug 2023 12:27:13 +0200 Subject: [PATCH 64/72] Add ProGuard header to every class --- .../lambdainline/BaseLambdaInliner.java | 20 +++++++++++++++++++ .../lambdainline/CannotInlineException.java | 20 +++++++++++++++++++ .../lambdainline/CastPatternRemover.java | 20 +++++++++++++++++++ .../optimize/lambdainline/CastRemover.java | 20 +++++++++++++++++++ .../lambdainline/DescriptorModifier.java | 20 +++++++++++++++++++ .../FixedPointCodeAttributeVisitor.java | 20 +++++++++++++++++++ .../lambdainline/InstructionAtOffset.java | 20 +++++++++++++++++++ .../LambdaImplementationVisitor.java | 20 +++++++++++++++++++ .../optimize/lambdainline/LambdaInliner.java | 20 +++++++++++++++++++ .../lambdainline/LambdaUsageFinder.java | 20 +++++++++++++++++++ .../lambdainline/LocalUsageRemover.java | 20 +++++++++++++++++++ .../lambdainline/MethodLengthFinder.java | 20 +++++++++++++++++++ .../proguard/optimize/lambdainline/Node.java | 20 +++++++++++++++++++ .../lambdainline/NullCheckRemover.java | 20 +++++++++++++++++++ .../lambdainline/RecursiveInliner.java | 20 +++++++++++++++++++ .../lambdainline/RefMethodFinder.java | 20 +++++++++++++++++++ .../lambdainline/ShortLambdaInliner.java | 20 +++++++++++++++++++ .../optimize/lambdainline/SourceTracer.java | 20 +++++++++++++++++++ .../lambdainline/lambdalocator/Lambda.java | 20 +++++++++++++++++++ .../lambdalocator/LambdaLocator.java | 20 +++++++++++++++++++ 20 files changed, 400 insertions(+) diff --git a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java index f7cf306b4..3aca64f54 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java b/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java index b00e58a32..175e595f0 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CannotInlineException.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; /** diff --git a/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java b/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java index 5ad7eea06..0a62a6870 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CastPatternRemover.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java b/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java index 1e779a50c..b1fe32918 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/CastRemover.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java b/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java index d781c8f79..5b5519b87 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java +++ b/base/src/main/java/proguard/optimize/lambdainline/DescriptorModifier.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java b/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java index fbee2a563..ef6aec8e8 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java +++ b/base/src/main/java/proguard/optimize/lambdainline/FixedPointCodeAttributeVisitor.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java b/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java index 800b882cf..b289197ff 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java +++ b/base/src/main/java/proguard/optimize/lambdainline/InstructionAtOffset.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.instruction.Instruction; diff --git a/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java index 7ed64bd95..6de27b7e0 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaImplementationVisitor.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.AccessConstants; diff --git a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java index 42730e013..149850759 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java index 080b88019..8a0d50e80 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaUsageFinder.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java b/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java index 976e6a4af..3a9125aea 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LocalUsageRemover.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java b/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java index fd9ccca63..5134978cd 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/MethodLengthFinder.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/Node.java b/base/src/main/java/proguard/optimize/lambdainline/Node.java index 9ec067ea6..6f363ce41 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/Node.java +++ b/base/src/main/java/proguard/optimize/lambdainline/Node.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import java.util.List; diff --git a/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java index ca61eb469..f92f03e78 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java index 606522ccf..df7d15e98 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java b/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java index 6883835ed..0c41f0380 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RefMethodFinder.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java index 38f860216..42c021e04 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java b/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java index 9dddf1e81..21faafc5c 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java +++ b/base/src/main/java/proguard/optimize/lambdainline/SourceTracer.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline; import org.apache.logging.log4j.LogManager; diff --git a/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java index 332dc7087..a0c8879e1 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java +++ b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/Lambda.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline.lambdalocator; import proguard.classfile.Clazz; diff --git a/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java index 4e18641d0..afc340fd1 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java +++ b/base/src/main/java/proguard/optimize/lambdainline/lambdalocator/LambdaLocator.java @@ -1,3 +1,23 @@ +/* + * ProGuard -- shrinking, optimization, obfuscation, and preverification + * of Java bytecode. + * + * Copyright (c) 2002-2020 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.lambdainline.lambdalocator; import org.apache.logging.log4j.LogManager; From 3a866fc83c0f8105bda4d1a9bee63a734b169389 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Fri, 11 Aug 2023 14:18:57 +0200 Subject: [PATCH 65/72] Added detection for the second null check method for parameters in Kotlin --- .../java/proguard/optimize/lambdainline/NullCheckRemover.java | 2 +- .../java/proguard/optimize/lambdainline/RecursiveInliner.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java index f92f03e78..eb071ae27 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java +++ b/base/src/main/java/proguard/optimize/lambdainline/NullCheckRemover.java @@ -84,7 +84,7 @@ public void visitMethodrefConstant(Clazz clazz, MethodrefConstant methodrefConst // Check if the called function matches kotlin.jvm.internal.Intrinsics#void checkNotNullParameter(java.lang.Object,java.lang.String) if ( methodrefConstant.getClassName(clazz).equals("kotlin/jvm/internal/Intrinsics") && - methodrefConstant.getName(clazz).equals("checkNotNullParameter") && + (methodrefConstant.getName(clazz).equals("checkNotNullParameter") || methodrefConstant.getName(clazz).equals("checkParameterIsNotNull")) && methodrefConstant.getType(clazz).equals("(Ljava/lang/Object;Ljava/lang/String;)V") ) { for (int insIndex = 0; insIndex < pattern.length; insIndex++) { diff --git a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java index df7d15e98..489f20230 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/RecursiveInliner.java @@ -212,8 +212,8 @@ private void handleCalledMethod(Clazz referencedClass, Method referencedMethod) * inlining because we will remove these later. */ if (referencedClass.getName().equals("kotlin/jvm/internal/Intrinsics") && - referencedMethod.getName(referencedClass).equals("checkNotNullParameter") && - referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) + (referencedMethod.getName(referencedClass).equals("checkNotNullParameter") || referencedMethod.getName(referencedClass).equals("checkParameterIsNotNull")) && + referencedMethod.getDescriptor(referencedClass).equals("(Ljava/lang/Object;Ljava/lang/String;)V")) return; String methodDescriptor = referencedMethod.getDescriptor(referencedClass); From a910309655fb401b4b5efc17072d669ecdb5c400 Mon Sep 17 00:00:00 2001 From: MaartenS Date: Fri, 11 Aug 2023 14:49:10 +0200 Subject: [PATCH 66/72] Added WIP README file to the lambdainline package (Location can maybe be changed not sure exactly where we should put it, putting it in the official documentation seems incorrect this is not really something a proguard user should know it's more of a developer oriented piece of documentation.) --- .../proguard/optimize/lambdainline/README.md | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 base/src/main/java/proguard/optimize/lambdainline/README.md diff --git a/base/src/main/java/proguard/optimize/lambdainline/README.md b/base/src/main/java/proguard/optimize/lambdainline/README.md new file mode 100644 index 000000000..8ddb48e6e --- /dev/null +++ b/base/src/main/java/proguard/optimize/lambdainline/README.md @@ -0,0 +1,168 @@ +# Lambda inlining step by step +## Terminology +- The lambda = A Kotlin lambda that is implemented as a class that uses the singleton pattern. The class contains an `INSTANCE` field that is initialized in the static initializer of the class. Lambda classes inherit from `kotlin.jvm.internal.Lambda`. Every lambda class has an invoke method that depends on the `kotlin.jvm.functions.Function` interface they implement. This could be for example `kotlin.jvm.functions.Function2` which is a lambda that takes 2 arguments and returns something. +- Consuming method = The method that takes a lambda as an argument, when inlined this method will loose this lambda argument. +- Consuming call method = The method that calls the consuming method. This method will also contain the `getstatic` instruction that obtains a reference to the lambda. +- Invoke method = lambda implementation method = This method is a method in the class that implements the lambda. The method contains the actual implementation of the lambda written by the programmer. +- The consuming method, the consuming call method and the invoke method are all modified during the inlining process. In the sections below each method is shown individually with it's changes. +## Changes to the consuming method +### Starting point +This is the original consuming method without any changes. As you can see in the arguments, this method takes a lambda as it's first argument. + +Consuming method = `test(Lkotlin/jvm/functions/Function1;)V` +``` +[0] aload_0 v0 +[1] ldc #10 = String("a") +[3] invokestatic #16 = Methodref(kotlin/jvm/internal/Intrinsics.checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V) +[6] aload_0 v0 +[7] bipush 12 +[9] invokestatic #22 = Methodref(java/lang/Integer.valueOf(I)Ljava/lang/Integer;) +[12] invokeinterface #28, 512 = InterfaceMethodref(kotlin/jvm/functions/Function1.invoke(Ljava/lang/Object;)Ljava/lang/Object;) +[17] checkcast #30 = Class(java/lang/Number) +[20] invokevirtual #34 = Methodref(java/lang/Number.intValue()I) +[23] istore_1 v1 +[24] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[27] iload_1 v1 +[28] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[31] return +``` +### After copying the consuming method +We copy the consuming method because we will modify it to be specific to a particular usage of that method with a particular lambda argument. So if we have a method and we call it once with lambda 1 and once with lambda 2 we will have 2 new methods. One with lambda 1 inlined in it and one with lambda 2 inlined in it. + +Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` +### Removing casts and replacing call instruction +There are 2 invoke methods in the lambda class, a bridge method and a method that is called by the bridge method. This method that is called by the bridge method can have unboxed types as parameters. The bridge method has boxed parameter types because everything is erased to `Object` because we inline the non-bridge invoke method we will remove these casts. By removing the unneeded casts we are also able to improve performance. + +Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` +``` +[0] aload_0 v0 +[1] ldc #10 = String("a") +[3] invokestatic #16 = Methodref(kotlin/jvm/internal/Intrinsics.checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V) +[6] aload_0 v0 +[7] bipush 12 +[9] invokestatic #92 = Methodref(MainKt.a(Ljava/lang/Object;I)Ljava/lang/Integer;) +[12] istore_1 v1 +[13] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[16] iload_1 v1 +[17] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[20] return +``` +### After inlining static invoke method +In this step we actually copy the implementation from the invoke method and inline it into the consuming method. + +Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` +``` +[0] aload_0 v0 +[1] ldc #10 = String("a") +[3] invokestatic #16 = Methodref(kotlin/jvm/internal/Intrinsics.checkNotNullParameter(Ljava/lang/Object;Ljava/lang/String;)V) +[6] aload_0 v0 +[7] bipush 12 +[9] istore_3 v3 +[10] astore_2 v2 +[11] iload_3 v3 +[12] iload_3 v3 +[13] imul +[14] iconst_1 +[15] iadd +[16] istore_1 v1 +[17] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[20] iload_1 v1 +[21] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[24] return +``` +### After the null check remover +We are going to remove the lambda argument, because we do that we no longer need the null check that is present on the argument. + +Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` +``` +[0] aload_0 v0 +[1] bipush 12 +[3] istore_3 v3 +[4] astore_2 v2 +[5] iload_3 v3 +[6] iload_3 v3 +[7] imul +[8] iconst_1 +[9] iadd +[10] istore_1 v1 +[11] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[14] iload_1 v1 +[15] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[18] return +``` +### After changing the signature and removing the usage of the argument +Removing the usage of the argument shifts all load and store instruction that have a higher index than the index of the local that stores the lambda down by 1. Attempts to store the lambda in a local will result in a `pop` instruction. Any time the lambda local is loaded the instruction will be replaced with a direct reference to the lambda so this is a form of constant propagation. + +Consuming method = `c()V` +``` +[0] aconst_null +[1] nop +[2] bipush 12 +[4] istore_2 v2 +[5] astore_1 v1 +[6] iload_2 v2 +[7] iload_2 v2 +[8] imul +[9] iconst_1 +[10] iadd +[11] istore_0 v0 +[12] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[15] iload_0 v0 +[16] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[19] return +``` +## Changes to the method that calls the consuming method +### Starting point +At the starting point the code will obtain a reference to the lambda instance through the `getstatic` instruction. It will then invoke the method using this reference as one of the arguments. +``` +[0] getstatic #55 = Fieldref(MainKt$main$1.INSTANCE LMainKt$main$1;) +[3] checkcast #24 = Class(kotlin/jvm/functions/Function1) +[6] invokestatic #57 = Methodref(MainKt.test(Lkotlin/jvm/functions/Function1;)V) +[9] return +``` + +### After inlining the lambda +After inlining we will replace the first 3 instructions by just one `invokestatic` instruction that calls the new method that has the lambda inlined. As you can see this method takes no arguments. The method we call here is the final resulting method we get from the previous section. +``` +[0] invokestatic #95 = Methodref(MainKt.c()V) +[3] return +``` +## Changes to the invoke method +### The original invoke method +This is the original invoke method and its descriptor. This method contains the implementation of the lambda created by the programmer. In this case we have a lambda that multiplies the integer which is provided as an argument by itself and then adds 1 to the result. This matches the original Kotlin code of the lambda: +```kotlin +{ (it * it) + 1 } +``` + +At the end `valueOf` is called to take the primitive integer type and convert it to the boxed `java.lang.Integer` type. + +invoke method = `invoke(I)Ljava/lang/Integer;` +``` +[0] iload_1 v1 +[1] iload_1 v1 +[2] imul +[3] iconst_1 +[4] iadd +[5] invokestatic #22 = Methodref(java/lang/Integer.valueOf(I)Ljava/lang/Integer;) +[8] areturn +``` +### After copying to the consuming class +Still the same method and descriptor, just in a different class. +### After making it static +invoke method = `a(Ljava/lang/Object;I)Ljava/lang/Integer;` + +The code is the same. +### After removing the cast +The cast at the end is not really needed, after calling invoke in the consuming method we will cast it back into a primitive type. So we can remove the cast at the end here and we also do the same thing in the consuming method. + +invoke method = `a(Ljava/lang/Object;I)Ljava/lang/Integer;` +``` +[0] iload_1 v1 +[1] iload_1 v1 +[2] imul +[3] iconst_1 +[4] iadd +[5] areturn +``` +### After inlining +This method is deleted after inlining, we no longer need it at this point. \ No newline at end of file From 0cf13a7d0ed0d91ee2c2df1f063a408af85c662c Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Fri, 11 Aug 2023 16:58:22 +0200 Subject: [PATCH 67/72] Fix example and explain more about the cast removal/boxing/unboxing --- .../proguard/optimize/lambdainline/README.md | 47 ++++++++++++------- 1 file changed, 30 insertions(+), 17 deletions(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/README.md b/base/src/main/java/proguard/optimize/lambdainline/README.md index 8ddb48e6e..08e8cde69 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/README.md +++ b/base/src/main/java/proguard/optimize/lambdainline/README.md @@ -31,7 +31,13 @@ We copy the consuming method because we will modify it to be specific to a parti Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ### Removing casts and replacing call instruction -There are 2 invoke methods in the lambda class, a bridge method and a method that is called by the bridge method. This method that is called by the bridge method can have unboxed types as parameters. The bridge method has boxed parameter types because everything is erased to `Object` because we inline the non-bridge invoke method we will remove these casts. By removing the unneeded casts we are also able to improve performance. +There are 2 invoke methods in the lambda class, a bridge method and a method that is called by the bridge method. This +method that is called by the bridge method can have unboxed types as parameters. The bridge method has boxed parameter +types for primitives because everything is erased to `Object` but primitives are not reference types so you can just put +them in something of type Object without boxing them. Because we inline the non-bridge invoke method we will remove +these casts. This means that operations on primitive types can stay unboxed. By removing the unneeded casts we are also +able to improve performance. For reference types there will be no boxing code so in that case we won't have to +remove anything. Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ``` @@ -91,25 +97,27 @@ Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` [18] return ``` ### After changing the signature and removing the usage of the argument -Removing the usage of the argument shifts all load and store instruction that have a higher index than the index of the local that stores the lambda down by 1. Attempts to store the lambda in a local will result in a `pop` instruction. Any time the lambda local is loaded the instruction will be replaced with a direct reference to the lambda so this is a form of constant propagation. +Removing the usage of the argument shifts all load and store instruction that have a higher index than the index of the +local that stores the lambda down by 1. Attempts to store the lambda in a local will result in a `pop` instruction. Any +time the lambda local is loaded the instruction will be replaced with a direct reference to the lambda so this is a form +of constant propagation. Consuming method = `c()V` ``` -[0] aconst_null -[1] nop -[2] bipush 12 -[4] istore_2 v2 -[5] astore_1 v1 -[6] iload_2 v2 +[0] getstatic #55 = Fieldref(MainKt$main$1.INSTANCE LMainKt$main$1;) +[3] bipush 12 +[5] istore_2 v2 +[6] astore_1 v1 [7] iload_2 v2 -[8] imul -[9] iconst_1 -[10] iadd -[11] istore_0 v0 -[12] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) -[15] iload_0 v0 -[16] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) -[19] return +[8] iload_2 v2 +[9] imul +[10] iconst_1 +[11] iadd +[12] istore_0 v0 +[13] getstatic #40 = Fieldref(java/lang/System.out Ljava/io/PrintStream;) +[16] iload_0 v0 +[17] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) +[20] return ``` ## Changes to the method that calls the consuming method ### Starting point @@ -153,7 +161,12 @@ invoke method = `a(Ljava/lang/Object;I)Ljava/lang/Integer;` The code is the same. ### After removing the cast -The cast at the end is not really needed, after calling invoke in the consuming method we will cast it back into a primitive type. So we can remove the cast at the end here and we also do the same thing in the consuming method. +If this lambda returns a primitive type, the type will be boxed again, so we remove this boxing at end just before the +return using the `CastPatternRemover` class. If it returns a reference type there will be nothing to remove, the pattern +won't match so it won't do anything. The unboxing that was placed in the consuming method right after the call to the +invoke method is also removed. Boxing and then directly afterward unboxing it is unnecessary and degrades performance. +Originally the invoke bridge method was inlined, but then you would see a performance difference between using the +kotlin inline keyword and the ProGuard lambda inliner. invoke method = `a(Ljava/lang/Object;I)Ljava/lang/Integer;` ``` From 1ba33ba57ed41313eb400ae305eb791c9b70afdd Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Fri, 11 Aug 2023 17:14:55 +0200 Subject: [PATCH 68/72] Mention a bit more which classes are used to do which operations --- .../proguard/optimize/lambdainline/README.md | 44 ++++++++++++++++--- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/README.md b/base/src/main/java/proguard/optimize/lambdainline/README.md index 08e8cde69..d75480577 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/README.md +++ b/base/src/main/java/proguard/optimize/lambdainline/README.md @@ -26,11 +26,18 @@ Consuming method = `test(Lkotlin/jvm/functions/Function1;)V` [28] invokevirtual #46 = Methodref(java/io/PrintStream.println(I)V) [31] return ``` +The changes to the consuming method are done in the `BaseLambaInliner`, some of the changes require first doing changes +to the invoke method. The changes to the invoke method are described in a later section. + ### After copying the consuming method -We copy the consuming method because we will modify it to be specific to a particular usage of that method with a particular lambda argument. So if we have a method and we call it once with lambda 1 and once with lambda 2 we will have 2 new methods. One with lambda 1 inlined in it and one with lambda 2 inlined in it. +We copy the consuming method because we will modify it to be specific to a particular usage of that method with a +particular lambda argument. So if we have a method and we call it once with lambda 1 and once with lambda 2 we will have +2 new methods. One with lambda 1 inlined in it and one with lambda 2 inlined in it. + +To copy the consuming methd we will use the `MethodCopier` class. Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` -### Removing casts and replacing call instruction +### Removing casts and replacing the call instruction There are 2 invoke methods in the lambda class, a bridge method and a method that is called by the bridge method. This method that is called by the bridge method can have unboxed types as parameters. The bridge method has boxed parameter types for primitives because everything is erased to `Object` but primitives are not reference types so you can just put @@ -39,6 +46,10 @@ these casts. This means that operations on primitive types can stay unboxed. By able to improve performance. For reference types there will be no boxing code so in that case we won't have to remove anything. +The casts are removed using the `CastRemover` class. Replacing the call to the invoke method is done using the +`LambdaInvokeUsageReplacer` which is an inner class of the `BaseLambdaInliner`. + + Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ``` [0] aload_0 v0 @@ -54,7 +65,8 @@ Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` [20] return ``` ### After inlining static invoke method -In this step we actually copy the implementation from the invoke method and inline it into the consuming method. +In this step we actually copy the implementation from the invoke method and inline it into the consuming method. This is +done by using the `MethodInliner`. Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ``` @@ -79,6 +91,8 @@ Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ### After the null check remover We are going to remove the lambda argument, because we do that we no longer need the null check that is present on the argument. +Removing a Kotlin null check can be done using the `NullCheckRemover` class. + Consuming method = `b(Lkotlin/jvm/functions/Function1;)V` ``` [0] aload_0 v0 @@ -102,6 +116,8 @@ local that stores the lambda down by 1. Attempts to store the lambda in a local time the lambda local is loaded the instruction will be replaced with a direct reference to the lambda so this is a form of constant propagation. +The operation of removing a local usage happens using a class called the `LocalUsageRemover`. + Consuming method = `c()V` ``` [0] getstatic #55 = Fieldref(MainKt$main$1.INSTANCE LMainKt$main$1;) @@ -121,7 +137,8 @@ Consuming method = `c()V` ``` ## Changes to the method that calls the consuming method ### Starting point -At the starting point the code will obtain a reference to the lambda instance through the `getstatic` instruction. It will then invoke the method using this reference as one of the arguments. +At the starting point the code will obtain a reference to the lambda instance through the `getstatic` instruction. It +will then invoke the method using this reference as one of the arguments. ``` [0] getstatic #55 = Fieldref(MainKt$main$1.INSTANCE LMainKt$main$1;) [3] checkcast #24 = Class(kotlin/jvm/functions/Function1) @@ -130,14 +147,22 @@ At the starting point the code will obtain a reference to the lambda instance th ``` ### After inlining the lambda -After inlining we will replace the first 3 instructions by just one `invokestatic` instruction that calls the new method that has the lambda inlined. As you can see this method takes no arguments. The method we call here is the final resulting method we get from the previous section. +After inlining we will replace the first 3 instructions by just one `invokestatic` instruction that calls the new method +that has the lambda inlined. As you can see this method takes no arguments. The method we call here is the final +resulting method we get from the previous section. + +This happens in the `LambdaInliner` class, the inlined lambda method is obtained by calling the +`BaseLambdaInliner#inline()` method. This method returns null if it was unable to inline the specified lambda into the +specified method. ``` [0] invokestatic #95 = Methodref(MainKt.c()V) [3] return ``` ## Changes to the invoke method ### The original invoke method -This is the original invoke method and its descriptor. This method contains the implementation of the lambda created by the programmer. In this case we have a lambda that multiplies the integer which is provided as an argument by itself and then adds 1 to the result. This matches the original Kotlin code of the lambda: +This is the original invoke method and its descriptor. This method contains the implementation of the lambda created by +the programmer. In this case we have a lambda that multiplies the integer which is provided as an argument by itself and +then adds 1 to the result. This matches the original Kotlin code of the lambda: ```kotlin { (it * it) + 1 } ``` @@ -154,13 +179,18 @@ invoke method = `invoke(I)Ljava/lang/Integer;` [5] invokestatic #22 = Methodref(java/lang/Integer.valueOf(I)Ljava/lang/Integer;) [8] areturn ``` +The changes to the invoke method are done in the `BaseLambdaInliner`, the changes are performed at the same time as the +changes to the consuming method. They depend on each other, for example to inline the invoke method into the consuming +method using the `MethodInliner` we first have to copy it, make it static, remove boxing at the end, and then we can +actually inline it into the consuming method. Some more details of the changes to the invoke method are described below. + ### After copying to the consuming class Still the same method and descriptor, just in a different class. ### After making it static invoke method = `a(Ljava/lang/Object;I)Ljava/lang/Integer;` The code is the same. -### After removing the cast +### After removing the cast/boxing If this lambda returns a primitive type, the type will be boxed again, so we remove this boxing at end just before the return using the `CastPatternRemover` class. If it returns a reference type there will be nothing to remove, the pattern won't match so it won't do anything. The unboxing that was placed in the consuming method right after the call to the From ceea1b679d08301d6a389cc4ab8930c8ddb0493d Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Sat, 12 Aug 2023 18:16:45 +0200 Subject: [PATCH 69/72] Be more specific when using the ClassReferenceInitializer, not all references have to be updated, just the classes involved in the inlining process This should improve performance a bit. --- .../optimize/lambdainline/LambdaInliner.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java index 149850759..cb0e18a0b 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java @@ -33,7 +33,8 @@ import proguard.classfile.editor.ConstantPoolEditor; import proguard.classfile.instruction.ConstantInstruction; import proguard.classfile.instruction.Instruction; -import proguard.classfile.util.InitializationUtil; +import proguard.classfile.util.ClassReferenceInitializer; +import proguard.classfile.util.ClassSuperHierarchyInitializer; import proguard.optimize.lambdainline.lambdalocator.Lambda; import proguard.optimize.lambdainline.lambdalocator.LambdaLocator; import proguard.pass.Pass; @@ -69,7 +70,6 @@ public void execute(AppView appView) { logger.debug("Descriptor : " + lambda.method().getDescriptor(lambda.clazz())); Set remainder = new HashSet<>(); inlinedAllUsages = true; - InitializationUtil.initialize(appView.programClassPool, appView.libraryClassPool); lambda.codeAttribute().accept(lambda.clazz(), lambda.method(), new LambdaUsageFinder(lambda, lambdaLocator.getKotlinLambdaMap(), new LambdaUsageHandler(appView.programClassPool, appView.libraryClassPool, remainder) @@ -151,6 +151,19 @@ boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int } codeAttributeEditor.visitCodeAttribute(consumingCallClass, consumingCallMethod, consumingCallCodeAttribute); + + // Update references of classes involved after inlining. We need to do this because we made new methods, and + // we also replaced certain call instructions to point to those methods. + libraryClassPool.classesAccept( + new ClassSuperHierarchyInitializer(programClassPool, + libraryClassPool, + null, + null)); + programClassPool.classesAccept(new ClassSuperHierarchyInitializer(programClassPool, libraryClassPool)); + lambda.clazz().accept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); + consumingCallClass.accept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); + consumingClazz.accept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); + logger.info("Inlined a lambda into {}#{}{}", consumingClazz.getName(), consumingMethod.getName(consumingClazz), consumingMethod.getDescriptor(consumingClazz)); return true; } From 93d3bb65779833dcbeb2e0920941c0e8a9731464 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Sat, 12 Aug 2023 23:24:03 +0200 Subject: [PATCH 70/72] Only re-run the ClassReferenceInitializer on the consuming class This should also improve performance a bit. --- .../proguard/optimize/lambdainline/BaseLambdaInliner.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java index 3aca64f54..79d7cd627 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java @@ -245,10 +245,10 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod method) // Remove return value's casting from staticInvokeMethod. staticInvokeMethod.accept(consumingClass, new AllAttributeVisitor(new PeepholeEditor(codeAttributeEditor, new CastPatternRemover(codeAttributeEditor)))); - // Important for inlining, we need this so that method invocations have non-null referenced methods. - programClassPool.classesAccept( - new ClassReferenceInitializer(programClassPool, libraryClassPool) - ); + // Because we replaced the call instruction with a call to a different method, the current references of the + // consuming class are no longer correct. This is important because the MethodInliner uses those references + // to see where it should inline. + consumingClass.accept(new ClassReferenceInitializer(programClassPool, libraryClassPool)); // Inlining phase 2, inline that static invoke method into the actual function that uses the lambda. inlineMethodInClass(consumingClass, staticInvokeMethod); From ac7f42dd485ce9f5551efb718e2c6bfd2b8de504 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Sun, 13 Aug 2023 00:14:44 +0200 Subject: [PATCH 71/72] Only run AccessFixer on consumingClass instead of the entire programClassPool This should again improve performance of the pass a bit. --- .../java/proguard/optimize/lambdainline/BaseLambdaInliner.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java index 79d7cd627..423425231 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/BaseLambdaInliner.java @@ -286,7 +286,7 @@ public void visitFieldrefConstant(Clazz clazz, FieldrefConstant fieldrefConstant methodWithoutLambdaParameter.accept(consumingClass, new LocalUsageRemover(codeAttributeEditor, sizeAdjustedLambdaIndex, replacementInstruction)); } }); - programClassPool.classesAccept(new AccessFixer()); + consumingClass.accept(new AccessFixer()); // The resulting new method is: methodWithoutLambdaParameter, the user of the BaseLambdaInliner can then replace // calls to the old function to calls to this new function. From 370704b4ee2facfa4e7ca779de906489f559b6e9 Mon Sep 17 00:00:00 2001 From: MaartenS11 Date: Sun, 13 Aug 2023 13:53:29 +0200 Subject: [PATCH 72/72] Adjust conditional inlining attempt condition to use sum of consuming method and lambda length --- .../optimize/lambdainline/LambdaInliner.java | 2 +- ...r.java => LimitedLengthLambdaInliner.java} | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) rename base/src/main/java/proguard/optimize/lambdainline/{ShortLambdaInliner.java => LimitedLengthLambdaInliner.java} (72%) diff --git a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java index cb0e18a0b..65ae9d483 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LambdaInliner.java @@ -107,7 +107,7 @@ public LambdaUsageHandler(ClassPool programClassPool, ClassPool libraryClassPool boolean handle(Lambda lambda, Clazz consumingClazz, Method consumingMethod, int lambdaArgumentIndex, int consumingCallOffset, Clazz consumingCallClass, Method consumingCallMethod, CodeAttribute consumingCallCodeAttribute, List sourceTrace, List possibleLambdas) { // Try inlining the lambda in consumingMethod - BaseLambdaInliner baseLambdaInliner = new ShortLambdaInliner(programClassPool, libraryClassPool, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); + BaseLambdaInliner baseLambdaInliner = new LimitedLengthLambdaInliner(programClassPool, libraryClassPool, consumingClazz, consumingMethod, lambdaArgumentIndex, lambda); Method inlinedLambamethod = baseLambdaInliner.inline(); // We didn't inline anything so no need to change any call instructions. diff --git a/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java b/base/src/main/java/proguard/optimize/lambdainline/LimitedLengthLambdaInliner.java similarity index 72% rename from base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java rename to base/src/main/java/proguard/optimize/lambdainline/LimitedLengthLambdaInliner.java index 42c021e04..2be344ee2 100644 --- a/base/src/main/java/proguard/optimize/lambdainline/ShortLambdaInliner.java +++ b/base/src/main/java/proguard/optimize/lambdainline/LimitedLengthLambdaInliner.java @@ -30,17 +30,17 @@ import java.util.Optional; /** - * This class is an implementation of the {@link proguard.optimize.lambdainline.BaseLambdaInliner BaseLambdaInliner } that - * inlines lambdas depending on the length of the lambda implementation method and the length of the consuming method. - * The length of the consuming method is taken into account because the consuming method will be copied when inlining - * this lambda because one method can take multiple different lambdas as an input. + * This class is an implementation of the {@link proguard.optimize.lambdainline.BaseLambdaInliner BaseLambdaInliner } + * that inlines lambdas depending on the length of the lambda implementation method and the length of the consuming + * method. The sum of the lengths of the lambda implementation and consuming method is used to determine if we will + * attempt to inline this lambda. We do this because inlining the lambda will result in a new method that has a length + * close to this sum of these 2 methods. */ -public class ShortLambdaInliner extends BaseLambdaInliner { +public class LimitedLengthLambdaInliner extends BaseLambdaInliner { private final Logger logger = LogManager.getLogger(); - private static final int MAXIMUM_CONSUMING_METHOD_LENGTH = 2000; - private static final int MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH = 64; + private static final int MAXIMUM_INLINED_LENGTH = 1000; - public ShortLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { + public LimitedLengthLambdaInliner(ClassPool programClassPool, ClassPool libraryClassPool, Clazz consumingClass, Method consumingMethod, int calledLambdaIndex, Lambda lambda) { super(programClassPool, libraryClassPool, consumingClass, consumingMethod, calledLambdaIndex, lambda); } @@ -60,12 +60,12 @@ protected boolean shouldInline(Clazz consumingClass, Method consumingMethod, Cla return false; } - boolean inline = lambdaImplMethodLength.get() < MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH && consumingMethodLength.get() < MAXIMUM_CONSUMING_METHOD_LENGTH; + boolean inline = lambdaImplMethodLength.get() + consumingMethodLength.get() < MAXIMUM_INLINED_LENGTH; if (!inline) { - logger.info("Will not attempt inlining lambda because methods are too long, maximum consuming method length = {}, maximum lambda implementation method length = {}", MAXIMUM_CONSUMING_METHOD_LENGTH, MAXIMUM_LAMBDA_IMPL_METHOD_LENGTH); + logger.info("Will not attempt inlining lambda because methods are too long, maximum consuming method length + lambda implementation method length = {}", MAXIMUM_INLINED_LENGTH); logger.info("Consuming method = {}#{}{}", consumingClass.getName(), consumingMethod.getName(consumingClass), consumingMethod.getDescriptor(consumingClass)); logger.info("Lambda implementation method = {}#{}{}", lambdaClass.getName(), lambdaImplMethod.getName(lambdaClass), lambdaImplMethod.getDescriptor(lambdaClass)); - logger.info("Consuming method length = {}, lambda implementation method length = {}", consumingMethodLength.get(), lambdaImplMethodLength.get()); + logger.info("Consuming method length = {}, lambda implementation method length = {}, sum = {}", consumingMethodLength.get(), lambdaImplMethodLength.get(), consumingMethodLength.get() + lambdaImplMethodLength.get()); } return inline; }