Implement stub methods generation for Kotlin Immutable Collection classes.

This change is to fill the gap between Kotlin Collection
classes(immutable) and Java Collection classes(mutable), to avoid
calling an unsupported operation like remove() on an immutable class in
jvm.
This commit is contained in:
Jiaxiang Chen
2019-04-02 14:02:15 -07:00
committed by Georgy Bronnikov
parent 8c3cef97bd
commit afcbd76c9e
22 changed files with 259 additions and 19 deletions
@@ -155,6 +155,22 @@ abstract class Symbols<out T : CommonBackendContext>(val context: T, private val
val arrays = primitiveArrays.values + unsignedArrays.values + array
val collection = symbolTable.referenceClass(builtIns.collection)
val set = symbolTable.referenceClass(builtIns.set)
val list = symbolTable.referenceClass(builtIns.list)
val map = symbolTable.referenceClass(builtIns.map)
val mapEntry = symbolTable.referenceClass(builtIns.mapEntry)
val iterable = symbolTable.referenceClass(builtIns.iterable)
val listIterator = symbolTable.referenceClass(builtIns.listIterator)
val mutableCollection = symbolTable.referenceClass(builtIns.mutableCollection)
val mutableSet = symbolTable.referenceClass(builtIns.mutableSet)
val mutableList = symbolTable.referenceClass(builtIns.mutableList)
val mutableMap = symbolTable.referenceClass(builtIns.mutableMap)
val mutableMapEntry = symbolTable.referenceClass(builtIns.mutableMapEntry)
val mutableIterable = symbolTable.referenceClass(builtIns.mutableIterable)
val mutableIterator = symbolTable.referenceClass(builtIns.mutableIterator)
val mutableListIterator = symbolTable.referenceClass(builtIns.mutableListIterator)
abstract val copyRangeTo: Map<ClassDescriptor, IrSimpleFunctionSymbol>
abstract val coroutineImpl: IrClassSymbol
@@ -119,3 +119,28 @@ fun IrType.substitute(substitutionMap: Map<IrTypeParameterSymbol, IrType>): IrTy
newAnnotations
)
}
private fun getImmediateSupertypes(irClass: IrClass): List<IrSimpleType> {
val originalSupertypes = irClass.superTypes
val args = irClass.defaultType.arguments.mapNotNull { (it as? IrTypeProjection)?.type }
return originalSupertypes
.filter { it.classOrNull != null }
.map { superType ->
superType.substitute(superType.classOrNull!!.owner.typeParameters, args) as IrSimpleType
}
}
private fun collectAllSupertypes(irClass: IrClass, result: MutableSet<IrSimpleType>) {
val immediateSupertypes = getImmediateSupertypes(irClass)
result.addAll(immediateSupertypes)
for (supertype in immediateSupertypes) {
supertype.classOrNull.let { collectAllSupertypes(it!!.owner, result) }
}
}
fun getAllSupertypes(irClass: IrClass): MutableSet<IrSimpleType> {
val result = HashSet<IrSimpleType>()
collectAllSupertypes(irClass, result)
return result
}
@@ -139,6 +139,7 @@ val jvmPhases = namedIrFilePhase<JvmBackendContext>(
objectClassPhase then
makeInitializersPhase(JvmLoweredDeclarationOrigin.CLASS_STATIC_INITIALIZER, true) then
syntheticAccessorPhase then
collectionStubMethodLowering then
bridgePhase then
jvmOverloadsAnnotationPhase then
jvmStaticAnnotationPhase then
@@ -168,7 +168,7 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
addBridge(
irClass, targetForCommonBridges, method, signaturesToSkip,
defaultValueGenerator = null,
isSpecial = false
isSpecial = targetForCommonBridges.isCollectionStub()
)
}
}
@@ -214,7 +214,7 @@ private class BridgeLowering(val context: JvmBackendContext) : ClassLoweringPass
isSpecial: Boolean,
isSynthetic: Boolean
): IrSimpleFunction {
val modality = if (isSpecial) Modality.FINAL else Modality.OPEN
val modality = if (isSpecial && !target.isCollectionStub()) Modality.FINAL else Modality.OPEN
val origin = if (isSynthetic) IrDeclarationOrigin.BRIDGE else IrDeclarationOrigin.BRIDGE_SPECIAL
val visibility = if (signatureFunction.visibility === Visibilities.INTERNAL) Visibilities.PUBLIC else signatureFunction.visibility
@@ -429,6 +429,9 @@ private data class SignatureWithSource(val signature: Method, val source: IrSimp
fun IrSimpleFunction.overriddenInClasses(): Sequence<IrSimpleFunction> =
allOverridden().filter { !(it.parent.safeAs<IrClass>()?.isInterface ?: true) }
fun IrSimpleFunction.isCollectionStub(): Boolean =
origin == IrDeclarationOrigin.IR_BUILTINS_STUB
// TODO: At present, there is no reliable way to distinguish Java imports from Kotlin cross-module imports.
val ORIGINS_FROM_JAVA = setOf(IrDeclarationOrigin.IR_EXTERNAL_JAVA_DECLARATION_STUB, IrDeclarationOrigin.IR_EXTERNAL_DECLARATION_STUB)
@@ -0,0 +1,212 @@
/*
* Copyright 2010-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.backend.jvm.lower
import org.jetbrains.kotlin.backend.common.ClassLoweringPass
import org.jetbrains.kotlin.backend.common.descriptors.WrappedValueParameterDescriptor
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.common.lower.irThrow
import org.jetbrains.kotlin.backend.common.phaser.makeIrFilePhase
import org.jetbrains.kotlin.backend.jvm.JvmBackendContext
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.buildFun
import org.jetbrains.kotlin.ir.builders.irBlockBody
import org.jetbrains.kotlin.ir.builders.irCall
import org.jetbrains.kotlin.ir.builders.irString
import org.jetbrains.kotlin.ir.declarations.*
import org.jetbrains.kotlin.ir.declarations.impl.IrValueParameterImpl
import org.jetbrains.kotlin.ir.symbols.IrClassSymbol
import org.jetbrains.kotlin.ir.symbols.IrTypeParameterSymbol
import org.jetbrains.kotlin.ir.symbols.impl.IrValueParameterSymbolImpl
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
internal val collectionStubMethodLowering = makeIrFilePhase(
::CollectionStubMethodLowering,
name = "CollectionStubMethod",
description = "Generate Collection stub methods"
)
private class CollectionStubMethodLowering(val context: JvmBackendContext) : ClassLoweringPass {
private val state = context.state
private val typeMapper = state.typeMapper
override fun lower(irClass: IrClass) {
if (irClass.isInterface) {
return
}
val methodStubsToGenerate = generateRelevantStubMethods(irClass)
// We don't need to generate stub for existing methods, but for FAKE_OVERRIDE methods with ABSTRACT modality,
// it means an abstract function in superclass that is not implemented yet,
// stub generation is still needed to avoid invocation error.
val existingMethodSignatures = irClass.functions.filterNot {
it.modality == Modality.ABSTRACT && it.origin == IrDeclarationOrigin.FAKE_OVERRIDE
}.associateBy { it.toSignature() }
for (member in methodStubsToGenerate) {
val existingMethod = existingMethodSignatures[member.toSignature()]
if (existingMethod != null) {
// In the case that we find a defined method that matches the stub signature, we add the overridden symbols to that
// defined method, so that bridge lowering can still generate correct bridge for that method
existingMethod.overriddenSymbols.addAll(member.overriddenSymbols)
} else {
irClass.declarations.add(member)
}
}
}
//TODO: replace with new typeMapper using no descriptor
private fun IrSimpleFunction.toSignature() = typeMapper.mapAsmMethod(this.descriptor).toString()
private fun createStubMethod(function: IrSimpleFunction, irClass: IrClass, substitutionMap: Map<IrTypeParameterSymbol, IrType>): IrSimpleFunction {
return buildFun {
name = function.name
returnType = function.returnType.substitute(substitutionMap)
visibility = function.visibility
origin = IrDeclarationOrigin.IR_BUILTINS_STUB
modality = Modality.OPEN
}.apply {
// Replace Function metadata with the data from class
// Add the abstract function symbol to stub function for bridge lowering
overriddenSymbols.add(function.symbol)
parent = irClass
dispatchReceiverParameter = function.dispatchReceiverParameter?.copyWithSubstitution(this, substitutionMap)
extensionReceiverParameter = function.extensionReceiverParameter?.copyWithSubstitution(this, substitutionMap)
for (parameter in function.valueParameters) {
valueParameters.add(parameter.copyWithSubstitution(this, substitutionMap))
}
val exception = context.getTopLevelClass(FqName("java.lang.UnsupportedOperationException"))
// Function body consist only of throwing UnsupportedOperationException statement
body = context.createIrBuilder(function.symbol).irBlockBody {
+irThrow(
irCall(
exception.owner.constructors.single {
it.valueParameters.size == 1 && it.valueParameters.single().type.isNullableString()
}
).apply {
putValueArgument(0, irString("Operation is not supported for read-only collection"))
}
)
}
}
}
// Copy value parameter with type substitution
private fun IrValueParameter.copyWithSubstitution(target: IrSimpleFunction, substitutionMap: Map<IrTypeParameterSymbol, IrType>)
: IrValueParameter {
val descriptor = WrappedValueParameterDescriptor(this.descriptor.annotations)
return IrValueParameterImpl(
UNDEFINED_OFFSET, UNDEFINED_OFFSET,
IrDeclarationOrigin.IR_BUILTINS_STUB,
IrValueParameterSymbolImpl(descriptor),
name,
index,
type.substitute(substitutionMap),
varargElementType?.substitute(substitutionMap),
isCrossinline,
isNoinline
).apply {
descriptor.bind(this)
parent = target
}
}
// Compute a substitution map for type parameters between source class (Mutable Collection classes) to
// target class (class currently in lowering phase), this map is later used for substituting type parameters in generated functions
private fun computeSubstitutionMap(readOnlyClass: IrClass, mutableClass: IrClass, targetClass: IrClass)
: Map<IrTypeParameterSymbol, IrType> {
// We find the most specific type for the immutable collection class from the inheritance chain of target class
// Perform type substitution along searching, then use the type arguments obtained from the most specific type
// for type substitution.
val readOnlyClassType = getAllSupertypes(targetClass).findMostSpecificTypeForClass(readOnlyClass.symbol)
val readOnlyClassTypeArguments = (readOnlyClassType as IrSimpleType).arguments.mapNotNull { (it as? IrTypeProjection)?.type }
if (readOnlyClassTypeArguments.isEmpty() || readOnlyClassTypeArguments.size != mutableClass.typeParameters.size) {
throw IllegalStateException(
"Type argument mismatch between immutable class ${readOnlyClass.fqNameWhenAvailable}" +
" and mutable class ${mutableClass.fqNameWhenAvailable}, when processing" +
"class ${targetClass.fqNameWhenAvailable}"
)
}
return mutableClass.typeParameters.map { it.symbol }.zip(readOnlyClassTypeArguments).toMap()
}
private data class StubsForCollectionClass(
val readOnlyClass: IrClassSymbol,
val mutableClass: IrClassSymbol,
val mutableOnlyMethods: Collection<IrSimpleFunction>
)
private val preComputedStubs: Collection<StubsForCollectionClass> by lazy {
with(context.ir.symbols) {
listOf(
collection to mutableCollection,
set to mutableSet,
list to mutableList,
map to mutableMap,
mapEntry to mutableMapEntry,
iterable to mutableIterable,
iterator to mutableIterator,
listIterator to mutableListIterator
).map { (readOnlyClass, mutableClass) ->
val readOnlyMethodSignatures = readOnlyClass.functions.map { it.owner.toSignature() }.toHashSet()
val mutableMethods = mutableClass.functions
.map { it.owner }
.filter { it.toSignature() !in readOnlyMethodSignatures }
.toHashSet()
StubsForCollectionClass(readOnlyClass, mutableClass, mutableMethods)
}
}
}
// Compute stubs that should be generated, compare based on signature
private fun generateRelevantStubMethods(irClass: IrClass): Set<IrSimpleFunction> {
val ourStubsForCollectionClasses = preComputedStubs.filter { (readOnlyClass, mutableClass) ->
irClass.superTypes.any { supertypeSymbol ->
val supertype = supertypeSymbol.classOrNull?.owner
// We need to generate stub methods for following 2 cases:
// current class's direct super type is a java class or kotlin interface, and is an subtype of an immutable collection
supertype != null
&& (supertype.comesFromJava() || supertype.isInterface)
&& supertypeSymbol.isSubtypeOfClass(readOnlyClass)
&& !irClass.symbol.isSubtypeOfClass(mutableClass)
}
}
// do a second filtering to ensure only most relevant classes are included.
val redundantClasses = ourStubsForCollectionClasses.filter { (readOnlyClass) ->
ourStubsForCollectionClasses.any { readOnlyClass != it.readOnlyClass && it.readOnlyClass.isSubtypeOfClass(readOnlyClass) }
}.map { it.readOnlyClass }
// perform type substitution and type erasure here
return ourStubsForCollectionClasses.filter { (readOnlyClass) ->
readOnlyClass !in redundantClasses
}.flatMap { (readOnlyClass, mutableClass, mutableOnlyMethods) ->
val substitutionMap = computeSubstitutionMap(readOnlyClass.owner, mutableClass.owner, irClass)
mutableOnlyMethods.map { function ->
createStubMethod(function, irClass, substitutionMap)
}
}.toHashSet()
}
fun IrClass.comesFromJava() = origin in ORIGINS_FROM_JAVA
private fun Collection<IrType>.findMostSpecificTypeForClass(classifier: IrClassSymbol): IrType {
val types = this.filter { it.classifierOrNull == classifier }
if (types.isEmpty()) error("No supertype of $classifier in $this")
if (types.size == 1) return types.first()
// Find the first type in the list such that it's a subtype of every other type in that list
return types.first { type ->
types.all { other -> type.isSubtypeOfClass(other.classOrNull!!) }
}
}
}
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyCollection<T>: Collection<T> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyList<T>: List<T> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyListIterator<T> : ListIterator<T> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyList<T>(val v: T): List<T> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
open class Super<T>(val v: T) {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyMap<K, V>: Map<K, V> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyMapEntry<K, V>: Map.Entry<K, V> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyList: List<String> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
import java.util.ArrayList
@@ -1,5 +1,4 @@
// SKIP_JDK6
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FULL_JDK
// WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
interface Addable {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
open class SetStringImpl {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// WITH_RUNTIME
@@ -1,5 +1,4 @@
// SKIP_JDK6
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FULL_JDK
// WITH_RUNTIME
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
class MyCollection<T> : Collection<List<Iterator<T>>> {
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FILE: Test.java
@@ -1,4 +1,3 @@
// IGNORE_BACKEND: JVM_IR
// TARGET_BACKEND: JVM
// FILE: B.java