Check lambda inlining in package part files in test framework
This commit is contained in:
@@ -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
@@ -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
-1
@@ -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
-1
@@ -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
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user