Allow method from interfaces to be inlined if it is private and is being called from within the interface

This commit is contained in:
Thomas Vochten
2023-09-22 08:18:57 +02:00
committed by Dimitrios Anyfantakis
parent 8bb7cc0c4b
commit 7429219cd2
4 changed files with 97 additions and 7 deletions

View File

@@ -654,11 +654,12 @@ implements AttributeVisitor,
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 @@ implements AttributeVisitor,
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

View File

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

View File

@@ -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",

View File

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