Skip to content

Commit

Permalink
Allow method from interfaces to be inlined if it is private and is be…
Browse files Browse the repository at this point in the history
…ing called from within the interface
  • Loading branch information
tvoc-gs authored and dimitrisAnyfantakis committed Sep 22, 2023
1 parent 8bb7cc0 commit 7429219
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 7 deletions.
22 changes: 17 additions & 5 deletions base/src/main/java/proguard/optimize/peephole/MethodInliner.java
Original file line number Diff line number Diff line change
Expand Up @@ -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?") &&

Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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<CodeAttribute>()[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
}
}
})
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion docs/md/manual/releasenotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down

0 comments on commit 7429219

Please sign in to comment.