diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java index e3731cd284c..a3a19f4b992 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/SamWrapperCodegen.java @@ -185,6 +185,8 @@ public class SamWrapperCodegen { private FqName getWrapperName(@NotNull KtFile containingFile) { FqName fileClassFqName = JvmFileClassUtil.getFileClassInfoNoResolve(containingFile).getFileClassFqName(); JavaClassDescriptor descriptor = samType.getJavaClassDescriptor(); + //Change sam wrapper name template carefully cause it's used in inliner: + // see isSamWrapper/isSamWrapperConstructorCall in inlineCodegenUtils.kt int hash = PackagePartClassUtils.getPathHashCode(containingFile.getVirtualFile()) * 31 + DescriptorUtils.getFqNameSafe(descriptor).hashCode(); String shortName = String.format( diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt index cd08ab3a662..472ac7e531a 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/MethodInliner.kt @@ -180,7 +180,7 @@ class MethodInliner( } override fun anew(type: Type) { - if (isAnonymousClass(type.internalName)) { + if (isSamWrapper(type.internalName) || isAnonymousClass(type.internalName)) { handleAnonymousObjectRegeneration() } @@ -430,12 +430,15 @@ class MethodInliner( val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, true, instructions, sources, toDelete) invokeCalls.add(InvokeCall(lambdaInfo, currentFinallyDeep)) } + else if (isSamWrapperConstructorCall(owner, name)) { + transformations.add(SamWrapperTransformationInfo(owner, inliningContext, isAlreadyRegenerated(owner))) + } else if (isAnonymousConstructorCall(owner, name)) { val lambdaMapping = HashMap() var offset = 0 var capturesAnonymousObjectThatMustBeRegenerated = false - for (i in 0..paramCount - 1) { + for (i in 0 until paramCount) { val sourceValue = frame.getStack(firstParameterIndex + i) val lambdaInfo = getLambdaIfExistsAndMarkInstructions(sourceValue, false, instructions, sources, toDelete ) diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SamWrapperTransformer.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SamWrapperTransformer.kt new file mode 100644 index 00000000000..5bd007006d0 --- /dev/null +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/SamWrapperTransformer.kt @@ -0,0 +1,67 @@ +/* + * Copyright 2010-2017 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.jetbrains.kotlin.codegen.inline + +import org.jetbrains.org.objectweb.asm.ClassReader +import org.jetbrains.org.objectweb.asm.ClassVisitor + +class SamWrapperTransformationInfo(override val oldClassName: String, private val inliningContext: InliningContext, private val alreadyRegenerated: Boolean): TransformationInfo { + override val nameGenerator: NameGenerator + get() = object: NameGenerator("stub") { + override fun getGeneratorClass(): String { + error ("Shouldn't be called on $oldClassName transformation") + } + + override fun subGenerator(inliningMethod: String?): NameGenerator { + error ("Shouldn't be called on $oldClassName transformation") + } + + override fun subGenerator(lambdaNoWhen: Boolean, nameSuffix: String?): NameGenerator { + error ("Shouldn't be called on $oldClassName transformation") + } + } + + //TODO consider to use package class instead of inliningContext.root.callSiteInfo.ownerClassName + override val newClassName: String + get() = inliningContext.root.callSiteInfo.ownerClassName + "\$inlined" + "\$sam$".run { this + oldClassName.substringAfter(this) } + + override fun shouldRegenerate(sameModule: Boolean) = !sameModule && !alreadyRegenerated + + override fun canRemoveAfterTransformation() = false + + override fun createTransformer(inliningContext: InliningContext, sameModule: Boolean) = + SamWrapperTransformer(this, inliningContext) +} + +class SamWrapperTransformer(transformationInfo: SamWrapperTransformationInfo, private val inliningContext: InliningContext) : + ObjectTransformer(transformationInfo, inliningContext.state) { + + override fun doTransform(parentRemapper: FieldRemapper): InlineResult { + val classReader = createClassReader() + val classBuilder = createRemappingClassBuilderViaFactory(inliningContext) + + classReader.accept(object : ClassVisitor(API, classBuilder.visitor) { + override fun visit(version: Int, access: Int, name: String, signature: String?, superName: String, interfaces: Array) { + classBuilder.defineClass(null, version, access, name, signature, superName, interfaces) + } + + }, ClassReader.SKIP_FRAMES) + classBuilder.done() + + return transformationResult + } +} \ No newline at end of file diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt index bc5f499c4c4..120548f44bc 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/inline/inlineCodegenUtils.kt @@ -219,9 +219,15 @@ internal fun isWhenMappingAccess(internalName: String, fieldName: String): Boole internal fun isAnonymousSingletonLoad(internalName: String, fieldName: String): Boolean = JvmAbi.INSTANCE_FIELD == fieldName && isAnonymousClass(internalName) -internal fun isAnonymousClass(internalName: String): Boolean { - return internalName.substringAfterLast('/').substringAfterLast("$", "").isInteger() -} +internal fun isSamWrapper(internalName: String) = + internalName.contains("\$sam$") && internalName.substringAfter("\$i$", "").run { length == 8 && toLongOrNull(16) != null } + +internal fun isSamWrapperConstructorCall(internalName: String, methodName: String) = + isConstructor(methodName) && isSamWrapper(internalName) + +internal fun isAnonymousClass(internalName: String) = + !isSamWrapper(internalName) && + internalName.substringAfterLast('/').substringAfterLast("$", "").isInteger() fun wrapWithMaxLocalCalc(methodNode: MethodNode) = MaxStackFrameSizeAndLocalsCalculator(API, methodNode.access, methodNode.desc, methodNode) diff --git a/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt new file mode 100644 index 00000000000..a3ca24a3d04 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt @@ -0,0 +1,20 @@ +// FILE: 1.kt +// FULL_JDK + +package test +import java.util.concurrent.Executors + +inline fun doWork(noinline job: ()-> Unit) { + Executors.callable(job).call() +} + +// FILE: 2.kt + +import test.* + +fun box() : String { + var result = "fail" + doWork { result = "OK" } + + return result +} diff --git a/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt new file mode 100644 index 00000000000..b86d4fd7985 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt @@ -0,0 +1,22 @@ +// FILE: 1.kt +// FULL_JDK + +package test +import java.util.concurrent.Executors + +inline fun doWork(noinline job: ()-> Unit) { + Executors.callable(job).call() + Executors.callable(job).call() +} + +// FILE: 2.kt + +import test.* + +fun box() : String { + var result = "" + var value = 1 + doWork { result += if (value++ == 1) "O" else "K" } + + return result +} diff --git a/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt new file mode 100644 index 00000000000..233c24755e2 --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt @@ -0,0 +1,22 @@ +// FILE: 1.kt +// FULL_JDK + +package test +import java.util.concurrent.Executors + +inline fun doWork(noinline job: ()-> Unit) { + { Executors.callable(job).call() } () + Executors.callable(job).call() +} + +// FILE: 2.kt + +import test.* + +fun box() : String { + var result = "" + var value = 1 + doWork { result += if (value++ == 1) "O" else "K" } + + return result +} diff --git a/compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt b/compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt new file mode 100644 index 00000000000..a52059c540f --- /dev/null +++ b/compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt @@ -0,0 +1,24 @@ +// FILE: 1.kt +// FULL_JDK + +package test + +inline fun doWork(job: ()-> Unit) { + job() +} + +// FILE: 2.kt + +//NO_CHECK_LAMBDA_INLINING +import test.* +import java.util.concurrent.Executors + +fun box() : String { + var result = "fail" + doWork { + val job = { result = "OK" } + Executors.callable(job).call() + } + + return result +} diff --git a/compiler/testData/compileKotlinAgainstKotlin/copySamOnInline.kt b/compiler/testData/compileKotlinAgainstKotlin/copySamOnInline.kt new file mode 100644 index 00000000000..a338cfa9635 --- /dev/null +++ b/compiler/testData/compileKotlinAgainstKotlin/copySamOnInline.kt @@ -0,0 +1,30 @@ +// FILE: A.kt +// FULL_JDK + +package test + +import java.util.concurrent.Callable + +class A(val callable: Callable) + +inline fun doWork(noinline job: () -> String): Callable { + val a = A(Callable(job)) + return a.callable +} + +var sameModule = doWork { "O" } + + +// FILE: B.kt + +import test.* + +fun box(): String { + val anotherModule = doWork { "K" } + + if (sameModule.javaClass.name == anotherModule.javaClass.name) return "class should be regenerated, but ${anotherModule.javaClass.name}" + if (sameModule.javaClass.name.contains("inlined")) return "Sam in same module shouldn't be copied, but ${sameModule.javaClass.name}" + if (!anotherModule.javaClass.name.contains("inlined")) return "Sam in another module should be copied, but ${sameModule.javaClass.name}" + + return sameModule.call() + anotherModule.call() +} diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java index be28bf6d029..f0d2987c613 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxInlineCodegenTestGenerated.java @@ -455,6 +455,39 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli } } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sam extends AbstractIrBlackBoxInlineCodegenTest { + public void testAllFilesPresentInSam() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/anonymousObject/sam"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("kt21671.kt") + public void testKt21671() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_2.kt") + public void testKt21671_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_3.kt") + public void testKt21671_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt"); + doTest(fileName); + } + + @TestMetadata("samOnCallSite.kt") + public void testSamOnCallSite() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java index ac4e75e8ace..c8accabf5e9 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrCompileKotlinAgainstInlineKotlinTestGenerated.java @@ -455,6 +455,39 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC } } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sam extends AbstractIrCompileKotlinAgainstInlineKotlinTest { + public void testAllFilesPresentInSam() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/anonymousObject/sam"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("kt21671.kt") + public void testKt21671() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_2.kt") + public void testKt21671_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_3.kt") + public void testKt21671_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt"); + doTest(fileName); + } + + @TestMetadata("samOnCallSite.kt") + public void testSamOnCallSite() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java index ea779aa3313..81f5dbbbb09 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxInlineCodegenTestGenerated.java @@ -455,6 +455,39 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo } } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sam extends AbstractBlackBoxInlineCodegenTest { + public void testAllFilesPresentInSam() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/anonymousObject/sam"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("kt21671.kt") + public void testKt21671() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_2.kt") + public void testKt21671_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_3.kt") + public void testKt21671_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt"); + doTest(fileName); + } + + @TestMetadata("samOnCallSite.kt") + public void testSamOnCallSite() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java index 161065ffb40..c6b5e957bd8 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstInlineKotlinTestGenerated.java @@ -455,6 +455,39 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi } } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam") + @TestDataPath("$PROJECT_ROOT") + @RunWith(JUnit3RunnerWithInners.class) + public static class Sam extends AbstractCompileKotlinAgainstInlineKotlinTest { + public void testAllFilesPresentInSam() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/anonymousObject/sam"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true); + } + + @TestMetadata("kt21671.kt") + public void testKt21671() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_2.kt") + public void testKt21671_2() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_2.kt"); + doTest(fileName); + } + + @TestMetadata("kt21671_3.kt") + public void testKt21671_3() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/kt21671_3.kt"); + doTest(fileName); + } + + @TestMetadata("samOnCallSite.kt") + public void testSamOnCallSite() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/sam/samOnCallSite.kt"); + doTest(fileName); + } + } + @TestMetadata("compiler/testData/codegen/boxInline/anonymousObject/twoCapturedReceivers") @TestDataPath("$PROJECT_ROOT") @RunWith(JUnit3RunnerWithInners.class) diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java index 097e80fd0d0..db05999b1b0 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/CompileKotlinAgainstKotlinTestGenerated.java @@ -84,6 +84,12 @@ public class CompileKotlinAgainstKotlinTestGenerated extends AbstractCompileKotl doTest(fileName); } + @TestMetadata("copySamOnInline.kt") + public void testCopySamOnInline() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/compileKotlinAgainstKotlin/copySamOnInline.kt"); + doTest(fileName); + } + @TestMetadata("coroutinesBinary.kt") public void testCoroutinesBinary() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/compileKotlinAgainstKotlin/coroutinesBinary.kt");