Fail with NPE on explicit 'equals' call for null platform type value

This commit is contained in:
Dmitry Petrov
2017-09-08 16:49:42 +03:00
parent 4c2cfd3ea9
commit a3c4850f0d
9 changed files with 152 additions and 7 deletions
@@ -20,6 +20,10 @@ import org.jetbrains.kotlin.codegen.AsmUtil
import org.jetbrains.kotlin.codegen.Callable
import org.jetbrains.kotlin.codegen.CallableMethod
import org.jetbrains.kotlin.resolve.jvm.AsmTypes.OBJECT_TYPE
import org.jetbrains.org.objectweb.asm.Type
internal val equalsMethodDescriptor: String =
Type.getMethodDescriptor(Type.BOOLEAN_TYPE, Type.getType(Any::class.java));
class Equals : IntrinsicMethod() {
override fun toCallable(method: CallableMethod): Callable =
@@ -32,3 +36,15 @@ class Equals : IntrinsicMethod() {
AsmUtil.genAreEqualCall(it)
}
}
class ConsistentEquals(private val lhsType: Type) : IntrinsicMethod() {
override fun toCallable(method: CallableMethod): Callable =
createBinaryIntrinsicCallable(
method.returnType,
OBJECT_TYPE,
nullOrObject(method.dispatchReceiverType),
nullOrObject(method.extensionReceiverType)
) {
it.invokevirtual(lhsType.internalName, "equals", equalsMethodDescriptor, false)
}
}
@@ -22,6 +22,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.builtins.PrimitiveType;
import org.jetbrains.kotlin.codegen.AsmUtil;
import org.jetbrains.kotlin.config.JvmTarget;
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor;
import org.jetbrains.kotlin.name.FqName;
@@ -29,6 +30,7 @@ import org.jetbrains.kotlin.name.FqNameUnsafe;
import org.jetbrains.kotlin.name.Name;
import org.jetbrains.kotlin.resolve.jvm.JvmPrimitiveType;
import org.jetbrains.kotlin.types.expressions.OperatorConventions;
import org.jetbrains.org.objectweb.asm.Type;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.*;
import static org.jetbrains.org.objectweb.asm.Opcodes.*;
@@ -46,7 +48,6 @@ public class IntrinsicMethods {
private static final IntrinsicMethod RANGE_TO = new RangeTo();
private static final IntrinsicMethod INC = new Increment(1);
private static final IntrinsicMethod DEC = new Increment(-1);
private final IntrinsicMethod HASH_CODE;
private static final IntrinsicMethod ARRAY_SIZE = new ArraySize();
private static final Equals EQUALS = new Equals();
@@ -61,7 +62,10 @@ public class IntrinsicMethods {
private final IntrinsicsMap intrinsicsMap = new IntrinsicsMap();
public IntrinsicMethods(JvmTarget jvmTarget) {
HASH_CODE = new HashCode(jvmTarget);
this(jvmTarget, true);
}
public IntrinsicMethods(JvmTarget jvmTarget, boolean useConsistentEqualsForPrimitiveWrappers) {
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());
@@ -94,10 +98,20 @@ public class IntrinsicMethods {
declareIntrinsicFunction(typeFqName, "dec", 0, DEC);
}
IntrinsicMethod hashCode = new HashCode(jvmTarget);
for (PrimitiveType type : PrimitiveType.values()) {
FqName typeFqName = type.getTypeFqName();
declareIntrinsicFunction(typeFqName, "equals", 1, EQUALS);
declareIntrinsicFunction(typeFqName, "hashCode", 0, HASH_CODE);
IntrinsicMethod equalsMethod;
if (useConsistentEqualsForPrimitiveWrappers) {
Type wrapperType = AsmUtil.asmTypeByFqNameWithoutInnerClasses(JvmPrimitiveType.get(type).getWrapperFqName());
equalsMethod = new ConsistentEquals(wrapperType);
}
else {
equalsMethod = EQUALS;
}
declareIntrinsicFunction(typeFqName, "equals", 1, equalsMethod);
declareIntrinsicFunction(typeFqName, "hashCode", 0, hashCode);
declareIntrinsicFunction(typeFqName, "toString", 0, TO_STRING);
intrinsicsMap.registerIntrinsic(
@@ -126,6 +126,8 @@ class GenerationState @JvmOverloads constructor(
extraJvmDiagnosticsTrace.bindingContext.diagnostics
}
val languageVersionSettings = configuration.languageVersionSettings
val target = configuration.get(JVMConfigurationKeys.JVM_TARGET) ?: JvmTarget.DEFAULT
val isJvm8Target: Boolean = target == JvmTarget.JVM_1_8
val isJvm8TargetWithDefaults: Boolean = isJvm8Target && configuration.getBoolean(JVMConfigurationKeys.JVM8_TARGET_WITH_DEFAULTS)
@@ -140,7 +142,7 @@ class GenerationState @JvmOverloads constructor(
this.bindingContext, classBuilderMode, IncompatibleClassTrackerImpl(extraJvmDiagnosticsTrace),
this.moduleName, isJvm8Target, isJvm8TargetWithDefaults
)
val intrinsics: IntrinsicMethods = IntrinsicMethods(target)
val intrinsics: IntrinsicMethods = IntrinsicMethods(target, languageVersionSettings.supportsFeature(LanguageFeature.ConsistentExplicitEqualsForPrimitiveWrappers))
val samWrapperClasses: SamWrapperClasses = SamWrapperClasses(this)
val inlineCycleReporter: InlineCycleReporter = InlineCycleReporter(diagnostics)
val mappingsClassesForWhenByEnum: MappingsClassesForWhenByEnum = MappingsClassesForWhenByEnum(this)
@@ -158,8 +160,6 @@ class GenerationState @JvmOverloads constructor(
var hasResult: Boolean = false
}
val languageVersionSettings = configuration.languageVersionSettings
val isCallAssertionsDisabled: Boolean = configuration.getBoolean(JVMConfigurationKeys.DISABLE_CALL_ASSERTIONS)
val isReceiverAssertionsDisabled: Boolean =
configuration.getBoolean(JVMConfigurationKeys.DISABLE_RECEIVER_ASSERTIONS) ||
@@ -0,0 +1,39 @@
// LANGUAGE_VERSION: 1.1
// 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;
}
@@ -0,0 +1,39 @@
// LANGUAGE_VERSION: 1.2
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: test.kt
import kotlin.test.*
fun box(): String {
assertFailsWith<NullPointerException> { J.BOOL_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.CHAR_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.BYTE_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.SHORT_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.INT_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.LONG_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.FLOAT_NULL.equals(null) }
assertFailsWith<NullPointerException> { J.DOUBLE_NULL.equals(null) }
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;
}
@@ -12388,6 +12388,18 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
doTest(fileName);
}
@TestMetadata("equalsNull_lv11.kt")
public void testEqualsNull_lv11() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt");
doTest(fileName);
}
@TestMetadata("equalsNull_lv12.kt")
public void testEqualsNull_lv12() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt");
doTest(fileName);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt");
@@ -12388,6 +12388,18 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
doTest(fileName);
}
@TestMetadata("equalsNull_lv11.kt")
public void testEqualsNull_lv11() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt");
doTest(fileName);
}
@TestMetadata("equalsNull_lv12.kt")
public void testEqualsNull_lv12() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt");
doTest(fileName);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt");
@@ -12388,6 +12388,18 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
doTest(fileName);
}
@TestMetadata("equalsNull_lv11.kt")
public void testEqualsNull_lv11() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv11.kt");
doTest(fileName);
}
@TestMetadata("equalsNull_lv12.kt")
public void testEqualsNull_lv12() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/equalsNull_lv12.kt");
doTest(fileName);
}
@TestMetadata("hashCode.kt")
public void testHashCode() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/platformTypes/primitives/hashCode.kt");
@@ -61,6 +61,7 @@ enum class LanguageFeature(
LateinitLocalVariables(KOTLIN_1_2),
InnerClassInEnumEntryClass(KOTLIN_1_2),
CallableReferencesToClassMembersWithEmptyLHS(KOTLIN_1_2),
ConsistentExplicitEqualsForPrimitiveWrappers(KOTLIN_1_2),
JvmPackageName(KOTLIN_1_2),
RestrictionOfValReassignmentViaBackingField(KOTLIN_1_3),