Box/unbox nullable inline class values with null check

When we have a nullable inline class value with non-null underlying
type, corresponding value in unboxed representation is nullable. E.g.:

  inline class Str(val value: String)

  fun test(s: Str?) = listOf(s)

Here 'test(s: Str?)' accepts nullable 'java.lang.String' as a parameter.

When boxing/unboxing nullable values of such inline classes, take care
of nulls.

 #KT-26052 Fixed Target versions 1.3-M2
This commit is contained in:
Dmitry Petrov
2018-08-13 12:28:01 +03:00
parent 56ad091534
commit ebf8ec455d
12 changed files with 360 additions and 1 deletions
@@ -36,12 +36,14 @@ import org.jetbrains.kotlin.resolve.jvm.jvmSignature.JvmMethodParameterSignature
import org.jetbrains.kotlin.resolve.scopes.receivers.ReceiverValue;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.SimpleType;
import org.jetbrains.kotlin.types.TypeUtils;
import org.jetbrains.org.objectweb.asm.Label;
import org.jetbrains.org.objectweb.asm.Opcodes;
import org.jetbrains.org.objectweb.asm.Type;
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter;
import java.util.List;
import java.util.function.Consumer;
import static org.jetbrains.kotlin.codegen.AsmUtil.*;
import static org.jetbrains.kotlin.resolve.jvm.AsmTypes.*;
@@ -402,6 +404,21 @@ public abstract class StackValue {
Type boxedType = KotlinTypeMapper.mapInlineClassTypeAsDeclaration(kotlinType);
Type owner = KotlinTypeMapper.mapToErasedInlineClassType(kotlinType);
Type underlyingType = KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(kotlinType);
if (TypeUtils.isNullableType(kotlinType) && !isPrimitive(underlyingType)) {
boxOrUnboxWithNullCheck(v, vv -> invokeBoxMethod(vv, boxedType, owner, underlyingType));
}
else {
invokeBoxMethod(v, boxedType, owner, underlyingType);
}
}
private static void invokeBoxMethod(
@NotNull InstructionAdapter v,
Type boxedType,
Type owner,
Type underlyingType
) {
v.invokestatic(
owner.getInternalName(),
InlineClassDescriptorResolver.BOX_METHOD_NAME.asString(),
@@ -416,6 +433,16 @@ public abstract class StackValue {
coerce(type, owner, v);
Type resultType = KotlinTypeMapper.mapUnderlyingTypeOfInlineClassType(targetInlineClassType);
if (TypeUtils.isNullableType(targetInlineClassType) && !isPrimitive(type)) {
boxOrUnboxWithNullCheck(v, vv -> invokeUnboxMethod(vv, owner, resultType));
}
else {
invokeUnboxMethod(v, owner, resultType);
}
}
private static void invokeUnboxMethod(@NotNull InstructionAdapter v, Type owner, Type resultType) {
v.invokevirtual(
owner.getInternalName(),
InlineClassDescriptorResolver.UNBOX_METHOD_NAME.asString(),
@@ -424,6 +451,23 @@ public abstract class StackValue {
);
}
private static void boxOrUnboxWithNullCheck(@NotNull InstructionAdapter v, @NotNull Consumer<InstructionAdapter> body) {
Label lNull = new Label();
Label lDone = new Label();
// NB The following piece of code looks sub-optimal (we have a 'null' value on stack and could just keep it there),
// but it is required, because bytecode verifier doesn't take into account null checks,
// and sees null-checked value on the top of the stack as a value of the source type (e.g., Ljava/lang/String;),
// which is not assignable to the expected type (destination type, e.g., LStr;).
v.dup();
v.ifnull(lNull);
body.accept(v);
v.goTo(lDone);
v.mark(lNull);
v.pop();
v.aconst(null);
v.mark(lDone);
}
protected void coerceTo(@NotNull Type toType, @Nullable KotlinType toKotlinType, @NotNull InstructionAdapter v) {
coerce(this.type, this.kotlinType, toType, toKotlinType, v);
}