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");