Add null checks in constructors taking value class types

^KT-53492: Fixed
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 d899808..0e1ab47 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
@@ -32574,6 +32574,12 @@
         }
 
         @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 {
             runTest("compiler/testData/codegen/box/notNullAssertions/definitelyNotNullTypes.kt");
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 766ecd2..fbd701e 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 @@
 
     // * 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 a365a8d..7dc5d26 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.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 @@
         }
 
         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 @@
     }
 
     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, IrConstructor>
+    ): 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 cf56f48..a21a071 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 @@
     val multifileFacadeClassForPart = mutableMapOf<IrClass, IrClass>()
     val multifileFacadeMemberToPartMember = mutableMapOf<IrSimpleFunction, IrSimpleFunction>()
 
-    val hiddenConstructors = ConcurrentHashMap<IrConstructor, IrConstructor>()
+    val hiddenConstructorsWithMangledParams = ConcurrentHashMap<IrConstructor, IrConstructor>()
+    val hiddenConstructorsOfSealedClasses = ConcurrentHashMap<IrConstructor, IrConstructor>()
 
     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 0000000..8ae3504
--- /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 5b04fa7..4f0b477 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
@@ -32574,6 +32574,12 @@
         }
 
         @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 {
             runTest("compiler/testData/codegen/box/notNullAssertions/definitelyNotNullTypes.kt");