diff --git a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java index b6b8a02fe37..bc0922b7302 100644 --- a/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java +++ b/compiler/fir/fir2ir/tests-gen/org/jetbrains/kotlin/test/runners/codegen/FirBlackBoxCodegenTestGenerated.java @@ -32585,6 +32585,12 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt"); } + @Test + @TestMetadata("constructorWithMangledParams.kt") + public void testConstructorWithMangledParams() throws Exception { + runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); + } + @Test @TestMetadata("definitelyNotNullTypes.kt") public void testDefinitelyNotNullTypes() throws Exception { diff --git a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt index 766ecd22a14..fbd701ee832 100644 --- a/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt +++ b/compiler/ir/backend.jvm/codegen/src/org/jetbrains/kotlin/backend/jvm/codegen/ExpressionCodegen.kt @@ -322,10 +322,12 @@ class ExpressionCodegen( // * Operator functions require non-null assertions on parameters even if they are private. // * Local function for lambda survives at this stage if it was used in 'invokedynamic'-based code. - // Such functions require non-null assertions on parameters. - private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction) = - irFunction is IrSimpleFunction && irFunction.isOperator || - irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA + // * Hidden constructors with mangled parameters require non-null assertions (see KT-53492) + private fun shouldGenerateNonNullAssertionsForPrivateFun(irFunction: IrFunction): Boolean { + if (irFunction is IrSimpleFunction && irFunction.isOperator || irFunction.origin == IrDeclarationOrigin.LOCAL_FUNCTION_FOR_LAMBDA) return true + if (context.hiddenConstructorsWithMangledParams.containsKey(irFunction)) return true + return false + } private fun generateNonNullAssertion(param: IrValueParameter) { if (param.origin == JvmLoweredDeclarationOrigin.FIELD_FOR_OUTER_THIS || diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt index a365a8de7a9..7dc5d265fd9 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/SyntheticAccessorLowering.kt @@ -33,6 +33,7 @@ import org.jetbrains.kotlin.load.java.JavaDescriptorVisibilities import org.jetbrains.kotlin.load.java.JvmAbi import org.jetbrains.kotlin.name.Name import org.jetbrains.org.objectweb.asm.Opcodes +import java.util.concurrent.ConcurrentHashMap internal class SyntheticAccessorLowering(val context: JvmBackendContext) : FileLoweringPass { override fun lower(irFile: IrFile) { @@ -142,8 +143,10 @@ private class SyntheticAccessorTransformer( } val accessor = when { - callee is IrConstructor && callee.isOrShouldBeHidden -> - handleHiddenConstructor(callee).symbol + callee is IrConstructor && callee.isOrShouldBeHiddenAsSealedClassConstructor -> + handleHiddenConstructorOfSealedClass(callee).symbol + callee is IrConstructor && callee.isOrShouldBeHiddenSinceHasMangledParams -> + handleHiddenConstructorWithMangledParams(callee).symbol !expression.symbol.isAccessible(withSuper, thisSymbol) -> createAccessor(expression) else -> @@ -331,59 +334,71 @@ private class SyntheticAccessorTransformer( } override fun visitConstructor(declaration: IrConstructor): IrStatement { - if (declaration.isOrShouldBeHidden) { - pendingAccessorsToAdd.add(handleHiddenConstructor(declaration)) - declaration.visibility = DescriptorVisibilities.PRIVATE + when { + declaration.isOrShouldBeHiddenSinceHasMangledParams -> { + pendingAccessorsToAdd.add(handleHiddenConstructorWithMangledParams(declaration)) + declaration.visibility = DescriptorVisibilities.PRIVATE + } + declaration.isOrShouldBeHiddenAsSealedClassConstructor -> { + pendingAccessorsToAdd.add(handleHiddenConstructorOfSealedClass(declaration)) + declaration.visibility = DescriptorVisibilities.PRIVATE + } } - return super.visitConstructor(declaration) } override fun visitFunctionReference(expression: IrFunctionReference): IrExpression { val function = expression.symbol.owner - if (!expression.origin.isLambda && function is IrConstructor && function.isOrShouldBeHidden) { - handleHiddenConstructor(function).let { accessor -> - expression.transformChildrenVoid() - return IrFunctionReferenceImpl( - expression.startOffset, expression.endOffset, expression.type, - accessor.symbol, accessor.typeParameters.size, - accessor.valueParameters.size, accessor.symbol, expression.origin - ) - } + if (!expression.origin.isLambda && function is IrConstructor + && (function.isOrShouldBeHiddenSinceHasMangledParams || function.isOrShouldBeHiddenAsSealedClassConstructor) + ) { + val accessor = + if (function.isOrShouldBeHiddenSinceHasMangledParams) + handleHiddenConstructorWithMangledParams(function) + else + handleHiddenConstructorOfSealedClass(function) + expression.transformChildrenVoid() + return IrFunctionReferenceImpl( + expression.startOffset, expression.endOffset, expression.type, + accessor.symbol, accessor.typeParameters.size, + accessor.valueParameters.size, accessor.symbol, expression.origin + ) } return super.visitFunctionReference(expression) } - private val IrConstructor.isOrShouldBeHidden: Boolean + private val IrConstructor.isOrShouldBeHiddenSinceHasMangledParams: Boolean get() { - if (this in context.hiddenConstructors.keys) - return true - - if (origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || - origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR || - origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR || - origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB - ) { - return false - } - - val constructedClass = constructedClass - - if (!DescriptorVisibilities.isPrivate(visibility) && !constructedClass.isValue && hasMangledParameters() && - !constructedClass.isAnonymousObject - ) return true - - if (visibility != DescriptorVisibilities.PUBLIC && constructedClass.modality == Modality.SEALED) - return true - - return false + if (this in context.hiddenConstructorsWithMangledParams.keys) return true + return isOrShouldBeHiddenDueToOrigin && !DescriptorVisibilities.isPrivate(visibility) + && !constructedClass.isValue && hasMangledParameters() && !constructedClass.isAnonymousObject } - private fun handleHiddenConstructor(declaration: IrConstructor): IrConstructor { - require(declaration.isOrShouldBeHidden, declaration::render) - return context.hiddenConstructors.getOrPut(declaration) { + private val IrConstructor.isOrShouldBeHiddenAsSealedClassConstructor: Boolean + get() { + if (this in context.hiddenConstructorsOfSealedClasses.keys) return true + return isOrShouldBeHiddenDueToOrigin && visibility != DescriptorVisibilities.PUBLIC && constructedClass.modality == Modality.SEALED + } + + private val IrConstructor.isOrShouldBeHiddenDueToOrigin: Boolean + get() = !(origin == IrDeclarationOrigin.FUNCTION_FOR_DEFAULT_PARAMETER || + origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR || + origin == JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR || + origin == IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB) + + private fun handleHiddenConstructorWithMangledParams(declaration: IrConstructor) = + handleHiddenConstructor(declaration, context.hiddenConstructorsWithMangledParams) + + private fun handleHiddenConstructorOfSealedClass(declaration: IrConstructor) = + handleHiddenConstructor(declaration, context.hiddenConstructorsOfSealedClasses) + + private fun handleHiddenConstructor( + declaration: IrConstructor, + constructorToAccessorMap: ConcurrentHashMap + ): IrConstructor { + return constructorToAccessorMap.getOrPut(declaration) { declaration.makeConstructorAccessor(JvmLoweredDeclarationOrigin.SYNTHETIC_ACCESSOR_FOR_HIDDEN_CONSTRUCTOR).also { accessor -> if (declaration.constructedClass.modality != Modality.SEALED) { // There's a special case in the JVM backend for serializing the metadata of hidden diff --git a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt index cf56f48a706..a21a0718f26 100644 --- a/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt +++ b/compiler/ir/backend.jvm/src/org/jetbrains/kotlin/backend/jvm/JvmBackendContext.kt @@ -114,7 +114,8 @@ class JvmBackendContext( val multifileFacadeClassForPart = mutableMapOf() val multifileFacadeMemberToPartMember = mutableMapOf() - val hiddenConstructors = ConcurrentHashMap() + val hiddenConstructorsWithMangledParams = ConcurrentHashMap() + val hiddenConstructorsOfSealedClasses = ConcurrentHashMap() val collectionStubComputer = CollectionStubComputer(this) diff --git a/compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt b/compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt new file mode 100644 index 00000000000..8ae350438a7 --- /dev/null +++ b/compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt @@ -0,0 +1,36 @@ +// WITH_REFLECT +// FULL_JDK +// WORKS_WHEN_VALUE_CLASS +// LANGUAGE: +ValueClasses +// TARGET_BACKEND: JVM_IR + +import java.lang.NullPointerException +import java.lang.reflect.InvocationTargetException +import kotlin.reflect.jvm.isAccessible + + +OPTIONAL_JVM_INLINE_ANNOTATION +value class IC(val str: String) + +class A(val a: IC, val x : String) { + fun foo() = "$a$x" + + private constructor(x: IC) : this(IC(""), "") +} + +inline fun assertThrowsExpectedException(block: () -> Unit): Boolean { + try { + block() + } catch (t: Throwable) { + return t is InvocationTargetException && t.targetException is NullPointerException + } + return false +} + +fun box(): String { + if (!assertThrowsExpectedException { ::A.call(null, "").foo() }) return "Fail 1" + if (!assertThrowsExpectedException { ::A.call(IC(""), null).foo() }) return "Fail 2" + val privateConstructor = A::class.constructors.single { it.parameters.size == 1 } + privateConstructor.also { it.isAccessible = true }.call(null).foo() + return "OK" +} \ No newline at end of file diff --git a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java index d9bff85892a..84dd63a7498 100644 --- a/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-common-new/tests-gen/org/jetbrains/kotlin/test/runners/codegen/IrBlackBoxCodegenTestGenerated.java @@ -32585,6 +32585,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes runTest("compiler/testData/codegen/box/notNullAssertions/callAssertions.kt"); } + @Test + @TestMetadata("constructorWithMangledParams.kt") + public void testConstructorWithMangledParams() throws Exception { + runTest("compiler/testData/codegen/box/notNullAssertions/constructorWithMangledParams.kt", TransformersFunctions.getReplaceOptionalJvmInlineAnnotationWithReal()); + } + @Test @TestMetadata("definitelyNotNullTypes.kt") public void testDefinitelyNotNullTypes() throws Exception {