JVM: Do not use equals-impl0 methods generated by older compiler versions

This commit is contained in:
Steven Schäfer
2019-09-03 16:32:35 +02:00
committed by Alexander Udalov
parent b85b2d9af8
commit 2e53e36fd5
4 changed files with 50 additions and 13 deletions
@@ -3513,10 +3513,13 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
Label endLabel = new Label();
boolean flipComparison = opToken == KtTokens.EXCLEQ || opToken == KtTokens.EXCLEQEQEQ;
// Don't call equals-impl0 if the class file version is too low to support it.
boolean useUnboxedEquals = leftIsUnboxed && JvmCodegenUtil.typeHasSpecializedInlineClassEquality(leftKotlinType, state);
leftValue.put(leftType, leftKotlinType, v);
//noinspection SuspiciousNameCombination
Type afterTopType = leftType;
if (!leftIsUnboxed) {
if (!useUnboxedEquals) {
StackValue.coerce(leftType, leftKotlinType, OBJECT_TYPE, nullableAnyType, v);
afterTopType = OBJECT_TYPE;
}
@@ -3524,12 +3527,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
rightValue.put(rightType, rightKotlinType, v);
//noinspection SuspiciousNameCombination
Type topType = rightType;
if (!leftIsUnboxed || !rightIsUnboxed) {
if (!useUnboxedEquals || !rightIsUnboxed) {
StackValue.coerce(rightType, rightKotlinType, OBJECT_TYPE, nullableAnyType, v);
topType = OBJECT_TYPE;
}
if (leftIsUnboxed) {
if (useUnboxedEquals) {
String className = typeMapper.mapTypeAsDeclaration(leftKotlinType).getInternalName();
// Nullable inline class wrappers around non-nullable types are unboxed, yet
// equals-impl expects a non-nullable first argument and equals-impl0 expects
@@ -3568,12 +3571,12 @@ public class ExpressionCodegen extends KtVisitor<StackValue, StackValue> impleme
v.goTo(endLabel);
v.visitLabel(nonNullLabel);
}
String descriptor = Type.getMethodType(Type.BOOLEAN_TYPE, leftType, leftType).toString();
String descriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, leftType, leftType);
v.invokestatic(className, InlineClassDescriptorResolver.SPECIALIZED_EQUALS_NAME.asString(), descriptor, false);
} else {
// equals-impl expects a non-nullable first argument, yet `left` may be unboxed even if
// it is nullable when it is a wrapper around a non-nullable reference type.
String descriptor = Type.getMethodType(Type.BOOLEAN_TYPE, leftType, OBJECT_TYPE).toString();
String descriptor = Type.getMethodDescriptor(Type.BOOLEAN_TYPE, leftType, OBJECT_TYPE);
v.invokestatic(className, "equals-impl", descriptor, false);
}
} else {
@@ -20,6 +20,7 @@ import org.jetbrains.kotlin.codegen.context.MethodContext;
import org.jetbrains.kotlin.codegen.context.RootContext;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper;
import org.jetbrains.kotlin.config.ApiVersion;
import org.jetbrains.kotlin.config.JvmAnalysisFlags;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
import org.jetbrains.kotlin.descriptors.*;
@@ -42,6 +43,7 @@ import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.resolve.scopes.receivers.TransientReceiver;
import org.jetbrains.kotlin.resolve.source.PsiSourceElement;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedCallableMemberDescriptor;
import org.jetbrains.kotlin.serialization.deserialization.descriptors.DeserializedClassDescriptor;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.util.OperatorNameConventions;
@@ -400,4 +402,24 @@ public class JvmCodegenUtil {
return name;
}
// Before metadata version 1.1.16 we did not generate equals-impl0 methods correctly.
// The method is still present on all inline classes, but the implementation always throws
// a NullPointerException.
public static boolean typeHasSpecializedInlineClassEquality(@NotNull KotlinType type, @NotNull GenerationState state) {
ClassifierDescriptor descriptor = type.getConstructor().getDeclarationDescriptor();
if (!(descriptor instanceof DeserializedClassDescriptor))
return true;
DeserializedClassDescriptor classDescriptor = (DeserializedClassDescriptor) descriptor;
// The Result class is the only inline class in the standard library without special rules for equality.
// We only call Result.equals-impl0 if we are compiling for Kotlin 1.4 or later. Otherwise, the code
// might well be running against an older version of the standard library.
if (DescriptorUtils.getFqNameSafe(classDescriptor).equals(DescriptorUtils.RESULT_FQ_NAME)) {
return state.getLanguageVersionSettings().getApiVersion().compareTo(ApiVersion.KOTLIN_1_4) >= 0;
} else {
return ((DeserializedClassDescriptor) descriptor).getMetadataVersion().isAtLeast(1, 1, 16);
}
}
}