JVM: regenerate objects if they have been regenerated in parent contexts

This is a hack to work around the fact that type mappings should not be
inherited by inlining contexts for lambdas called from anonymous
objects. As the lambda can call the inline function again, this could
produce a reference to the original object, which is remapped to a new
type in the parent context. Unfortunately, there are many redundant
`MethodRemapper`s between the lambda and the class file, so simply
editing `TypeRemapper` does not work. Hence, this hack. For now.

(Issue found by compiling IntelliJ IDEA BTW.)
This commit is contained in:
pyos
2021-01-05 13:09:10 +01:00
committed by max-kammerer
parent ef36b81c67
commit 4a76ea6ecb
13 changed files with 89 additions and 29 deletions
@@ -157,13 +157,8 @@ class MethodInliner(
// MethodRemapper doesn't extends LocalVariablesSorter, but RemappingMethodAdapter does.
// So wrapping with LocalVariablesSorter to keep old behavior
// TODO: investigate LocalVariablesSorter removing (see also same code in RemappingClassBuilder.java)
val remappingMethodAdapter = MethodRemapper(
LocalVariablesSorter(
resultNode.access,
resultNode.desc,
wrapWithMaxLocalCalc(resultNode)
), AsmTypeRemapper(remapper, result)
)
val localVariablesSorter = LocalVariablesSorter(resultNode.access, resultNode.desc, wrapWithMaxLocalCalc(resultNode))
val remappingMethodAdapter = MethodRemapper(localVariablesSorter, AsmTypeRemapper(remapper, result))
val fakeContinuationName = CoroutineTransformer.findFakeContinuationConstructorClassName(node)
val markerShift = calcMarkerShift(parameters, node)
@@ -174,7 +169,9 @@ class MethodInliner(
transformationInfo = iterator.next()
val oldClassName = transformationInfo!!.oldClassName
if (transformationInfo!!.shouldRegenerate(isSameModule)) {
if (inliningContext.parent?.transformationInfo?.oldClassName == oldClassName) {
// Object constructs itself, don't enter an infinite recursion of regeneration.
} else if (transformationInfo!!.shouldRegenerate(isSameModule)) {
//TODO: need poping of type but what to do with local funs???
val newClassName = transformationInfo!!.newClassName
remapper.addMapping(oldClassName, newClassName)
@@ -206,7 +203,7 @@ class MethodInliner(
ReifiedTypeInliner.putNeedClassReificationMarker(mv)
result.reifiedTypeParametersUsages.mergeAll(transformResult.reifiedTypeParametersUsages)
}
} else if (!transformationInfo!!.wasAlreadyRegenerated) {
} else {
result.addNotChangedClass(oldClassName)
}
}
@@ -300,7 +297,7 @@ class MethodInliner(
val varRemapper = LocalVarRemapper(lambdaParameters, valueParamShift)
//TODO add skipped this and receiver
val lambdaResult = inliner.doInline(this.mv, varRemapper, true, info, invokeCall.finallyDepthShift)
val lambdaResult = inliner.doInline(localVariablesSorter, varRemapper, true, info, invokeCall.finallyDepthShift)
result.mergeWithNotChangeInfo(lambdaResult)
result.reifiedTypeParametersUsages.mergeAll(lambdaResult.reifiedTypeParametersUsages)
@@ -312,19 +309,19 @@ class MethodInliner(
inlineOnlySmapSkipper?.onInlineLambdaEnd(remappingMethodAdapter)
} else if (isAnonymousConstructorCall(owner, name)) { //TODO add method
//TODO add proper message
var info = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError(
val newInfo = transformationInfo as? AnonymousObjectTransformationInfo ?: throw AssertionError(
"<init> call doesn't correspond to object transformation info for '$owner.$name': $transformationInfo"
)
val objectConstructsItself = inlineCallSiteInfo.ownerClassName == info.oldClassName
if (objectConstructsItself) {
// inline fun f() -> new f$1 -> fun something() in class f$1 -> new f$1
// ^-- fetch the info that was created for this instruction
info = inliningContext.parent?.transformationInfo as? AnonymousObjectTransformationInfo
?: throw AssertionError("anonymous object $owner constructs itself, but we have no info on in")
}
// inline fun f() -> new f$1 -> fun something() in class f$1 -> new f$1
// ^-- fetch the info that was created for this instruction
// Currently, this self-reference pattern only happens in coroutine objects, which construct
// copies of themselves in `create` or `invoke` (not `invokeSuspend`).
val existingInfo = inliningContext.parent?.transformationInfo?.takeIf { it.oldClassName == newInfo.oldClassName }
as AnonymousObjectTransformationInfo?
val info = existingInfo ?: newInfo
if (info.shouldRegenerate(isSameModule)) {
for (capturedParamDesc in info.allRecapturedParameters) {
val realDesc = if (objectConstructsItself && capturedParamDesc.fieldName == AsmUtil.THIS) {
val realDesc = if (existingInfo != null && capturedParamDesc.fieldName == AsmUtil.THIS) {
// The captures in `info` are relative to the parent context, so a normal `this` there
// is a captured outer `this` here.
CapturedParamDesc(Type.getObjectType(owner), AsmUtil.CAPTURED_THIS_FIELD, capturedParamDesc.type)
@@ -26,10 +26,6 @@ interface TransformationInfo {
val nameGenerator: NameGenerator
val wasAlreadyRegenerated: Boolean
get() = false
fun shouldRegenerate(sameModule: Boolean): Boolean
fun canRemoveAfterTransformation(): Boolean
@@ -86,9 +82,6 @@ class AnonymousObjectTransformationInfo internal constructor(
lateinit var capturedLambdasToInline: Map<String, LambdaInfo>
override val wasAlreadyRegenerated: Boolean
get() = alreadyRegenerated
constructor(
ownerInternalName: String,
needReification: Boolean,
@@ -97,9 +90,14 @@ class AnonymousObjectTransformationInfo internal constructor(
nameGenerator: NameGenerator
) : this(ownerInternalName, needReification, hashMapOf(), false, alreadyRegenerated, null, isStaticOrigin, nameGenerator)
override fun shouldRegenerate(sameModule: Boolean): Boolean = !alreadyRegenerated &&
(!sameModule || capturedOuterRegenerated || needReification || capturesAnonymousObjectThatMustBeRegenerated ||
functionalArguments.values.any { it != NonInlineableArgumentForInlineableParameterCalledInSuspend })
// TODO: unconditionally regenerating an object if it has previously been regenerated is a hack that works around
// the fact that TypeRemapper cannot differentiate between different references to the same object. See the test
// boxInline/anonymousObject/constructOriginalInRegenerated.kt for an example where a single anonymous object
// is referenced twice with otherwise different `shouldRegenerate` results and the inliner gets confused, trying
// to map the inner reference to the outer regenerated type and producing an infinite recursion.
override fun shouldRegenerate(sameModule: Boolean): Boolean = alreadyRegenerated ||
!sameModule || capturedOuterRegenerated || needReification || capturesAnonymousObjectThatMustBeRegenerated ||
functionalArguments.values.any { it != NonInlineableArgumentForInlineableParameterCalledInSuspend }
override fun canRemoveAfterTransformation(): Boolean {
// Note: It is unsafe to remove anonymous class that is referenced by GETSTATIC within lambda
@@ -102,6 +102,11 @@ public class FirBlackBoxInlineCodegenTestGenerated extends AbstractFirBlackBoxIn
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -0,0 +1,15 @@
// FILE: 1.kt
// Intentionally in the same package, as objects in other packages are always regenerated.
fun f(x: () -> String) = x()
inline fun g(crossinline x: () -> String) = f { x() }
// FILE: 2.kt
// _1Kt$g$1 not regenerated because the original already does the same thing (invoking a functional object)
// \-v
fun h(x: () -> String) = g { g(x) }
// /-^
// _1Kt$g$1 regenerated to inline the lambda
fun box() = h { "OK" }
@@ -102,6 +102,11 @@ public class BlackBoxInlineCodegenTestGenerated extends AbstractBlackBoxInlineCo
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class CompileKotlinAgainstInlineKotlinTestGenerated extends AbstractCompi
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class IrBlackBoxInlineCodegenTestGenerated extends AbstractIrBlackBoxInli
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class IrCompileKotlinAgainstInlineKotlinTestGenerated extends AbstractIrC
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class JvmIrAgainstOldBoxInlineTestGenerated extends AbstractJvmIrAgainstO
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class JvmOldAgainstIrBoxInlineTestGenerated extends AbstractJvmOldAgainst
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class IrJsCodegenInlineES6TestGenerated extends AbstractIrJsCodegenInline
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class IrJsCodegenInlineTestGenerated extends AbstractIrJsCodegenInlineTes
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");
@@ -102,6 +102,11 @@ public class JsCodegenInlineTestGenerated extends AbstractJsCodegenInlineTest {
runTest("compiler/testData/codegen/boxInline/anonymousObject/changingReturnType.kt");
}
@TestMetadata("constructOriginalInRegenerated.kt")
public void testConstructOriginalInRegenerated() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructOriginalInRegenerated.kt");
}
@TestMetadata("constructorVisibility.kt")
public void testConstructorVisibility() throws Exception {
runTest("compiler/testData/codegen/boxInline/anonymousObject/constructorVisibility.kt");