JVM IR: Avoid CHECKCASTs on type operators (KT-39520)

The type information coming from Java or Kotlin generics may be wrong
due to type erasure and a CHECKCAST instruction could throw an
exception.
This commit is contained in:
Steven Schäfer
2020-08-06 15:33:46 +02:00
committed by Alexander Udalov
parent 469b164555
commit 9026f89ba5
13 changed files with 261 additions and 18 deletions
@@ -3296,6 +3296,39 @@ public class FirBlackBoxCodegenTestGenerated extends AbstractFirBlackBoxCodegenT
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractFirBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTestWithCustomIgnoreDirective(this::doTest, TargetBackend.JVM_IR, testDataFilePath, "// IGNORE_BACKEND_FIR: ");
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("castWithWrongType.kt")
public void testCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/castWithWrongType.kt");
}
@TestMetadata("implicitNotNullWithWrongType.kt")
public void testImplicitNotNullWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/implicitNotNullWithWrongType.kt");
}
@TestMetadata("instanceOfWithWrongType.kt")
public void testInstanceOfWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/instanceOfWithWrongType.kt");
}
@TestMetadata("safeCastWithWrongType.kt")
public void testSafeCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/safeCastWithWrongType.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -58,7 +58,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
type.isReifiedTypeParameter -> type.isReifiedTypeParameter ->
irIs(argument, type) irIs(argument, type)
argument.type.isNullable() && type.isNullable() -> { argument.type.isNullable() && type.isNullable() -> {
irLetS(argument) { valueSymbol -> irLetS(argument, irType = context.irBuiltIns.anyNType) { valueSymbol ->
context.oror( context.oror(
irEqualsNull(irGet(valueSymbol.owner)), irEqualsNull(irGet(valueSymbol.owner)),
irIs(irGet(valueSymbol.owner), type.makeNotNull()) irIs(irGet(valueSymbol.owner), type.makeNotNull())
@@ -76,7 +76,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
builder.irAs(argument, type) builder.irAs(argument, type)
argument.type.isNullable() && !type.isNullable() -> argument.type.isNullable() && !type.isNullable() ->
with(builder) { with(builder) {
irLetS(argument) { valueSymbol -> irLetS(argument, irType = context.irBuiltIns.anyNType) { valueSymbol ->
irIfNull( irIfNull(
type, type,
irGet(valueSymbol.owner), irGet(valueSymbol.owner),
@@ -118,7 +118,11 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
expression.transformChildrenVoid() expression.transformChildrenVoid()
expression expression
} else { } else {
irLetS(expression.argument.transformVoid(), IrStatementOrigin.SAFE_CALL) { valueSymbol -> irLetS(
expression.argument.transformVoid(),
IrStatementOrigin.SAFE_CALL,
irType = context.irBuiltIns.anyNType
) { valueSymbol ->
irIfThenElse( irIfThenElse(
expression.type, expression.type,
lowerInstanceOf(irGet(valueSymbol.owner), expression.typeOperand.makeNotNull()), lowerInstanceOf(irGet(valueSymbol.owner), expression.typeOperand.makeNotNull()),
@@ -138,7 +142,7 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
val (startOffset, endOffset) = expression.extents() val (startOffset, endOffset) = expression.extents()
val source = sourceViewFor(parent as IrDeclaration).subSequence(startOffset, endOffset).toString() val source = sourceViewFor(parent as IrDeclaration).subSequence(startOffset, endOffset).toString()
fun checkExpressionValue(valueSymbol: IrValueSymbol): IrExpression = irLetS(expression.argument.transformVoid(), irType = context.irBuiltIns.anyNType) { valueSymbol ->
irComposite(resultType = expression.type) { irComposite(resultType = expression.type) {
+irCall(checkExpressionValueIsNotNull).apply { +irCall(checkExpressionValueIsNotNull).apply {
putValueArgument(0, irGet(valueSymbol.owner)) putValueArgument(0, irGet(valueSymbol.owner))
@@ -146,14 +150,6 @@ private class TypeOperatorLowering(private val context: JvmBackendContext) : Fil
} }
+irGet(valueSymbol.owner) +irGet(valueSymbol.owner)
} }
val argument = expression.argument.transformVoid()
if (argument is IrGetValue) {
checkExpressionValue(argument.symbol)
} else {
irLetS(argument) { valueSymbol ->
checkExpressionValue(valueSymbol)
}
} }
} }
@@ -12,6 +12,7 @@ import org.jetbrains.kotlin.ir.expressions.*
import org.jetbrains.kotlin.ir.expressions.impl.* import org.jetbrains.kotlin.ir.expressions.impl.*
import org.jetbrains.kotlin.ir.symbols.* import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.types.IrType import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.util.isImmutable
import org.jetbrains.kotlin.ir.util.parentAsClass import org.jetbrains.kotlin.ir.util.parentAsClass
import org.jetbrains.kotlin.ir.util.render import org.jetbrains.kotlin.ir.util.render
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
@@ -23,14 +24,23 @@ inline fun IrBuilderWithScope.irLetS(
value: IrExpression, value: IrExpression,
origin: IrStatementOrigin? = null, origin: IrStatementOrigin? = null,
nameHint: String? = null, nameHint: String? = null,
irType: IrType? = null,
body: (IrValueSymbol) -> IrExpression body: (IrValueSymbol) -> IrExpression
): IrExpression { ): IrExpression {
val irTemporary = scope.createTemporaryVariable(value, nameHint) val (valueSymbol, irTemporary) = if (value is IrGetValue && value.symbol.owner.isImmutable) {
val irResult = body(irTemporary.symbol) value.symbol to null
val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin) } else {
irBlock.statements.add(irTemporary) scope.createTemporaryVariable(value, nameHint, irType = irType).let { it.symbol to it }
irBlock.statements.add(irResult) }
return irBlock val irResult = body(valueSymbol)
return if (irTemporary == null) {
irResult
} else {
val irBlock = IrBlockImpl(startOffset, endOffset, irResult.type, origin)
irBlock.statements.add(irTemporary)
irBlock.statements.add(irResult)
irBlock
}
} }
@@ -0,0 +1,17 @@
// KT-39520
// TARGET_BACKEND: JVM
// FILE: A.java
public class A<T> {
private T value;
private A(T x) { value = x; }
public static <T> T f() {
return ((A<T>) new A(1)).value;
}
}
// FILE: test.kt
fun box(): String {
val x = A.f<String>() as Int
return if (x == 1) "OK" else "Fail"
}
@@ -0,0 +1,17 @@
// KT-39520
// TARGET_BACKEND: JVM
// FILE: A.java
public class A<T> {
private T value;
private A(T x) { value = x; }
public static <T> T f() {
return ((A<T>) new A(1)).value;
}
}
// FILE: test.kt
fun box(): String {
val x: Any = A.f<String>()
return if (x == 1) "OK" else "Fail"
}
@@ -0,0 +1,16 @@
// KT-39520
// TARGET_BACKEND: JVM
// FILE: A.java
public class A<T> {
private T value;
private A(T x) { value = x; }
public static <T> T f() {
return ((A<T>) new A(1)).value;
}
}
// FILE: test.kt
fun box(): String {
return if (A.f<String?>() is CharSequence?) "Fail" else "OK"
}
@@ -0,0 +1,16 @@
// KT-39520
// TARGET_BACKEND: JVM
// FILE: A.java
public class A<T> {
private T value;
private A(T x) { value = x; }
public static <T> T f() {
return ((A<T>) new A(1)).value;
}
}
// FILE: test.kt
fun box(): String {
return A.f<String>() as? String ?: "OK"
}
@@ -3316,6 +3316,39 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("castWithWrongType.kt")
public void testCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/castWithWrongType.kt");
}
@TestMetadata("implicitNotNullWithWrongType.kt")
public void testImplicitNotNullWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/implicitNotNullWithWrongType.kt");
}
@TestMetadata("instanceOfWithWrongType.kt")
public void testInstanceOfWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/instanceOfWithWrongType.kt");
}
@TestMetadata("safeCastWithWrongType.kt")
public void testSafeCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/safeCastWithWrongType.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3316,6 +3316,39 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractLightAnalysisModeTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM, true);
}
@TestMetadata("castWithWrongType.kt")
public void testCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/castWithWrongType.kt");
}
@TestMetadata("implicitNotNullWithWrongType.kt")
public void testImplicitNotNullWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/implicitNotNullWithWrongType.kt");
}
@TestMetadata("instanceOfWithWrongType.kt")
public void testInstanceOfWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/instanceOfWithWrongType.kt");
}
@TestMetadata("safeCastWithWrongType.kt")
public void testSafeCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/safeCastWithWrongType.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -3296,6 +3296,39 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractIrBlackBoxCodegenTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest(this::doTest, TargetBackend.JVM_IR, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JVM_IR, true);
}
@TestMetadata("castWithWrongType.kt")
public void testCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/castWithWrongType.kt");
}
@TestMetadata("implicitNotNullWithWrongType.kt")
public void testImplicitNotNullWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/implicitNotNullWithWrongType.kt");
}
@TestMetadata("instanceOfWithWrongType.kt")
public void testInstanceOfWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/instanceOfWithWrongType.kt");
}
@TestMetadata("safeCastWithWrongType.kt")
public void testSafeCastWithWrongType() throws Exception {
runTest("compiler/testData/codegen/box/casts/javaInterop/safeCastWithWrongType.kt");
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -2631,6 +2631,19 @@ public class IrJsCodegenBoxES6TestGenerated extends AbstractIrJsCodegenBoxES6Tes
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractIrJsCodegenBoxES6Test {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR_ES6, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR_ES6, true);
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -2631,6 +2631,19 @@ public class IrJsCodegenBoxTestGenerated extends AbstractIrJsCodegenBoxTest {
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractIrJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS_IR, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS_IR, true);
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)
@@ -2631,6 +2631,19 @@ public class JsCodegenBoxTestGenerated extends AbstractJsCodegenBoxTest {
} }
} }
@TestMetadata("compiler/testData/codegen/box/casts/javaInterop")
@TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class)
public static class JavaInterop extends AbstractJsCodegenBoxTest {
private void runTest(String testDataFilePath) throws Exception {
KotlinTestUtils.runTest0(this::doTest, TargetBackend.JS, testDataFilePath);
}
public void testAllFilesPresentInJavaInterop() throws Exception {
KotlinTestUtils.assertAllTestsPresentByMetadataWithExcluded(this.getClass(), new File("compiler/testData/codegen/box/casts/javaInterop"), Pattern.compile("^(.+)\\.kt$"), null, TargetBackend.JS, true);
}
}
@TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument") @TestMetadata("compiler/testData/codegen/box/casts/literalExpressionAsGenericArgument")
@TestDataPath("$PROJECT_ROOT") @TestDataPath("$PROJECT_ROOT")
@RunWith(JUnit3RunnerWithInners.class) @RunWith(JUnit3RunnerWithInners.class)