Fix inline codegen on local functions inside inlined lambda

The problem was that anonymous classes wasn't regenerated
although they capture another anonymous class that is a subject
for regeneration

 #KT-8689 Fixed
This commit is contained in:
Denis Zharkov
2017-02-16 10:55:07 +03:00
parent dfb60ba4ac
commit e16b0524b6
7 changed files with 137 additions and 5 deletions
@@ -463,6 +463,7 @@ public class MethodInliner {
Map<Integer, LambdaInfo> lambdaMapping = new HashMap<Integer, LambdaInfo>();
int offset = 0;
boolean capturesAnonymousObjectThatMustBeRegenerated = false;
for (int i = 0; i < paramCount; i++) {
SourceValue sourceValue = frame.getStack(firstParameterIndex + i);
LambdaInfo lambdaInfo = MethodInlinerUtilKt.getLambdaIfExistsAndMarkInstructions(
@@ -471,12 +472,17 @@ public class MethodInliner {
if (lambdaInfo != null) {
lambdaMapping.put(offset, lambdaInfo);
}
else if (i < argTypes.length && isAnonymousClassThatMustBeRegenerated(argTypes[i])) {
capturesAnonymousObjectThatMustBeRegenerated = true;
}
offset += i == 0 ? 1 : argTypes[i - 1].getSize();
}
transformations.add(
buildConstructorInvocation(owner, desc, lambdaMapping, awaitClassReification)
buildConstructorInvocation(
owner, desc, lambdaMapping, awaitClassReification, capturesAnonymousObjectThatMustBeRegenerated
)
);
awaitClassReification = false;
}
@@ -538,6 +544,12 @@ public class MethodInliner {
return node;
}
private boolean isAnonymousClassThatMustBeRegenerated(@Nullable Type type) {
if (type == null || type.getSort() != Type.OBJECT) return false;
AnonymousObjectTransformationInfo info = inliningContext.findAnonymousObjectTransformationInfo(type.getInternalName());
return info != null && info.shouldRegenerate(true);
}
@NotNull
private Frame<SourceValue>[] analyzeMethodNodeBeforeInline(@NotNull MethodNode node) {
try {
@@ -584,7 +596,8 @@ public class MethodInliner {
@NotNull String anonymousType,
@NotNull String desc,
@NotNull Map<Integer, LambdaInfo> lambdaMapping,
boolean needReification
boolean needReification,
boolean capturesAnonymousObjectThatMustBeRegenerated
) {
boolean memoizeAnonymousObject = inliningContext.findAnonymousObjectTransformationInfo(anonymousType) == null;
@@ -594,7 +607,8 @@ public class MethodInliner {
isAlreadyRegenerated(anonymousType),
desc,
false,
inliningContext.nameGenerator
inliningContext.nameGenerator,
capturesAnonymousObjectThatMustBeRegenerated
);
if (memoizeAnonymousObject) {
@@ -68,7 +68,8 @@ class AnonymousObjectTransformationInfo internal constructor(
private val alreadyRegenerated: Boolean,
val constructorDesc: String?,
private val isStaticOrigin: Boolean,
parentNameGenerator: NameGenerator
parentNameGenerator: NameGenerator,
private val capturesAnonymousObjectThatMustBeRegenerated: Boolean = false
) : TransformationInfo {
override val nameGenerator by lazy {
@@ -93,7 +94,8 @@ class AnonymousObjectTransformationInfo internal constructor(
) : this(ownerInternalName, needReification, hashMapOf(), false, alreadyRegenerated, null, isStaticOrigin, nameGenerator)
override fun shouldRegenerate(sameModule: Boolean): Boolean =
!alreadyRegenerated && (!lambdasToInline.isEmpty() || !sameModule || capturedOuterRegenerated || needReification)
!alreadyRegenerated &&
(!lambdasToInline.isEmpty() || !sameModule || capturedOuterRegenerated || needReification || capturesAnonymousObjectThatMustBeRegenerated)
override fun canRemoveAfterTransformation(): Boolean {
// Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
@@ -0,0 +1,27 @@
// FILE: 1.kt
package test
public inline fun myRun(block: () -> Unit) {
return block()
}
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
var res = ""
myRun {
val x = object {
fun foo() {
res = "OK"
}
}
object {
fun bar() = x.foo()
}.bar()
}
return res
}
@@ -0,0 +1,27 @@
// FILE: 1.kt
package test
public inline fun myRun(block: () -> Unit) {
return block()
}
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
var res = ""
myRun {
fun f1() {
res = "OK"
}
val x: () -> Unit = {
f1()
}
x()
}
return res
}
@@ -0,0 +1,26 @@
// FILE: 1.kt
package test
public inline fun myRun(block: () -> Unit) {
return block()
}
// FILE: 2.kt
//NO_CHECK_LAMBDA_INLINING
import test.*
fun box(): String {
var res = ""
myRun {
fun f1() {
res = "OK"
}
fun f2() {
f1()
}
f2()
}
return res
}
@@ -200,6 +200,12 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
doTest(fileName);
}
@TestMetadata("objectInLambdaCapturesAnotherObject.kt")
public void testObjectInLambdaCapturesAnotherObject() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/objectInLambdaCapturesAnotherObject.kt");
doTest(fileName);
}
@TestMetadata("safeCall.kt")
public void testSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/safeCall.kt");
@@ -1151,11 +1157,23 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/localFunInLambda"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("lambdaInLambdaCapturesAnotherFun.kt")
public void testLambdaInLambdaCapturesAnotherFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/lambdaInLambdaCapturesAnotherFun.kt");
doTest(fileName);
}
@TestMetadata("localFunInLambda.kt")
public void testLocalFunInLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/localFunInLambda.kt");
doTest(fileName);
}
@TestMetadata("localFunInLambdaCapturesAnotherFun.kt")
public void testLocalFunInLambdaCapturesAnotherFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/localFunInLambdaCapturesAnotherFun.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/multifileClasses")
@@ -200,6 +200,12 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
doTest(fileName);
}
@TestMetadata("objectInLambdaCapturesAnotherObject.kt")
public void testObjectInLambdaCapturesAnotherObject() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/objectInLambdaCapturesAnotherObject.kt");
doTest(fileName);
}
@TestMetadata("safeCall.kt")
public void testSafeCall() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/anonymousObject/safeCall.kt");
@@ -1151,11 +1157,23 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/boxInline/localFunInLambda"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.ANY, true);
}
@TestMetadata("lambdaInLambdaCapturesAnotherFun.kt")
public void testLambdaInLambdaCapturesAnotherFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/lambdaInLambdaCapturesAnotherFun.kt");
doTest(fileName);
}
@TestMetadata("localFunInLambda.kt")
public void testLocalFunInLambda() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/localFunInLambda.kt");
doTest(fileName);
}
@TestMetadata("localFunInLambdaCapturesAnotherFun.kt")
public void testLocalFunInLambdaCapturesAnotherFun() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/boxInline/localFunInLambda/localFunInLambdaCapturesAnotherFun.kt");
doTest(fileName);
}
}
@TestMetadata("compiler/testData/codegen/boxInline/multifileClasses")