JVM IR: do not generate hidden constructor for inline classes more than once

SyntheticAccessorLowering was initially implemented under the assumption
that any access to an invisible declaration will cause an accessor to be
generated _in the same file_. Moreover, it's declared in the group of
phases that are performed by file.

But this assumption is incorrect for constructors which need to be
hidden (those which take parameters of inline class types), since such
constructor is public and can be called from anywhere. In this case,
SyntheticAccessorLowering actually generated a new accessor for the
hidden constructor for each (!) source file where that constructor is
called, which led to ClassFormatError because of the class file having
multiple methods with the same signature. The internal `functionMap`
cache didn't help because it's not shared among phase instances for
different files (well, it helped to generate not more than one accessor
per usage-file).

In this change, we use the global cache, stored in JvmBackendContext,
for accessors to hidden constructors. Note that after this change, calls
to hidden constructors are always transformed to the corresponding
accessor in SyntheticAccessorLowering right away, but that accessor
might be orphaned for a while (not declared in any parent's
declarations). Only when SyntheticAccessorLowering encounters the
original constructor which needs to be hidden, it adds the accessor
beside it.

The test is sensitive to the file order, so both variants are added.
This commit is contained in:
Alexander Udalov
2020-01-02 14:39:32 +01:00
parent a339e7af19
commit 957b100cd1
11 changed files with 137 additions and 50 deletions
@@ -29,6 +29,7 @@ import org.jetbrains.kotlin.ir.builders.IrBuilderWithScope
import org.jetbrains.kotlin.ir.builders.irBlock import org.jetbrains.kotlin.ir.builders.irBlock
import org.jetbrains.kotlin.ir.builders.irNull import org.jetbrains.kotlin.ir.builders.irNull
import org.jetbrains.kotlin.ir.declarations.* import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrConstructorImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrExpression import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.IrFunctionReference import org.jetbrains.kotlin.ir.expressions.IrFunctionReference
@@ -92,6 +93,8 @@ class JvmBackendContext(
internal val multifileFacadeForPart = mutableMapOf<IrClass, JvmClassName>() internal val multifileFacadeForPart = mutableMapOf<IrClass, JvmClassName>()
internal val multifileFacadeMemberToPartMember = mutableMapOf<IrFunction, IrFunction>() internal val multifileFacadeMemberToPartMember = mutableMapOf<IrFunction, IrFunction>()
internal val hiddenConstructors = mutableMapOf<IrConstructor, IrConstructorImpl>()
override var inVerbosePhase: Boolean = false override var inVerbosePhase: Boolean = false
override val configuration get() = state.configuration override val configuration get() = state.configuration
@@ -11,7 +11,6 @@ import org.jetbrains.kotlin.backend.jvm.JvmSymbols
import org.jetbrains.kotlin.backend.jvm.codegen.isInlineOnly import org.jetbrains.kotlin.backend.jvm.codegen.isInlineOnly
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory import org.jetbrains.kotlin.backend.jvm.descriptors.JvmDeclarationFactory
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledParameters
import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.unboxInlineClass
import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
@@ -141,10 +140,6 @@ fun IrType.getArrayElementType(irBuiltIns: IrBuiltIns): IrType =
val IrStatementOrigin?.isLambda: Boolean val IrStatementOrigin?.isLambda: Boolean
get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION get() = this == IrStatementOrigin.LAMBDA || this == IrStatementOrigin.ANONYMOUS_FUNCTION
val IrConstructor.shouldBeHidden: Boolean
get() = !Visibilities.isPrivate(visibility) && !constructedClass.isInline && hasMangledParameters &&
origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER
// An IR builder with a reference to the JvmBackendContext // An IR builder with a reference to the JvmBackendContext
class JvmIrBuilder( class JvmIrBuilder(
val backendContext: JvmBackendContext, val backendContext: JvmBackendContext,
@@ -15,7 +15,7 @@ import org.jetbrains.kotlin.backend.jvm.intrinsics.receiverAndArgs
import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator import org.jetbrains.kotlin.backend.jvm.ir.IrInlineReferenceLocator
import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault import org.jetbrains.kotlin.backend.jvm.ir.hasJvmDefault
import org.jetbrains.kotlin.backend.jvm.ir.isLambda import org.jetbrains.kotlin.backend.jvm.ir.isLambda
import org.jetbrains.kotlin.backend.jvm.ir.shouldBeHidden import org.jetbrains.kotlin.backend.jvm.lower.inlineclasses.hasMangledParameters
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.descriptors.Visibility import org.jetbrains.kotlin.descriptors.Visibility
@@ -57,20 +57,30 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
return super.visitFunctionAccess(expression) return super.visitFunctionAccess(expression)
} }
val callee = expression.symbol.owner
val withSuper = (expression as? IrCall)?.superQualifierSymbol != null val withSuper = (expression as? IrCall)?.superQualifierSymbol != null
val thisSymbol = (expression as? IrCall)?.dispatchReceiver?.type?.classifierOrNull as? IrClassSymbol val thisSymbol = (expression as? IrCall)?.dispatchReceiver?.type?.classifierOrNull as? IrClassSymbol
if (expression.symbol.isAccessible(withSuper, thisSymbol)) {
return super.visitFunctionAccess(expression) val accessor = when {
} callee is IrConstructor && callee.isOrShouldBeHidden ->
return super.visitExpression( handleHiddenConstructor(callee).symbol
modifyFunctionAccessExpression(expression, functionMap.getOrPut(expression.symbol) {
when (val symbol = expression.symbol) { !expression.symbol.isAccessible(withSuper, thisSymbol) ->
is IrConstructorSymbol -> symbol.owner.makeConstructorAccessor().symbol functionMap.getOrPut(expression.symbol) {
is IrSimpleFunctionSymbol -> symbol.owner.makeSimpleFunctionAccessor(expression as IrCall).symbol when (val symbol = expression.symbol) {
else -> error("Unknown subclass of IrFunctionSymbol") is IrConstructorSymbol -> symbol.owner.makeConstructorAccessor().also { accessor ->
pendingTransformations.add {
(accessor.parent as IrDeclarationContainer).declarations.add(accessor)
}
}.symbol
is IrSimpleFunctionSymbol -> symbol.owner.makeSimpleFunctionAccessor(expression as IrCall).symbol
else -> error("Unknown subclass of IrFunctionSymbol")
}
} }
})
) else -> return super.visitFunctionAccess(expression)
}
return super.visitExpression(modifyFunctionAccessExpression(expression, accessor))
} }
override fun visitGetField(expression: IrGetField) = super.visitExpression( override fun visitGetField(expression: IrGetField) = super.visitExpression(
@@ -90,25 +100,26 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
) )
override fun visitConstructor(declaration: IrConstructor): IrStatement { override fun visitConstructor(declaration: IrConstructor): IrStatement {
handleHiddenConstructor(declaration) if (declaration.isOrShouldBeHidden) {
return super.visitConstructor(declaration) pendingTransformations.add {
} declaration.parentAsClass.declarations.add(handleHiddenConstructor(declaration))
}
declaration.visibility = Visibilities.PRIVATE
}
override fun visitConstructorCall(expression: IrConstructorCall): IrExpression { return super.visitConstructor(declaration)
handleHiddenConstructor(expression.symbol.owner)
return super.visitConstructorCall(expression)
} }
override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { override fun visitFunctionReference(expression: IrFunctionReference): IrExpression {
val function = expression.symbol.owner val function = expression.symbol.owner
if (!expression.origin.isLambda && function is IrConstructor) { if (!expression.origin.isLambda && function is IrConstructor && function.isOrShouldBeHidden) {
handleHiddenConstructor(function)?.let { accessor -> handleHiddenConstructor(function).let { accessor ->
expression.transformChildrenVoid() expression.transformChildrenVoid()
return IrFunctionReferenceImpl( return IrFunctionReferenceImpl(
expression.startOffset, expression.endOffset, expression.type, expression.startOffset, expression.endOffset, expression.type,
accessor, accessor.owner.typeParameters.size, accessor.symbol, accessor.typeParameters.size,
accessor.owner.valueParameters.size, expression.origin accessor.valueParameters.size, expression.origin
) )
} }
} }
@@ -116,28 +127,28 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
return super.visitFunctionReference(expression) return super.visitFunctionReference(expression)
} }
private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructorSymbol? { private val IrConstructor.isOrShouldBeHidden: Boolean
functionMap[declaration.symbol]?.let { return it as IrConstructorSymbol } get() = this in context.hiddenConstructors || (
!Visibilities.isPrivate(visibility) && !constructedClass.isInline && hasMangledParameters &&
origin != IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER &&
origin != JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
if (!declaration.shouldBeHidden) private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructorImpl {
return null require(declaration.isOrShouldBeHidden, declaration::render)
return context.hiddenConstructors.getOrPut(declaration) {
declaration.visibility = Visibilities.PRIVATE declaration.makeConstructorAccessor().also { accessor ->
// There's a special case in the JVM backend for serializing the metadata of hidden
return declaration.makeConstructorAccessor().also { accessor -> // constructors - we serialize the descriptor of the original constructor, but the
functionMap[declaration.symbol] = accessor.symbol // signature of the accessor. We implement this special case in the JVM IR backend by
// attaching the metadata directly to the accessor. We also have to move all annotations
// There's a special case in the JVM backend for serializing the metadata of hidden // to the accessor. Parameter annotations are already moved by the copyTo method.
// constructors - we serialize the descriptor of the original constructor, but the accessor.metadata = declaration.metadata
// signature of the bridge. We implement this special case in the JVM IR backend by declaration.safeAs<IrConstructorImpl>()?.metadata = null
// attaching the metadata directly to the bridge. We also have to move all annotations accessor.annotations += declaration.annotations
// to the bridge method. Parameter annotations are already moved by the copyTo method. declaration.annotations.clear()
accessor.metadata = declaration.metadata declaration.valueParameters.forEach { it.annotations.clear() }
declaration.safeAs<IrConstructorImpl>()?.metadata = null }
accessor.annotations += declaration.annotations }
declaration.annotations.clear()
declaration.valueParameters.forEach { it.annotations.clear() }
}.symbol
} }
// In case of Java `protected static`, access could be done from a public inline function in the same package, // In case of Java `protected static`, access could be done from a public inline function in the same package,
@@ -155,10 +166,8 @@ internal class SyntheticAccessorLowering(val context: JvmBackendContext) : IrEle
origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR origin = JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR
name = source.name name = source.name
visibility = Visibilities.PUBLIC visibility = Visibilities.PUBLIC
}.also { accessor -> }.also { accessor ->
accessor.parent = source.parent accessor.parent = source.parent
pendingTransformations.add { (accessor.parent as IrDeclarationContainer).declarations.add(accessor) }
accessor.copyTypeParametersFrom(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR) accessor.copyTypeParametersFrom(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
accessor.copyValueParametersToStatic(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR) accessor.copyValueParametersToStatic(source, JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR)
@@ -0,0 +1,10 @@
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: 1.kt
fun box(): String = X(Z("OK")).z.result
// FILE: 2.kt
inline class Z(val result: String)
class X(val z: Z)
@@ -0,0 +1,10 @@
// IGNORE_BACKEND_FIR: JVM_IR
// FILE: 2.kt
fun box(): String = X(Z("OK")).z.result
// FILE: 1.kt
inline class Z(val result: String)
class X(val z: Z)
@@ -13723,6 +13723,16 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
@@ -13723,6 +13723,16 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
@@ -12573,6 +12573,16 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
@@ -12573,6 +12573,16 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
@@ -10888,6 +10888,16 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");
@@ -12028,6 +12028,16 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true); KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/inlineClasses/hiddenConstructor"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
} }
@TestMetadata("constructorReferencedFromOtherFile1.kt")
public void testConstructorReferencedFromOtherFile1() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile1.kt");
}
@TestMetadata("constructorReferencedFromOtherFile2.kt")
public void testConstructorReferencedFromOtherFile2() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorReferencedFromOtherFile2.kt");
}
@TestMetadata("constructorWithDefaultParameters.kt") @TestMetadata("constructorWithDefaultParameters.kt")
public void testConstructorWithDefaultParameters() throws Exception { public void testConstructorWithDefaultParameters() throws Exception {
runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt"); runTest("compiler/testData/codegen/box/inlineClasses/hiddenConstructor/constructorWithDefaultParameters.kt");