Fix runtime CCE in case of out-projected SAM

Probably, when NI is there this fix will become unnecessary because
there will be no approximation applied, thus the value parameter
will remain Hello<#Captured> instead of Nothing

 #KT-17171 Fixed
This commit is contained in:
Denis Zharkov
2018-02-12 12:09:17 +03:00
parent 6baf937a52
commit c47c1c5ffd
7 changed files with 113 additions and 11 deletions
@@ -17,13 +17,34 @@
package org.jetbrains.kotlin.codegen;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.builtins.KotlinBuiltIns;
import org.jetbrains.kotlin.codegen.state.TypeMapperUtilsKt;
import org.jetbrains.kotlin.descriptors.ClassifierDescriptor;
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.kotlin.descriptors.ValueParameterDescriptor;
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor;
import org.jetbrains.kotlin.load.java.sam.SingleAbstractMethodUtils;
import org.jetbrains.kotlin.types.KotlinType;
import org.jetbrains.kotlin.types.typeUtil.TypeUtilsKt;
public class SamType {
@Nullable
public static SamType createByValueParameter(@NotNull ValueParameterDescriptor valueParameter) {
KotlinType originalTypeToUse =
// This can be true in case when the value parameter is in the method of a generic type with out-projection.
// We approximate Inv<Captured#1> to Nothing, while Inv itself can be a SAM interface safe to call here
// (see testData genericSamProjectedOut.kt for details)
KotlinBuiltIns.isNothing(valueParameter.getType())
// In such a case we can't have a proper supertype since wildcards are not allowed there,
// so we use Nothing arguments instead that leads to a raw type used for a SAM wrapper.
// See org.jetbrains.kotlin.codegen.state.KotlinTypeMapper#writeGenericType to understand how
// raw types and Nothing arguments relate.
? TypeUtilsKt.replaceArgumentsWithNothing(valueParameter.getOriginal().getType())
: valueParameter.getType();
return create(TypeMapperUtilsKt.removeExternalProjections(originalTypeToUse));
}
public static SamType create(@NotNull KotlinType originalType) {
if (!SingleAbstractMethodUtils.isSamType(originalType)) return null;
return new SamType(originalType);
@@ -29,7 +29,6 @@ import org.jetbrains.kotlin.cfg.WhenChecker;
import org.jetbrains.kotlin.codegen.*;
import org.jetbrains.kotlin.codegen.coroutines.CoroutineCodegenUtilKt;
import org.jetbrains.kotlin.codegen.state.GenerationState;
import org.jetbrains.kotlin.codegen.state.TypeMapperUtilsKt;
import org.jetbrains.kotlin.codegen.when.SwitchCodegenProvider;
import org.jetbrains.kotlin.codegen.when.WhenByEnumsMapping;
import org.jetbrains.kotlin.config.LanguageVersionSettings;
@@ -608,7 +607,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
ValueParameterDescriptor adaptedParameter = descriptor.getValueParameters().get(valueParameter.getIndex());
if (KotlinTypeChecker.DEFAULT.equalTypes(adaptedParameter.getType(), valueParameter.getType())) continue;
SamType samType = SamType.create(TypeMapperUtilsKt.removeExternalProjections(valueParameter.getType()));
SamType samType = SamType.createByValueParameter(valueParameter);
if (samType == null) continue;
ResolvedValueArgument resolvedValueArgument = valueArguments.get(valueParameter.getIndex());
@@ -648,7 +647,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
@Override
public void visitConstructorDelegationCall(@NotNull KtConstructorDelegationCall call) {
withinUninitializedClass(call, () -> super.visitConstructorDelegationCall(call));
checkSamCall(call);
}
@@ -692,7 +691,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
FunctionDescriptor original = SamCodegenUtil.getOriginalIfSamAdapter((FunctionDescriptor) operationDescriptor);
if (original == null) return;
SamType samType = SamType.create(original.getValueParameters().get(0).getType());
SamType samType = SamType.createByValueParameter(original.getValueParameters().get(0));
if (samType == null) return;
IElementType token = expression.getOperationToken();
@@ -718,7 +717,7 @@ class CodegenAnnotatingVisitor extends KtVisitorVoid {
List<KtExpression> indexExpressions = expression.getIndexExpressions();
List<ValueParameterDescriptor> parameters = original.getValueParameters();
for (ValueParameterDescriptor valueParameter : parameters) {
SamType samType = SamType.create(valueParameter.getType());
SamType samType = SamType.createByValueParameter(valueParameter);
if (samType == null) continue;
if (isSetter && valueParameter.getIndex() == parameters.size() - 1) {
@@ -0,0 +1,60 @@
// TARGET_BACKEND: JVM
// WITH_RUNTIME
// FILE: example/Hello.java
package example;
@FunctionalInterface
public interface Hello<A> {
void invoke(A a);
}
// FILE: example/SomeJavaClass.java
package example;
public class SomeJavaClass<A> {
public void someFunction(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
public void plus(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
public void get(Hello<A> hello) {
((Hello)hello).invoke("OK");
}
}
// FILE: main.kt
import example.SomeJavaClass
fun box(): String {
val a: SomeJavaClass<out String> = SomeJavaClass()
var result = "fail"
// a::someFunction parameter has type of Nothing
// while it's completely safe to pass a lambda for a SAM
// since Hello is effectively contravariant by its parameter
a.someFunction {
result = it
}
if (result != "OK") return "fail 1: $result"
result = "fail"
a + {
result = it
}
if (result != "OK") return "fail 2: $result"
result = "fail"
a[{
result = it
}]
if (result != "OK") return "fail 3: $result"
return "OK"
}
@@ -10995,6 +10995,12 @@ public class IrBlackBoxCodegenTestGenerated extends AbstractIrBlackBoxCodegenTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("genericSamProjectedOut.kt")
public void testGenericSamProjectedOut() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt");
doTest(fileName);
}
@TestMetadata("lambdaInstanceOf.kt")
public void testLambdaInstanceOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt");
@@ -10995,6 +10995,12 @@ public class BlackBoxCodegenTestGenerated extends AbstractBlackBoxCodegenTest {
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("genericSamProjectedOut.kt")
public void testGenericSamProjectedOut() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt");
doTest(fileName);
}
@TestMetadata("lambdaInstanceOf.kt")
public void testLambdaInstanceOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt");
@@ -10995,6 +10995,12 @@ public class LightAnalysisModeTestGenerated extends AbstractLightAnalysisModeTes
KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("compiler/testData/codegen/box/javaInterop"), Pattern.compile("^(.+)\\.kt$"), TargetBackend.JVM, true);
}
@TestMetadata("genericSamProjectedOut.kt")
public void testGenericSamProjectedOut() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/genericSamProjectedOut.kt");
doTest(fileName);
}
@TestMetadata("lambdaInstanceOf.kt")
public void testLambdaInstanceOf() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("compiler/testData/codegen/box/javaInterop/lambdaInstanceOf.kt");
@@ -21,6 +21,7 @@ import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.descriptors.annotations.Annotations
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.calls.inference.isCaptured
import org.jetbrains.kotlin.resolve.descriptorUtil.builtIns
import org.jetbrains.kotlin.types.*
import org.jetbrains.kotlin.types.checker.KotlinTypeChecker
import org.jetbrains.kotlin.types.checker.NewCapturedType
@@ -170,21 +171,24 @@ fun KotlinType.getImmediateSuperclassNotAny(): KotlinType? {
fun KotlinType.asTypeProjection(): TypeProjection = TypeProjectionImpl(this)
fun KotlinType.contains(predicate: (UnwrappedType) -> Boolean) = TypeUtils.contains(this, predicate)
fun KotlinType.replaceArgumentsWithStarProjections(): KotlinType {
fun KotlinType.replaceArgumentsWithStarProjections() = replaceArgumentsWith(::StarProjectionImpl)
fun KotlinType.replaceArgumentsWithNothing() = replaceArgumentsWith { it.builtIns.nothingType.asTypeProjection() }
private inline fun KotlinType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): KotlinType {
val unwrapped = unwrap()
return when (unwrapped) {
is FlexibleType -> KotlinTypeFactory.flexibleType(
unwrapped.lowerBound.replaceArgumentsWithStarProjections(),
unwrapped.upperBound.replaceArgumentsWithStarProjections()
unwrapped.lowerBound.replaceArgumentsWith(replacement),
unwrapped.upperBound.replaceArgumentsWith(replacement)
)
is SimpleType -> unwrapped.replaceArgumentsWithStarProjections()
is SimpleType -> unwrapped.replaceArgumentsWith(replacement)
}.inheritEnhancement(unwrapped)
}
private fun SimpleType.replaceArgumentsWithStarProjections(): SimpleType {
private inline fun SimpleType.replaceArgumentsWith(replacement: (TypeParameterDescriptor) -> TypeProjection): SimpleType {
if (constructor.parameters.isEmpty() || constructor.declarationDescriptor == null) return this
val newArguments = constructor.parameters.map(::StarProjectionImpl)
val newArguments = constructor.parameters.map(replacement)
return replace(newArguments)
}