diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt index 381485cd41f..125f37951d5 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/Equals.kt @@ -37,7 +37,7 @@ class Equals : IntrinsicMethod() { } } -class ConsistentEquals(private val lhsType: Type) : IntrinsicMethod() { +class EqualsThrowingNpeForNullReceiver(private val lhsType: Type) : IntrinsicMethod() { override fun toCallable(method: CallableMethod): Callable = createBinaryIntrinsicCallable( method.returnType, diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java index f08e607f1a4..720419993c2 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/intrinsics/IntrinsicMethods.java @@ -65,7 +65,7 @@ public class IntrinsicMethods { this(jvmTarget, true); } - public IntrinsicMethods(JvmTarget jvmTarget, boolean useConsistentEqualsForPrimitiveWrappers) { + public IntrinsicMethods(JvmTarget jvmTarget, boolean shouldThrowNpeOnExplicitEqualsForBoxedNull) { intrinsicsMap.registerIntrinsic(KOTLIN_JVM, RECEIVER_PARAMETER_FQ_NAME, "javaClass", -1, JavaClassProperty.INSTANCE); intrinsicsMap.registerIntrinsic(KOTLIN_JVM, KotlinBuiltIns.FQ_NAMES.kClass, "java", -1, new KClassJavaProperty()); intrinsicsMap.registerIntrinsic(KotlinBuiltIns.FQ_NAMES.kCallable.toSafe(), null, "name", -1, new KCallableNameProperty()); @@ -102,9 +102,9 @@ public class IntrinsicMethods { for (PrimitiveType type : PrimitiveType.values()) { FqName typeFqName = type.getTypeFqName(); IntrinsicMethod equalsMethod; - if (useConsistentEqualsForPrimitiveWrappers) { + if (shouldThrowNpeOnExplicitEqualsForBoxedNull) { Type wrapperType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(JvmPrimitiveType.get(type).getWrapperFqName()); - equalsMethod = new ConsistentEquals(wrapperType); + equalsMethod = new EqualsThrowingNpeForNullReceiver(wrapperType); } else { equalsMethod = EQUALS; diff --git a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt index 1dfa1423bf3..6a9269e3275 100644 --- a/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt +++ b/compiler/backend/src/org/jetbrains/kotlin/codegen/state/GenerationState.kt @@ -142,7 +142,11 @@ class GenerationState @JvmOverloads constructor( this.bindingContext, classBuilderMode, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace), this.moduleName, isJvm8Target, isJvm8TargetWithDefaults ) - val intrinsics: IntrinsicMethods = IntrinsicMethods(target, languageVersionSettings.supportsFeature(LanguageFeature.ConsistentExplicitEqualsForPrimitiveWrappers)) + val intrinsics: IntrinsicMethods = run { + val shouldUseConsistentEquals = languageVersionSettings.supportsFeature(LanguageFeature.ThrowNpeOnExplicitEqualsForBoxedNull) && + !configuration.getBoolean(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL) + IntrinsicMethods(target, shouldUseConsistentEquals) + } val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this) val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics) val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this) diff --git a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt index a5be6ac2d63..0e4e47854bf 100644 --- a/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt +++ b/compiler/cli/cli-common/src/org/jetbrains/kotlin/cli/common/arguments/K2JVMCompilerArguments.kt @@ -167,6 +167,12 @@ class K2JVMCompilerArguments : CommonCompilerArguments() { ) var jsr305: String? by FreezableVar(Jsr305State.DEFAULT.description) + @Argument( + value = "-Xno-exception-on-explicit-equals-for-boxed-null", + description = "Do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type" + ) + var noExceptionOnExplicitEqualsForBoxedNull by FreezableVar(false) + // Paths to output directories for friend modules. var friendPaths: Array? by FreezableVar(null) diff --git a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt index 76b9176ae11..311bfacc133 100644 --- a/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt +++ b/compiler/cli/src/org/jetbrains/kotlin/cli/jvm/K2JVMCompiler.kt @@ -343,6 +343,7 @@ class K2JVMCompiler : CLICompiler() { configuration.put(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS, arguments.noCallAssertions) configuration.put(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS, arguments.noReceiverAssertions) configuration.put(JVMConfigurationKeys.DISABLE_PARAM_ASSERTIONS, arguments.noParamAssertions) + configuration.put(JVMConfigurationKeys.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL, arguments.noExceptionOnExplicitEqualsForBoxedNull); configuration.put(JVMConfigurationKeys.DISABLE_OPTIMIZATION, arguments.noOptimize) configuration.put(JVMConfigurationKeys.INHERIT_MULTIFILE_PARTS, arguments.inheritMultifileParts) configuration.put(JVMConfigurationKeys.SKIP_RUNTIME_VERSION_CHECK, arguments.skipRuntimeVersionCheck) diff --git a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java index 369e89f43d3..9ed993297e8 100644 --- a/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java +++ b/compiler/frontend.java/src/org/jetbrains/kotlin/config/JVMConfigurationKeys.java @@ -54,6 +54,8 @@ public class JVMConfigurationKeys { CompilerConfigurationKey.create("disable not-null call receiver assertions"); public static final CompilerConfigurationKey DISABLE_PARAM_ASSERTIONS = CompilerConfigurationKey.create("disable not-null parameter assertions"); + public static final CompilerConfigurationKey NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL = + CompilerConfigurationKey.create("do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type"); public static final CompilerConfigurationKey DISABLE_OPTIMIZATION = CompilerConfigurationKey.create("disable optimization"); public static final CompilerConfigurationKey INHERIT_MULTIFILE_PARTS = diff --git a/compiler/testData/cli/jvm/extraHelp.out b/compiler/testData/cli/jvm/extraHelp.out index fd460a16812..1768d4a0c5f 100644 --- a/compiler/testData/cli/jvm/extraHelp.out +++ b/compiler/testData/cli/jvm/extraHelp.out @@ -14,6 +14,8 @@ where advanced options include: -Xload-builtins-from-dependencies Load definitions of built-in declarations from module dependencies, instead of from the compiler -Xno-call-assertions Don't generate not-null assertions for arguments of platform types + -Xno-exception-on-explicit-equals-for-boxed-null + Do not throw NPE on explicit 'equals' call for null receiver of platform boxed primitive type -Xno-optimize Disable optimizations -Xno-param-assertions Don't generate not-null assertions on parameters of methods accessible from Java -Xno-receiver-assertions Don't generate not-null assertion for extension receiver arguments of platform types diff --git a/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt new file mode 100644 index 00000000000..4d7aba6b1a9 --- /dev/null +++ b/compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt @@ -0,0 +1,40 @@ +// LANGUAGE_VERSION: 1.2 +// KOTLIN_CONFIGURATION_FLAGS: +JVM.NO_EXCEPTION_ON_EXPLICIT_EQUALS_FOR_BOXED_NULL +// TARGET_BACKEND: JVM +// WITH_RUNTIME +// FILE: test.kt +import kotlin.test.* + +fun box(): String { + assertEquals(J.BOOL_NULL.equals(null), true) + assertEquals(J.CHAR_NULL.equals(null), true) + assertEquals(J.BYTE_NULL.equals(null), true) + assertEquals(J.SHORT_NULL.equals(null), true) + assertEquals(J.INT_NULL.equals(null), true) + assertEquals(J.LONG_NULL.equals(null), true) + assertEquals(J.FLOAT_NULL.equals(null), true) + assertEquals(J.DOUBLE_NULL.equals(null), true) + + assertEquals(J.BOOL_NULL == null, true) + assertEquals(J.CHAR_NULL == null, true) + assertEquals(J.BYTE_NULL == null, true) + assertEquals(J.SHORT_NULL == null, true) + assertEquals(J.INT_NULL == null, true) + assertEquals(J.LONG_NULL == null, true) + assertEquals(J.FLOAT_NULL == null, true) + assertEquals(J.DOUBLE_NULL == null, true) + + return "OK" +} + +// FILE: J.java +public class J { + public static Boolean BOOL_NULL = null; + public static Character CHAR_NULL = null; + public static Byte BYTE_NULL = null; + public static Short SHORT_NULL = null; + public static Integer INT_NULL = null; + public static Long LONG_NULL = null; + public static Float FLOAT_NULL = null; + public static Double DOUBLE_NULL = null; +} \ No newline at end of file diff --git a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java index 643bbff8206..6ccc3c9c59d 100644 --- a/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java +++ b/compiler/tests-ir-jvm/tests/org/jetbrains/kotlin/codegen/ir/IrBlackBoxCodegenTestGenerated.java @@ -12400,6 +12400,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes doTest(fileName); } + @TestMetadata("equalsNull_withExplicitFlag.kt") + public void testEqualsNull_withExplicitFlag() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java index 3cad21a9b14..443fe770d7d 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/BlackBoxCodegenTestGenerated.java @@ -12400,6 +12400,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest { doTest(fileName); } + @TestMetadata("equalsNull_withExplicitFlag.kt") + public void testEqualsNull_withExplicitFlag() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java index 91cb6377519..754b1b27663 100644 --- a/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java +++ b/compiler/tests/org/jetbrains/kotlin/codegen/LightAnalysisModeTestGenerated.java @@ -12400,6 +12400,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes doTest(fileName); } + @TestMetadata("equalsNull_withExplicitFlag.kt") + public void testEqualsNull_withExplicitFlag() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_withExplicitFlag.kt"); + doTest(fileName); + } + @TestMetadata("hashCode.kt") public void testHashCode() throws Exception { String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt"); diff --git a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt index 8b7a48b0da1..adb2b01ec90 100644 --- a/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt +++ b/compiler/util/src/org/jetbrains/kotlin/config/LanguageVersionSettings.kt @@ -61,7 +61,7 @@ enum class LanguageFeature( LateinitLocalVariables(KOTLIN_1_2), InnerClassInEnumEntryClass(KOTLIN_1_2), CallableReferencesToClassMembersWithEmptyLHS(KOTLIN_1_2), - ConsistentExplicitEqualsForPrimitiveWrappers(KOTLIN_1_2), + ThrowNpeOnExplicitEqualsForBoxedNull(KOTLIN_1_2), JvmPackageName(KOTLIN_1_2), RestrictionOfValReassignmentViaBackingField(KOTLIN_1_3),