Check lambda inlining in package part files in test framework

This commit is contained in:
Michael Bogdanov
2016-08-17 16:54:06 +03:00
parent f2b8569b4b
commit a12d7b6019
22 changed files with 98 additions and 48 deletions
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,5 +1,6 @@
// FILE: 1.kt // NO_CHECK_LAMBDA_INLINING
// WITH_RUNTIME // WITH_RUNTIME
// FILE: 1.kt
package test package test
@@ -1,5 +1,6 @@
// FILE: 1.kt // NO_CHECK_LAMBDA_INLINING
// WITH_RUNTIME // WITH_RUNTIME
// FILE: 1.kt
package test package test
fun <T> T.noInline(p: (T) -> Unit) { fun <T> T.noInline(p: (T) -> Unit) {
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
// WITH_RUNTIME // WITH_RUNTIME
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -2,8 +2,8 @@
package test package test
inline fun call(p: String, s: String.() -> Int): Int { inline fun call(a: String, b: String, s: String.(String) -> String): Int {
return p.s() return a.s(b)
} }
// FILE: 2.kt // FILE: 2.kt
@@ -11,5 +11,5 @@ inline fun call(p: String, s: String.() -> Int): Int {
import test.* import test.*
fun box() : String { fun box() : String {
return if (call("123", String::length) == 3) "OK" else "fail" return return call("O", "K", String::plus)
} }
@@ -1,3 +1,4 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
@@ -1,5 +1,6 @@
// FILE: 1.kt // NO_CHECK_LAMBDA_INLINING
// WITH_REFLECT // WITH_REFLECT
// FILE: 1.kt
package test package test
inline fun <R> call(s: () -> R) = s() inline fun <R> call(s: () -> R) = s()
@@ -1,5 +1,6 @@
// FILE: 1.kt // NO_CHECK_LAMBDA_INLINING
// WITH_RUNTIME // WITH_RUNTIME
// FILE: 1.kt
package test package test
inline fun test(s: () -> Unit) { inline fun test(s: () -> Unit) {
@@ -1,5 +1,5 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
inline fun test(s: () -> Unit) { inline fun test(s: () -> Unit) {
@@ -1,5 +1,5 @@
// NO_CHECK_LAMBDA_INLINING
// FILE: 1.kt // FILE: 1.kt
package test package test
inline fun test(s: () -> Unit) { inline fun test(s: () -> Unit) {
+13 -13
View File
@@ -4,10 +4,18 @@ package test
interface InlineTrait { interface InlineTrait {
fun finalInline(s: () -> String): String { private inline fun privateInline(s: () -> String): String {
return s() return s()
} }
fun testPrivateInline(): String {
return privateInline { "private" }
}
fun testPrivateInline2(): String {
return privateInline { "private2" }
}
companion object { companion object {
inline final fun finalInline(s: () -> String): String { inline final fun finalInline(s: () -> String): String {
return s() return s()
@@ -15,7 +23,7 @@ interface InlineTrait {
} }
} }
class Z: InlineTrait { class Z : InlineTrait {
} }
@@ -23,21 +31,13 @@ class Z: InlineTrait {
import test.* import test.*
fun testFinalInline(): String {
return Z().finalInline({"final"})
}
fun testFinalInline2(instance: InlineTrait): String {
return instance.finalInline({"final2"})
}
fun testClassObject(): String { fun testClassObject(): String {
return InlineTrait.finalInline({"classobject"}) return InlineTrait.finalInline({ "classobject" })
} }
fun box(): String { fun box(): String {
if (testFinalInline() != "final") return "test1: ${testFinalInline()}" if (Z().testPrivateInline() != "private") return "test1: ${Z().testPrivateInline()}"
if (testFinalInline2(Z()) != "final2") return "test2: ${testFinalInline2(Z())}" if (Z().testPrivateInline2() != "private2") return "test2: ${Z().testPrivateInline2()}"
if (testClassObject() != "classobject") return "test3: ${testClassObject()}" if (testClassObject() != "classobject") return "test3: ${testClassObject()}"
return "OK" return "OK"
@@ -40,7 +40,7 @@ object InlineTestUtil {
val skipParameterChecking = sourceFiles.any { val skipParameterChecking = sourceFiles.any {
InTextDirectivesUtils.isDirectiveDefined(it.text, "NO_CHECK_LAMBDA_INLINING") InTextDirectivesUtils.isDirectiveDefined(it.text, "NO_CHECK_LAMBDA_INLINING")
} } || !doLambdaInliningCheck(files, inlineInfo)
if (!skipParameterChecking) { if (!skipParameterChecking) {
val notInlinedParameters = checkParametersInlined(files, inlineInfo) val notInlinedParameters = checkParametersInlined(files, inlineInfo)
@@ -54,20 +54,16 @@ object InlineTestUtil {
private fun obtainInlineInfo(files: Iterable<OutputFile>): InlineInfo { private fun obtainInlineInfo(files: Iterable<OutputFile>): InlineInfo {
val inlineMethods = HashSet<MethodInfo>() val inlineMethods = HashSet<MethodInfo>()
val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>() val binaryClasses = hashMapOf<String, KotlinJvmBinaryClass>()
for (file in files) { for (file in files) {
val binaryClass = loadBinaryClass(file) val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader) val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
val classVisitor = object : ClassVisitorWithName() { val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor { override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) { if (name + desc in inlineFunctions) {
override fun visitEnd() { inlineMethods.add(MethodInfo(className, name, desc))
if (name + desc in inlineFunctions) {
inlineMethods.add(MethodInfo(className, name, this.desc))
}
}
} }
return null
} }
} }
@@ -78,6 +74,34 @@ object InlineTestUtil {
return InlineInfo(inlineMethods, binaryClasses) return InlineInfo(inlineMethods, binaryClasses)
} }
private fun doLambdaInliningCheck(files: Iterable<OutputFile>, inlineInfo: InlineInfo): Boolean {
var doLambdaInliningCheck = true
for (file in files) {
val binaryClass = loadBinaryClass(file)
val inlineFunctions = inlineFunctionsJvmNames(binaryClass.classHeader)
val classVisitor = object : ClassVisitorWithName() {
override fun visitMethod(access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?): MethodVisitor? {
if (name + desc in inlineFunctions) {
return object: MethodNodeWithAnonymousObjectCheck(inlineInfo, access, name, desc, signature, exceptions) {
override fun onAnonymousConstructorCallOrSingletonAccess(owner: String) {
doLambdaInliningCheck = false
}
}
}
return null
}
}
ClassReader(file.asByteArray()).accept(classVisitor, 0)
if (!doLambdaInliningCheck) break
}
return doLambdaInliningCheck
}
private fun checkInlineMethodNotInvoked(files: Iterable<OutputFile>, inlinedMethods: Set<MethodInfo>): List<NotInlinedCall> { private fun checkInlineMethodNotInvoked(files: Iterable<OutputFile>, inlinedMethods: Set<MethodInfo>): List<NotInlinedCall> {
val notInlined = ArrayList<NotInlinedCall>() val notInlined = ArrayList<NotInlinedCall>()
@@ -139,23 +163,10 @@ object InlineTestUtil {
return null return null
} }
return object : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) { return object: MethodNodeWithAnonymousObjectCheck(inlineInfo, access, name, desc, signature, exceptions) {
private fun isInlineParameterLikeOwner(owner: String) = override fun onAnonymousConstructorCallOrSingletonAccess(owner: String) {
"$" in owner && !isTopLevelOrInnerOrPackageClass(owner, inlineInfo) val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if ("<init>".equals(name) && isInlineParameterLikeOwner(owner)) {
val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
}
}
override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
if (opcode == Opcodes.GETSTATIC && isInlineParameterLikeOwner(owner)) {
val fromCall = MethodInfo(className, this.name, this.desc)
notInlinedParameters.add(NotInlinedParameter(owner, fromCall))
}
super.visitFieldInsn(opcode, owner, name, desc)
} }
} }
} }
@@ -174,6 +185,8 @@ object InlineTestUtil {
private fun isClassOrPackagePartKind(klass: KotlinJvmBinaryClass): Boolean { private fun isClassOrPackagePartKind(klass: KotlinJvmBinaryClass): Boolean {
return klass.classHeader.kind == KotlinClassHeader.Kind.CLASS && !klass.classId.isLocal return klass.classHeader.kind == KotlinClassHeader.Kind.CLASS && !klass.classId.isLocal
|| klass.classHeader.kind == KotlinClassHeader.Kind.FILE_FACADE /*single file facade equals to package part*/
|| klass.classHeader.kind == KotlinClassHeader.Kind.MULTIFILE_CLASS_PART
} }
private fun loadBinaryClass(file: OutputFile): KotlinJvmBinaryClass { private fun loadBinaryClass(file: OutputFile): KotlinJvmBinaryClass {
@@ -207,4 +220,24 @@ object InlineTestUtil {
super.visit(version, access, name, signature, superName, interfaces) super.visit(version, access, name, signature, superName, interfaces)
} }
} }
private abstract class MethodNodeWithAnonymousObjectCheck(val inlineInfo: InlineInfo, access: Int, name: String, desc: String, signature: String?, exceptions: Array<String>?) : MethodNode(Opcodes.ASM5, access, name, desc, signature, exceptions) {
private fun isInlineParameterLikeOwner(owner: String) =
"$" in owner && !isTopLevelOrInnerOrPackageClass(owner, inlineInfo)
override fun visitMethodInsn(opcode: Int, owner: String, name: String, desc: String, itf: Boolean) {
if ("<init>" == name && isInlineParameterLikeOwner(owner)) {
onAnonymousConstructorCallOrSingletonAccess(owner)
}
}
abstract fun onAnonymousConstructorCallOrSingletonAccess(owner: String)
override fun visitFieldInsn(opcode: Int, owner: String, name: String, desc: String) {
if (opcode == Opcodes.GETSTATIC && isInlineParameterLikeOwner(owner)) {
onAnonymousConstructorCallOrSingletonAccess(owner)
}
super.visitFieldInsn(opcode, owner, name, desc)
}
}
} }