From 7429219cd2d1b5611c06ebc5dd270bfbb132205f Mon Sep 17 00:00:00 2001 From: Thomas Vochten Date: Fri, 22 Sep 2023 08:18:57 +0200 Subject: [PATCH] Allow method from interfaces to be inlined if it is private and is being called from within the interface --- .../optimize/peephole/MethodInliner.java | 22 ++++-- .../peephole/MethodInlinerJava9Test.kt | 78 +++++++++++++++++++ .../optimize/peephole/MethodInlinerTest.kt | 2 +- docs/md/manual/releasenotes.md | 2 +- 4 files changed, 97 insertions(+), 7 deletions(-) create mode 100644 base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerJava9Test.kt diff --git a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java index 55d5fbf2..d5631131 100644 --- a/base/src/main/java/proguard/optimize/peephole/MethodInliner.java +++ b/base/src/main/java/proguard/optimize/peephole/MethodInliner.java @@ -654,11 +654,12 @@ public void visitProgramMethod(ProgramClass programClass, ProgramMethod programM DEBUG("Interface?") && - // Methods in interfaces should not be inlined since this can potentially - // lead to other methods in the interface needing broadened visibility, - // which can lead to either compilation errors during output writing - // or various issues at runtime. - (programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 && + // Methods in interfaces should only very rarely be inlined + // since this can potentially lead to other methods in the interface + // needing broadened visibility, which can lead to either compilation errors + // during output writing or various issues at runtime. + ((programClass.getAccessFlags() & AccessConstants.INTERFACE) == 0 || + canInlineMethodFromInterface(programClass, programMethod)) && DEBUG("Synchronized?") && @@ -897,6 +898,17 @@ private boolean returnsIntLike(String methodDescriptor) returnChar == TypeConstants.SHORT; } + /** + * We only inline methods from interfaces if both of these conditions hold: + * - The class that references the method is the interface itself. + * - The method is private. + */ + private boolean canInlineMethodFromInterface(ProgramClass sourceClass, ProgramMethod sourceMethod) + { + return + sourceClass.equals(targetClass) && + (sourceMethod.getAccessFlags() & AccessConstants.PRIVATE) != 0; + } /** * Indicates whether this method should be inlined. Subclasses can overwrite diff --git a/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerJava9Test.kt b/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerJava9Test.kt new file mode 100644 index 00000000..38a44664 --- /dev/null +++ b/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerJava9Test.kt @@ -0,0 +1,78 @@ +package proguard.optimize.peephole + +import io.kotest.core.spec.IsolationMode +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.ints.shouldBeGreaterThan +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.visitor.AllMethodVisitor +import proguard.classfile.visitor.ClassVisitor +import proguard.classfile.visitor.MultiClassVisitor +import proguard.optimize.info.ProgramClassOptimizationInfoSetter +import proguard.optimize.info.ProgramMemberOptimizationInfoSetter +import proguard.testutils.ClassPoolBuilder +import proguard.testutils.JavaSource +import testutils.RequiresJavaVersion + +@RequiresJavaVersion(9) +class MethodInlinerJava9Test : FreeSpec({ + isolationMode = IsolationMode.InstancePerTest + + "Given a method calling a private method in the same interface" - { + val (programClassPool, _) = ClassPoolBuilder.fromSource( + JavaSource( + "Foo.java", + """interface Foo { + default void f1() { + f2(); + } + + private static void f2() { + StringBuilder sb = new StringBuilder(); + sb.append(System.currentTimeMillis()); + System.out.println(sb.toString()); + } + }""", + ) + ) + + val clazz = programClassPool.getClass("Foo") as ProgramClass + val method = clazz.findMethod("f1", "()V") as ProgramMethod + val codeAttr = method.attributes.filterIsInstance()[0] + + val lengthBefore = codeAttr.u4codeLength + + // Initialize optimization info (used when inlining). + val optimizationInfoInitializer: ClassVisitor = MultiClassVisitor( + ProgramClassOptimizationInfoSetter(), + AllMethodVisitor( + ProgramMemberOptimizationInfoSetter() + ) + ) + + programClassPool.classesAccept(optimizationInfoInitializer) + + // Create a mock method inliner which always returns true. + val methodInliner = object : MethodInliner(false, true, true) { + override fun shouldInline(clazz: Clazz?, method: Method?, codeAttribute: CodeAttribute?): Boolean = true + } + + "Then the interface method is inlined" { + programClassPool.classesAccept( + AllMethodVisitor( + AllAttributeVisitor( + methodInliner + ) + ) + ) + + val lengthAfter = codeAttr.u4codeLength + + lengthAfter shouldBeGreaterThan lengthBefore + } + } +}) diff --git a/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerTest.kt b/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerTest.kt index d6ef6030..975e0844 100644 --- a/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerTest.kt +++ b/base/src/test/kotlin/proguard/optimize/peephole/MethodInlinerTest.kt @@ -283,7 +283,7 @@ class MethodInlinerTest : FreeSpec({ } } - "Given a method calling another method in an interface" - { + "Given a method calling another non-private method in an interface" - { val (programClassPool, _) = ClassPoolBuilder.fromSource( JavaSource( "Foo.java", diff --git a/docs/md/manual/releasenotes.md b/docs/md/manual/releasenotes.md index a9731a4e..16e8d48b 100644 --- a/docs/md/manual/releasenotes.md +++ b/docs/md/manual/releasenotes.md @@ -13,7 +13,7 @@ - Fix "NoClassDefFoundError: Failed resolution of: Lorg/apache/logging/log4j/LogManager" when using GSON optimization or `-addconfigurationdebugging`. (#326) - Don't drop Record attribute for records with no components. (proguard-core#118) - Fix potential duplication class when name obfuscating Kotlin multi-file facades. -- Do not inline interface methods to avoid compilation errors during output writing due to an interface method being made package visible. +- Do not inline interface methods during optimization to avoid compilation errors during output writing due to an interface method being made package visible. ## Version 7.3.2