[KT-79276] fix(irValNotNull): prevent null check compiler crashes
diff --git a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSafeCallChainFoldingLowering.kt b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSafeCallChainFoldingLowering.kt index ab3196d..efa5c70 100644 --- a/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSafeCallChainFoldingLowering.kt +++ b/compiler/ir/backend.jvm/lower/src/org/jetbrains/kotlin/backend/jvm/lower/JvmSafeCallChainFoldingLowering.kt
@@ -118,10 +118,8 @@ IrConstImpl.boolean(startOffset, endOffset, context.irBuiltIns.booleanType, false) private fun irValNotNull(startOffset: Int, endOffset: Int, irVariable: IrVariable): IrExpression = - if (irVariable.type.isJvmNullable() || irVariable.initializer?.isConstantLike != true) - IrGetValueImpl(startOffset, endOffset, irVariable.symbol).irEqEqNull().irNot() - else - irTrue(startOffset, endOffset) + // Always generate a null check for safe calls to ensure bytecode is verifiable by D8/R8 + IrGetValueImpl(startOffset, endOffset, irVariable.symbol).irEqEqNull().irNot() private fun IrType.isJvmNullable(): Boolean = isNullable() || hasAnnotation(JvmAnnotationNames.ENHANCED_NULLABILITY_ANNOTATION)
diff --git a/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt b/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt new file mode 100644 index 0000000..340e80a --- /dev/null +++ b/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt
@@ -0,0 +1,44 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +class Container { + val d: String = "OK" +} + +class Wrapper { + var container: Container? = null +} + +fun getWrapper(flag: Boolean): Wrapper? { + return if (flag) Wrapper() else null +} + +fun box(): String { + // Create a potentially null wrapper + val wrapper = getWrapper(true) + + // Access container which is initially null + val container = wrapper?.container + + // Try to access field 'd' on the null container + // This should be safe in Kotlin due to null safety, but might generate problematic bytecode + val result = container?.d + + // Use a more complex pattern with lambdas and control flow + val complexResult = wrapper?.let { w -> + w.container?.let { c -> + c.d + } + } + + // Try with nested null checks and assignments + if (wrapper != null) { + wrapper.container = Container() + val c = wrapper.container + if (c != null) { + return c.d + } + } + + return "OK" +} \ No newline at end of file
diff --git a/compiler/testData/codegen/box/d8/nullCheckIssue.kt b/compiler/testData/codegen/box/d8/nullCheckIssue.kt new file mode 100644 index 0000000..eeca241 --- /dev/null +++ b/compiler/testData/codegen/box/d8/nullCheckIssue.kt
@@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +class Container { + val d: String = "OK" +} + +// This function returns a nullable Container, but its return type is not explicitly marked as nullable +// This might cause isJvmNullable() to return false for the return type +fun getContainer(): Container { + return if (System.currentTimeMillis() > 0) Container() else null as Container +} + +// This function returns a constant-like expression that might be null +// This might cause isConstantLike to return true for an expression that can be null +fun getConstantLikeContainer(): Container? { + val x: Any? = null + return x as? Container +} + +fun box(): String { + // Test case 1: Safe call on a value that might be null but whose type is not explicitly marked as nullable + val container1 = getContainer() + val result1 = try { + container1?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + // Test case 2: Safe call on a constant-like expression that might be null + val container2 = getConstantLikeContainer() + val result2 = try { + container2?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + // Test case 3: Chained safe calls with a mix of nullable and non-nullable types + val container3: Container? = if (System.currentTimeMillis() > 0) null else Container() + val wrapper = object { + val container: Container? = container3 + } + val result3 = try { + wrapper?.container?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + return "OK" +} \ No newline at end of file
diff --git a/compiler/testData/codegen/box/d8/nullFieldAccess.kt b/compiler/testData/codegen/box/d8/nullFieldAccess.kt new file mode 100644 index 0000000..e5c5978 --- /dev/null +++ b/compiler/testData/codegen/box/d8/nullFieldAccess.kt
@@ -0,0 +1,23 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +class Container { + val d: String = "OK" +} + +fun getContainer(flag: Boolean): Container? { + return if (flag) Container() else null +} + +fun box(): String { + // This should trigger a null check before accessing field 'd' + val container = getContainer(false) + + // Try to access field 'd' on potentially null container + // This should be safe in Kotlin due to null safety, but might generate problematic bytecode + return try { + container?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } +} \ No newline at end of file
diff --git a/proj/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt b/proj/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt new file mode 100644 index 0000000..340e80a --- /dev/null +++ b/proj/compiler/testData/codegen/box/d8/complexNullFieldAccess.kt
@@ -0,0 +1,44 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +class Container { + val d: String = "OK" +} + +class Wrapper { + var container: Container? = null +} + +fun getWrapper(flag: Boolean): Wrapper? { + return if (flag) Wrapper() else null +} + +fun box(): String { + // Create a potentially null wrapper + val wrapper = getWrapper(true) + + // Access container which is initially null + val container = wrapper?.container + + // Try to access field 'd' on the null container + // This should be safe in Kotlin due to null safety, but might generate problematic bytecode + val result = container?.d + + // Use a more complex pattern with lambdas and control flow + val complexResult = wrapper?.let { w -> + w.container?.let { c -> + c.d + } + } + + // Try with nested null checks and assignments + if (wrapper != null) { + wrapper.container = Container() + val c = wrapper.container + if (c != null) { + return c.d + } + } + + return "OK" +} \ No newline at end of file
diff --git a/proj/compiler/testData/codegen/box/d8/nullCheckIssue.kt b/proj/compiler/testData/codegen/box/d8/nullCheckIssue.kt new file mode 100644 index 0000000..eeca241 --- /dev/null +++ b/proj/compiler/testData/codegen/box/d8/nullCheckIssue.kt
@@ -0,0 +1,50 @@ +// TARGET_BACKEND: JVM_IR +// WITH_STDLIB + +class Container { + val d: String = "OK" +} + +// This function returns a nullable Container, but its return type is not explicitly marked as nullable +// This might cause isJvmNullable() to return false for the return type +fun getContainer(): Container { + return if (System.currentTimeMillis() > 0) Container() else null as Container +} + +// This function returns a constant-like expression that might be null +// This might cause isConstantLike to return true for an expression that can be null +fun getConstantLikeContainer(): Container? { + val x: Any? = null + return x as? Container +} + +fun box(): String { + // Test case 1: Safe call on a value that might be null but whose type is not explicitly marked as nullable + val container1 = getContainer() + val result1 = try { + container1?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + // Test case 2: Safe call on a constant-like expression that might be null + val container2 = getConstantLikeContainer() + val result2 = try { + container2?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + // Test case 3: Chained safe calls with a mix of nullable and non-nullable types + val container3: Container? = if (System.currentTimeMillis() > 0) null else Container() + val wrapper = object { + val container: Container? = container3 + } + val result3 = try { + wrapper?.container?.d ?: "OK" + } catch (e: Exception) { + e.toString() + } + + return "OK" +} \ No newline at end of file