Restore implicit wildcard types

The Kotlin compiler represents types like List<out CharSequence> internally as List<CharSequence> due to the fact that List's type parameter is covariant, and similarly Comparable<in CharSequence> where Comparable's type parameter is contravariant. However it restores use-site variance when emitting class files, so we must do the same thing for
compatability with Java code.

Note this is a partial solution because it will also add wildcards to Java .class files that *could* have a variance / wildcard but don't -- for example, a Java method could really take an invariant Comparable<CharSequence>, which is only achievable in Kotlin via the @JvmSuppressWildcards annotation. We also don't yet support
@JvmSuppressWildcards given on a surrounding class or function.
This commit is contained in:
Chris Smowton
2022-06-01 16:01:27 +01:00
parent 4b2b6fae88
commit 37fce6ace9
2 changed files with 53 additions and 3 deletions

View File

@@ -497,7 +497,9 @@ open class KotlinFileExtractor(
else else
null null
} ?: vp.type } ?: vp.type
val substitutedType = typeSubstitution?.let { it(maybeErasedType, TypeContext.OTHER, pluginContext) } ?: maybeErasedType val typeWithWildcards = addJavaLoweringWildcards(maybeErasedType, true)
val substitutedType = typeSubstitution?.let { it(typeWithWildcards, TypeContext.OTHER, pluginContext) } ?: typeWithWildcards
val id = useValueParameter(vp, parent) val id = useValueParameter(vp, parent)
if (extractTypeAccess) { if (extractTypeAccess) {
extractTypeAccessRecursive(substitutedType, location, id, -1) extractTypeAccessRecursive(substitutedType, location, id, -1)
@@ -704,7 +706,7 @@ open class KotlinFileExtractor(
val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature!! } val paramsSignature = allParamTypes.joinToString(separator = ",", prefix = "(", postfix = ")") { it.javaResult.signature!! }
val adjustedReturnType = getAdjustedReturnType(f) val adjustedReturnType = addJavaLoweringWildcards(getAdjustedReturnType(f), false)
val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType val substReturnType = typeSubstitution?.let { it(adjustedReturnType, TypeContext.RETURN, pluginContext) } ?: adjustedReturnType
val locId = locOverride ?: getLocation(f, classTypeArgsIncludingOuterClasses) val locId = locOverride ?: getLocation(f, classTypeArgsIncludingOuterClasses)

View File

@@ -6,6 +6,7 @@ import com.github.codeql.utils.versions.isRawType
import com.semmle.extractor.java.OdasaOutput import com.semmle.extractor.java.OdasaOutput
import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext import org.jetbrains.kotlin.backend.common.extensions.IrPluginContext
import org.jetbrains.kotlin.backend.common.ir.allOverridden import org.jetbrains.kotlin.backend.common.ir.allOverridden
import org.jetbrains.kotlin.backend.common.ir.isFinalClass
import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf import org.jetbrains.kotlin.backend.common.lower.parentsWithSelf
import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation import org.jetbrains.kotlin.backend.jvm.ir.getJvmNameFromAnnotation
import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor import org.jetbrains.kotlin.backend.jvm.ir.propertyIfAccessor
@@ -850,6 +851,49 @@ open class KotlinUsesExtractor(
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) || (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableCollection")) ||
(f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList")) (f.name.asString() == "addAll" && overridesFunctionDefinedOn(f, "kotlin.collections", "MutableList"))
private fun wildcardAdditionAllowed(v: Variance, t: IrType, inParameterContext: Boolean) =
when {
t.hasAnnotation(FqName("kotlin.jvm.JvmWildcard")) -> true
!inParameterContext -> false // By default, wildcards are only automatically added for method parameters.
t.hasAnnotation(FqName("kotlin.jvm.JvmSuppressWildcards")) -> false
v == Variance.IN_VARIANCE -> !(t.isNullableAny() || t.isAny())
v == Variance.OUT_VARIANCE -> ((t as? IrSimpleType)?.classOrNull?.owner?.isFinalClass) != true
else -> false
}
private fun addJavaLoweringArgumentWildcards(p: IrTypeParameter, t: IrTypeArgument, inParameterContext: Boolean): IrTypeArgument =
(t as? IrTypeProjection)?.let {
val newBase = addJavaLoweringWildcards(it.type, inParameterContext)
val newVariance =
if (it.variance == Variance.INVARIANT &&
p.variance != Variance.INVARIANT &&
wildcardAdditionAllowed(p.variance, it.type, inParameterContext))
p.variance
else
it.variance
if (newBase !== it.type || newVariance != it.variance)
makeTypeProjection(newBase, newVariance)
else
null
} ?: t
fun addJavaLoweringWildcards(t: IrType, inParameterContext: Boolean): IrType =
(t as? IrSimpleType)?.let {
val typeParams = it.classOrNull?.owner?.typeParameters ?: return t
val newArgs = typeParams.zip(it.arguments).map { pair ->
addJavaLoweringArgumentWildcards(
pair.first,
pair.second,
inParameterContext
)
}
return if (newArgs.zip(it.arguments).all { pair -> pair.first === pair.second })
t
else
it.toBuilder().also { builder -> builder.arguments = newArgs }.buildSimpleType()
} ?: t
/* /*
* This is the normal getFunctionLabel function to use. If you want * This is the normal getFunctionLabel function to use. If you want
* to refer to the function in its source class then * to refer to the function in its source class then
@@ -956,8 +1000,10 @@ open class KotlinUsesExtractor(
// Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe. // Collection.remove(Object) because Collection.remove(Collection::E) in the Kotlin universe.
// If this has happened, erase the type again to get the correct Java signature. // If this has happened, erase the type again to get the correct Java signature.
val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type val maybeAmendedForCollections = if (overridesCollectionsMethod) eraseCollectionsMethodParameterType(it.value.type, name, it.index) else it.value.type
// Add any wildcard types that the Kotlin compiler would add in the Java lowering of this function:
val withAddedWildcards = addJavaLoweringWildcards(maybeAmendedForCollections, true)
// Now substitute any class type parameters in: // Now substitute any class type parameters in:
val maybeSubbed = maybeAmendedForCollections.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext) val maybeSubbed = withAddedWildcards.substituteTypeAndArguments(substitutionMap, TypeContext.OTHER, pluginContext)
// Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types; // Finally, mimic the Java extractor's behaviour by naming functions with type parameters for their erased types;
// those without type parameters are named for the generic type. // those without type parameters are named for the generic type.
val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed) val maybeErased = if (functionTypeParameters.isEmpty()) maybeSubbed else erase(maybeSubbed)
@@ -969,6 +1015,8 @@ open class KotlinUsesExtractor(
pluginContext.irBuiltIns.unitType pluginContext.irBuiltIns.unitType
else else
erase(returnType.substituteTypeAndArguments(substitutionMap, TypeContext.RETURN, pluginContext)) erase(returnType.substituteTypeAndArguments(substitutionMap, TypeContext.RETURN, pluginContext))
// Note that `addJavaLoweringWildcards` is not required here because the return type used to form the function
// label is always erased.
val returnTypeId = useType(labelReturnType, TypeContext.RETURN).javaResult.id val returnTypeId = useType(labelReturnType, TypeContext.RETURN).javaResult.id
// This suffix is added to generic methods (and constructors) to match the Java extractor's behaviour. // This suffix is added to generic methods (and constructors) to match the Java extractor's behaviour.
// Comments in that extractor indicates it didn't want the label of the callable to clash with the raw // Comments in that extractor indicates it didn't want the label of the callable to clash with the raw