Parcelize: Implement support for the JVM IR backend.

This commit is contained in:
Steven Schäfer
2020-04-07 14:48:39 +02:00
committed by Alexander Udalov
parent 7cad2be329
commit 779133e71e
13 changed files with 1852 additions and 12 deletions
@@ -18,6 +18,7 @@ dependencies {
compileOnly(project(":compiler:frontend.java"))
compileOnly(project(":compiler:backend"))
compileOnly(project(":compiler:ir.backend.common"))
compileOnly(project(":compiler:backend.jvm"))
compileOnly(project(":kotlin-android-extensions-runtime"))
compileOnly(intellijCoreDep()) { includeJars("intellij-core") }
compileOnly(intellijDep()) { includeJars("asm-all", rootProject = rootProject) }
@@ -25,6 +26,7 @@ dependencies {
testCompile(project(":compiler:util"))
testCompile(project(":compiler:backend"))
testCompile(project(":compiler:ir.backend.common"))
testCompile(project(":compiler:backend.jvm"))
testCompile(project(":compiler:cli"))
testCompile(project(":kotlin-android-extensions-runtime"))
testCompile(projectTests(":compiler:tests-common"))
@@ -9,6 +9,7 @@ import kotlinx.android.parcel.TypeParceler
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension.Companion.createMethod
import org.jetbrains.kotlin.android.parcel.serializers.*
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.android.parcel.serializers.ParcelableExtensionBase.Companion.FILE_DESCRIPTOR_FQNAME
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.codegen.*
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
@@ -44,11 +45,6 @@ import org.jetbrains.org.objectweb.asm.Type
import java.io.FileDescriptor
open class ParcelableCodegenExtension : ParcelableExtensionBase, ExpressionCodegenExtension {
companion object {
private val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
}
@Deprecated(
"@Parcelize is now available in non-experimental setups as well.",
replaceWith = ReplaceWith("true"),
@@ -0,0 +1,18 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel
import org.jetbrains.kotlin.android.parcel.ir.AndroidSymbols
import org.jetbrains.kotlin.android.parcel.ir.ParcelableIrTransformer
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.extensions.PureIrGenerationExtension
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
class ParcelableIrGeneratorExtension : PureIrGenerationExtension {
override fun generate(moduleFragment: IrModuleFragment, context: CommonBackendContext) {
ParcelableIrTransformer(context, AndroidSymbols(context, moduleFragment)).transform(moduleFragment)
}
}
@@ -0,0 +1,89 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
// This file is autogenerated based on android.jar, do not edit it directly.
package org.jetbrains.kotlin.android.parcel.ir
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrSymbol
// An IR builder with access to AndroidSymbols and convenience methods to build calls to some of these methods.
class AndroidIrBuilder internal constructor(
val androidSymbols: AndroidSymbols,
symbol: IrSymbol,
startOffset: Int,
endOffset: Int
) : IrBuilderWithScope(
IrGeneratorContextBase(androidSymbols.irBuiltIns), Scope(symbol),
startOffset, endOffset
) {
fun parcelReadInt(receiver: IrExpression): IrExpression =
irCall(androidSymbols.parcelReadInt).apply {
dispatchReceiver = receiver
}
fun parcelReadParcelable(receiver: IrExpression, loader: IrExpression): IrExpression =
irCall(androidSymbols.parcelReadParcelable).apply {
dispatchReceiver = receiver
putValueArgument(0, loader)
}
fun parcelReadString(receiver: IrExpression): IrExpression =
irCall(androidSymbols.parcelReadString).apply {
dispatchReceiver = receiver
}
fun parcelReadValue(receiver: IrExpression, loader: IrExpression): IrExpression =
irCall(androidSymbols.parcelReadValue).apply {
dispatchReceiver = receiver
putValueArgument(0, loader)
}
fun parcelWriteInt(receiver: IrExpression, value: IrExpression): IrExpression =
irCall(androidSymbols.parcelWriteInt).apply {
dispatchReceiver = receiver
putValueArgument(0, value)
}
fun parcelWriteParcelable(
receiver: IrExpression,
p: IrExpression,
parcelableFlags: IrExpression
): IrExpression = irCall(androidSymbols.parcelWriteParcelable).apply {
dispatchReceiver = receiver
putValueArgument(0, p)
putValueArgument(1, parcelableFlags)
}
fun parcelWriteString(receiver: IrExpression, value: IrExpression): IrExpression =
irCall(androidSymbols.parcelWriteString).apply {
dispatchReceiver = receiver
putValueArgument(0, value)
}
fun parcelWriteValue(receiver: IrExpression, v: IrExpression): IrExpression =
irCall(androidSymbols.parcelWriteValue).apply {
dispatchReceiver = receiver
putValueArgument(0, v)
}
fun textUtilsWriteToParcel(
cs: IrExpression,
p: IrExpression,
parcelableFlags: IrExpression
): IrExpression = irCall(androidSymbols.textUtilsWriteToParcel).apply {
putValueArgument(0, cs)
putValueArgument(1, p)
putValueArgument(2, parcelableFlags)
}
fun classGetClassLoader(receiver: IrExpression): IrExpression =
irCall(androidSymbols.classGetClassLoader).apply {
dispatchReceiver = receiver
}
fun getTextUtilsCharSequenceCreator(): IrExpression =
irGetField(null, androidSymbols.textUtilsCharSequenceCreator.owner)
}
@@ -0,0 +1,477 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
*/
// This file was autogenerated based on android.jar, do not edit it directly.
package org.jetbrains.kotlin.android.parcel.ir
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.Symbols
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.impl.EmptyPackageFragmentDescriptor
import org.jetbrains.kotlin.ir.UNDEFINED_OFFSET
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrPackageFragment
import org.jetbrains.kotlin.ir.declarations.impl.IrExternalPackageFragmentImpl
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.symbols.*
import org.jetbrains.kotlin.ir.symbols.impl.IrExternalPackageFragmentSymbolImpl
import org.jetbrains.kotlin.ir.types.defaultType
import org.jetbrains.kotlin.ir.types.makeNullable
import org.jetbrains.kotlin.ir.types.starProjectedType
import org.jetbrains.kotlin.ir.types.typeWith
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
// All of the IR declarations needed by the parcelize plugin. Note that the declarations are generated based on JVM descriptors and
// hence contain just enough information to produce correct JVM bytecode for *calls*. In particular, we omit generic types and
// supertypes, which are not needed to produce correct bytecode.
class AndroidSymbols(
context: CommonBackendContext,
private val moduleFragment: IrModuleFragment
) {
val irBuiltIns: IrBuiltIns = context.irBuiltIns
val irSymbols: Symbols<CommonBackendContext> = context.ir.symbols
private val androidOs: IrPackageFragment = createPackage("android.os")
private val androidText: IrPackageFragment = createPackage("android.text")
private val androidUtil: IrPackageFragment = createPackage("android.util")
private val javaIo: IrPackageFragment = createPackage("java.io")
private val javaLang: IrPackageFragment = createPackage("java.lang")
private val javaUtil: IrPackageFragment = createPackage("java.util")
private val kotlinJvm: IrPackageFragment = createPackage("kotlin.jvm")
private val androidOsBundle: IrClassSymbol =
createClass(androidOs, "Bundle", ClassKind.CLASS, Modality.FINAL)
private val androidOsIBinder: IrClassSymbol =
createClass(androidOs, "IBinder", ClassKind.INTERFACE, Modality.ABSTRACT)
val androidOsParcel: IrClassSymbol =
createClass(androidOs, "Parcel", ClassKind.CLASS, Modality.FINAL)
private val androidOsParcelFileDescriptor: IrClassSymbol =
createClass(androidOs, "ParcelFileDescriptor", ClassKind.CLASS, Modality.OPEN)
private val androidOsParcelable: IrClassSymbol =
createClass(androidOs, "Parcelable", ClassKind.INTERFACE, Modality.ABSTRACT)
private val androidOsPersistableBundle: IrClassSymbol =
createClass(androidOs, "PersistableBundle", ClassKind.CLASS, Modality.FINAL)
private val androidTextTextUtils: IrClassSymbol =
createClass(androidText, "TextUtils", ClassKind.CLASS, Modality.OPEN)
private val androidUtilSize: IrClassSymbol =
createClass(androidUtil, "Size", ClassKind.CLASS, Modality.FINAL)
private val androidUtilSizeF: IrClassSymbol =
createClass(androidUtil, "SizeF", ClassKind.CLASS, Modality.FINAL)
private val androidUtilSparseBooleanArray: IrClassSymbol =
createClass(androidUtil, "SparseBooleanArray", ClassKind.CLASS, Modality.OPEN)
private val javaIoFileDescriptor: IrClassSymbol =
createClass(javaIo, "FileDescriptor", ClassKind.CLASS, Modality.FINAL)
private val javaIoSerializable: IrClassSymbol =
createClass(javaIo, "Serializable", ClassKind.INTERFACE, Modality.ABSTRACT)
val javaLangClass: IrClassSymbol =
createClass(javaLang, "Class", ClassKind.CLASS, Modality.FINAL)
private val javaLangClassLoader: IrClassSymbol =
createClass(javaLang, "ClassLoader", ClassKind.CLASS, Modality.ABSTRACT)
private val javaUtilArrayList: IrClassSymbol =
createClass(javaUtil, "ArrayList", ClassKind.CLASS, Modality.OPEN)
private val javaUtilLinkedHashMap: IrClassSymbol =
createClass(javaUtil, "LinkedHashMap", ClassKind.CLASS, Modality.OPEN)
private val javaUtilLinkedHashSet: IrClassSymbol =
createClass(javaUtil, "LinkedHashSet", ClassKind.CLASS, Modality.OPEN)
private val javaUtilList: IrClassSymbol =
createClass(javaUtil, "List", ClassKind.INTERFACE, Modality.ABSTRACT)
private val javaUtilTreeMap: IrClassSymbol =
createClass(javaUtil, "TreeMap", ClassKind.CLASS, Modality.OPEN)
private val javaUtilTreeSet: IrClassSymbol =
createClass(javaUtil, "TreeSet", ClassKind.CLASS, Modality.OPEN)
val androidOsParcelableCreator: IrClassSymbol = buildClass {
name = Name.identifier("Creator")
kind = ClassKind.INTERFACE
modality = Modality.ABSTRACT
}.apply {
createImplicitParameterDeclarationWithWrappedDescriptor()
val t = addTypeParameter("T", context.irBuiltIns.anyNType)
parent = androidOsParcelable.owner
addFunction("createFromParcel", t.defaultType, Modality.ABSTRACT).apply {
addValueParameter("source", androidOsParcel.defaultType)
}
addFunction(
"newArray", irBuiltIns.arrayClass.typeWith(t.defaultType.makeNullable()),
Modality.ABSTRACT
).apply {
addValueParameter("size", context.irBuiltIns.intType)
}
}.symbol
val kotlinKClassJava: IrPropertySymbol = buildProperty {
name = Name.identifier("java")
}.apply {
parent = kotlinJvm
addGetter().apply {
addExtensionReceiver(irBuiltIns.kClassClass.starProjectedType)
returnType = javaLangClass.defaultType
}
}.symbol
val parcelCreateBinderArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createBinderArray", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType)).symbol
val parcelCreateBinderArrayList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createBinderArrayList", javaUtilArrayList.defaultType).symbol
val parcelCreateBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createBooleanArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType
).symbol
val parcelCreateByteArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createByteArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType
).symbol
val parcelCreateCharArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createCharArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType
).symbol
val parcelCreateDoubleArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createDoubleArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType
).symbol
val parcelCreateFloatArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createFloatArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType
).symbol
val parcelCreateIntArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createIntArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType
).symbol
val parcelCreateLongArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction(
"createLongArray",
irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType
).symbol
val parcelCreateStringArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createStringArray", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType)).symbol
val parcelCreateStringArrayList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("createStringArrayList", javaUtilArrayList.defaultType).symbol
val parcelReadBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readBundle", androidOsBundle.defaultType).symbol
val parcelReadByte: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readByte", irBuiltIns.byteType).symbol
val parcelReadDouble: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readDouble", irBuiltIns.doubleType).symbol
val parcelReadFileDescriptor: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readFileDescriptor", androidOsParcelFileDescriptor.defaultType).symbol
val parcelReadFloat: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readFloat", irBuiltIns.floatType).symbol
val parcelReadInt: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readInt", irBuiltIns.intType).symbol
val parcelReadLong: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readLong", irBuiltIns.longType).symbol
val parcelReadParcelable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readParcelable", androidOsParcelable.defaultType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelReadPersistableBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readPersistableBundle", androidOsPersistableBundle.defaultType).symbol
val parcelReadSerializable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSerializable", javaIoSerializable.defaultType).symbol
val parcelReadSize: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSize", androidUtilSize.defaultType).symbol
val parcelReadSizeF: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSizeF", androidUtilSizeF.defaultType).symbol
val parcelReadSparseBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readSparseBooleanArray", androidUtilSparseBooleanArray.defaultType).symbol
val parcelReadString: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readString", irBuiltIns.stringType).symbol
val parcelReadStrongBinder: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readStrongBinder", androidOsIBinder.defaultType).symbol
val parcelReadValue: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("readValue", irBuiltIns.anyNType).apply {
addValueParameter("loader", javaLangClassLoader.defaultType)
}.symbol
val parcelWriteBinderArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBinderArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.arrayClass.typeWith(androidOsIBinder.defaultType))
}.symbol
val parcelWriteBinderList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBinderList", irBuiltIns.unitType).apply {
addValueParameter("val", javaUtilList.defaultType)
}.symbol
val parcelWriteBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBooleanArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.booleanType).defaultType)
}.symbol
val parcelWriteBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeBundle", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsBundle.defaultType)
}.symbol
val parcelWriteByte: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeByte", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.byteType)
}.symbol
val parcelWriteByteArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeByteArray", irBuiltIns.unitType).apply {
addValueParameter("b", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.byteType).defaultType)
}.symbol
val parcelWriteCharArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeCharArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.charType).defaultType)
}.symbol
val parcelWriteDouble: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeDouble", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.doubleType)
}.symbol
val parcelWriteDoubleArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeDoubleArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.doubleType).defaultType)
}.symbol
val parcelWriteFileDescriptor: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFileDescriptor", irBuiltIns.unitType).apply {
addValueParameter("val", javaIoFileDescriptor.defaultType)
}.symbol
val parcelWriteFloat: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFloat", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.floatType)
}.symbol
val parcelWriteFloatArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeFloatArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.floatType).defaultType)
}.symbol
val parcelWriteInt: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeInt", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.intType)
}.symbol
val parcelWriteIntArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeIntArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.intType).defaultType)
}.symbol
val parcelWriteLong: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeLong", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.longType)
}.symbol
val parcelWriteLongArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeLongArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.primitiveArrayForType.getValue(irBuiltIns.longType).defaultType)
}.symbol
val parcelWriteParcelable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeParcelable", irBuiltIns.unitType).apply {
addValueParameter("p", androidOsParcelable.defaultType)
addValueParameter("parcelableFlags", irBuiltIns.intType)
}.symbol
val parcelWritePersistableBundle: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writePersistableBundle", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsPersistableBundle.defaultType)
}.symbol
val parcelWriteSerializable: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSerializable", irBuiltIns.unitType).apply {
addValueParameter("s", javaIoSerializable.defaultType)
}.symbol
val parcelWriteSize: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSize", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSize.defaultType)
}.symbol
val parcelWriteSizeF: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSizeF", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSizeF.defaultType)
}.symbol
val parcelWriteSparseBooleanArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeSparseBooleanArray", irBuiltIns.unitType).apply {
addValueParameter("val", androidUtilSparseBooleanArray.defaultType)
}.symbol
val parcelWriteString: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeString", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.stringType)
}.symbol
val parcelWriteStringArray: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStringArray", irBuiltIns.unitType).apply {
addValueParameter("val", irBuiltIns.arrayClass.typeWith(irBuiltIns.stringType))
}.symbol
val parcelWriteStringList: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStringList", irBuiltIns.unitType).apply {
addValueParameter("val", javaUtilList.defaultType)
}.symbol
val parcelWriteStrongBinder: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeStrongBinder", irBuiltIns.unitType).apply {
addValueParameter("val", androidOsIBinder.defaultType)
}.symbol
val parcelWriteValue: IrSimpleFunctionSymbol =
androidOsParcel.owner.addFunction("writeValue", irBuiltIns.unitType).apply {
addValueParameter("v", irBuiltIns.anyNType)
}.symbol
val textUtilsWriteToParcel: IrSimpleFunctionSymbol =
androidTextTextUtils.owner.addFunction("writeToParcel", irBuiltIns.unitType, isStatic = true).apply {
addValueParameter("cs", irSymbols.charSequence.defaultType)
addValueParameter("p", androidOsParcel.defaultType)
addValueParameter("parcelableFlags", irBuiltIns.intType)
}.symbol
val classGetClassLoader: IrSimpleFunctionSymbol =
javaLangClass.owner.addFunction("getClassLoader", javaLangClassLoader.defaultType).symbol
val arrayListConstructor: IrConstructorSymbol = javaUtilArrayList.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val arrayListAdd: IrSimpleFunctionSymbol =
javaUtilArrayList.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val linkedHashMapConstructor: IrConstructorSymbol =
javaUtilLinkedHashMap.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val linkedHashMapPut: IrSimpleFunctionSymbol =
javaUtilLinkedHashMap.owner.addFunction("put", irBuiltIns.anyNType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
addValueParameter("p_1", irBuiltIns.anyNType)
}.symbol
val linkedHashSetConstructor: IrConstructorSymbol =
javaUtilLinkedHashSet.owner.addConstructor().apply {
addValueParameter("p_0", irBuiltIns.intType)
}.symbol
val linkedHashSetAdd: IrSimpleFunctionSymbol =
javaUtilLinkedHashSet.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val treeMapConstructor: IrConstructorSymbol = javaUtilTreeMap.owner.addConstructor().symbol
val treeMapPut: IrSimpleFunctionSymbol =
javaUtilTreeMap.owner.addFunction("put", irBuiltIns.anyNType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
addValueParameter("p_1", irBuiltIns.anyNType)
}.symbol
val treeSetConstructor: IrConstructorSymbol = javaUtilTreeSet.owner.addConstructor().symbol
val treeSetAdd: IrSimpleFunctionSymbol =
javaUtilTreeSet.owner.addFunction("add", irBuiltIns.booleanType).apply {
addValueParameter("p_0", irBuiltIns.anyNType)
}.symbol
val textUtilsCharSequenceCreator: IrFieldSymbol = androidTextTextUtils.owner.addField {
name = Name.identifier("CHAR_SEQUENCE_CREATOR")
type = androidOsParcelableCreator.defaultType
isStatic = true
}.symbol
private fun createPackage(packageName: String): IrPackageFragment =
IrExternalPackageFragmentImpl(
IrExternalPackageFragmentSymbolImpl(
EmptyPackageFragmentDescriptor(
moduleFragment.descriptor,
FqName(packageName)
)
)
)
private fun createClass(
irPackage: IrPackageFragment,
shortName: String,
classKind: ClassKind,
classModality: Modality
): IrClassSymbol = buildClass {
name = Name.identifier(shortName)
kind = classKind
modality = classModality
}.apply {
parent = irPackage
createImplicitParameterDeclarationWithWrappedDescriptor()
}.symbol
fun createBuilder(
symbol: IrSymbol,
startOffset: Int = UNDEFINED_OFFSET,
endOffset: Int = UNDEFINED_OFFSET
) = AndroidIrBuilder(this, symbol, startOffset, endOffset)
}
@@ -0,0 +1,285 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel.ir
import org.jetbrains.kotlin.android.parcel.serializers.RAWVALUE_ANNOTATION_FQNAME
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.getArrayElementType
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.resolve.source.PsiSourceElement
class IrParcelSerializerFactory(symbols: AndroidSymbols) {
/**
* Resolve the given [irType] to a corresponding [IrParcelSerializer]. This depends on the TypeParcelers which
* are currently in [scope], as well as the type of the enclosing Parceleable class [parcelizeType], which is needed
* to get a class loader for reflection based serialization. Beyond this, we need to know whether to allow
* using read/writeValue for serialization (if [strict] is false). Beyond this, we need to know whether we are
* producing parcelers for properties of a Parcelable (if [toplevel] is true), or for a complete Parcelable.
*/
fun get(
irType: IrType,
scope: IrParcelerScope?,
parcelizeType: IrType,
strict: Boolean = false,
toplevel: Boolean = false
): IrParcelSerializer {
fun strict() = strict && !irType.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
scope.getCustomSerializer(irType)?.let { parceler ->
return IrCustomParcelSerializer(parceler)
}
// TODO inline classes
val classifier = irType.erasedUpperBound
val classifierFqName = classifier.fqNameWhenAvailable?.asString()
when (classifierFqName) {
// Built-in parcel serializers
"kotlin.String", "java.lang.String" ->
return stringSerializer
"kotlin.CharSequence", "java.lang.CharSequence" ->
return charSequenceSerializer
"android.os.Bundle" ->
return bundleSerializer
"android.os.PersistableBundle" ->
return persistableBundleSerializer
// Non-nullable built-in serializers
"kotlin.Byte", "java.lang.Byte" ->
return wrapNullableSerializerIfNeeded(irType, byteSerializer)
"kotlin.Boolean", "java.lang.Boolean" ->
return wrapNullableSerializerIfNeeded(irType, booleanSerializer)
"kotlin.Char", "java.lang.Character" ->
return wrapNullableSerializerIfNeeded(irType, charSerializer)
"kotlin.Short", "java.lang.Short" ->
return wrapNullableSerializerIfNeeded(irType, shortSerializer)
"kotlin.Int", "java.lang.Integer" ->
return wrapNullableSerializerIfNeeded(irType, intSerializer)
"kotlin.Long", "java.lang.Long" ->
return wrapNullableSerializerIfNeeded(irType, longSerializer)
"kotlin.Float", "java.lang.Float" ->
return wrapNullableSerializerIfNeeded(irType, floatSerializer)
"kotlin.Double", "java.lang.Double" ->
return wrapNullableSerializerIfNeeded(irType, doubleSerializer)
"java.io.FileDescriptor" ->
return wrapNullableSerializerIfNeeded(irType, fileDescriptorSerializer)
"android.util.Size" ->
return wrapNullableSerializerIfNeeded(irType, sizeSerializer)
"android.util.SizeF" ->
return wrapNullableSerializerIfNeeded(irType, sizeFSerializer)
// Built-in non-parameterized container types.
"kotlin.IntArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.intType))
return intArraySerializer
"kotlin.BooleanArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.booleanType))
return booleanArraySerializer
"kotlin.ByteArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.byteType))
return byteArraySerializer
"kotlin.CharArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.charType))
return charArraySerializer
"kotlin.FloatArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.floatType))
return floatArraySerializer
"kotlin.DoubleArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.doubleType))
return doubleArraySerializer
"kotlin.LongArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.longType))
return longArraySerializer
"android.util.SparseBooleanArray" ->
if (!scope.hasCustomSerializer(irBuiltIns.booleanType))
return sparseBooleanArraySerializer
}
// Generic container types
when (classifierFqName) {
// Apart from kotlin.Array and kotlin.ShortArray, these will only be hit if we have a
// special parceler for the element type.
"kotlin.Array", "kotlin.ShortArray", "kotlin.IntArray",
"kotlin.BooleanArray", "kotlin.ByteArray", "kotlin.CharArray",
"kotlin.FloatArray", "kotlin.DoubleArray", "kotlin.LongArray" -> {
val elementType = irType.getArrayElementType(irBuiltIns)
if (!scope.hasCustomSerializer(elementType)) {
when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) {
"java.lang.String", "kotlin.String" ->
return stringArraySerializer
"android.os.IBinder" ->
return iBinderArraySerializer
}
}
val arrayType =
if (classifier.defaultType.isPrimitiveArray()) classifier.defaultType else irBuiltIns.arrayClass.typeWith(elementType)
return IrArrayParcelSerializer(arrayType, elementType, get(elementType, scope, parcelizeType, strict()))
}
// This will only be hit if we have a custom serializer for booleans
"android.util.SparseBooleanArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.booleanType,
get(irBuiltIns.booleanType, scope, parcelizeType, strict())
)
"android.util.SparseIntArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.intType,
get(irBuiltIns.intType, scope, parcelizeType, strict())
)
"android.util.SparseLongArray" ->
return IrSparseArrayParcelSerializer(
classifier,
irBuiltIns.longType,
get(irBuiltIns.longType, scope, parcelizeType, strict())
)
"android.util.SparseArray" -> {
val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns)
return IrSparseArrayParcelSerializer(classifier, elementType, get(elementType, scope, parcelizeType, strict()))
}
// TODO: More java collections?
// TODO: Add tests for all of these types, not just some common ones...
// FIXME: Is the support for ArrayDeque missing in the old BE?
"kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List",
"kotlin.collections.ArrayList", "java.util.ArrayList",
"kotlin.collections.ArrayDeque", "java.util.ArrayDeque",
"kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set",
"kotlin.collections.HashSet", "java.util.HashSet",
"kotlin.collections.LinkedHashSet", "java.util.LinkedHashSet",
"java.util.NavigableSet", "java.util.SortedSet" -> {
val elementType = (irType as IrSimpleType).arguments.single().upperBound(irBuiltIns)
if (!scope.hasCustomSerializer(elementType) && classifierFqName in setOf(
"kotlin.collections.List", "kotlin.collections.MutableList", "kotlin.collections.ArrayList",
"java.util.List", "java.util.ArrayList"
)
) {
when (elementType.erasedUpperBound.fqNameWhenAvailable?.asString()) {
"android.os.IBinder" ->
return iBinderListSerializer
"kotlin.String", "java.lang.String" ->
return stringListSerializer
}
}
return wrapNullableSerializerIfNeeded(
irType,
IrListParcelSerializer(classifier, get(elementType, scope, parcelizeType, strict()))
)
}
"kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map",
"kotlin.collections.HashMap", "java.util.HashMap",
"kotlin.collections.LinkedHashMap", "java.util.LinkedHashMap",
"java.util.SortedMap", "java.util.NavigableMap", "java.util.TreeMap",
"java.util.concurrent.ConcurrentHashMap" -> {
val keyType = (irType as IrSimpleType).arguments[0].upperBound(irBuiltIns)
val valueType = irType.arguments[1].upperBound(irBuiltIns)
val parceler =
IrMapParcelSerializer(
classifier,
get(keyType, scope, parcelizeType, strict()),
get(valueType, scope, parcelizeType, strict())
)
return wrapNullableSerializerIfNeeded(irType, parceler)
}
}
// Generic parceleable types
when {
classifier.isSubclassOfFqName("android.os.Parcelable")
// Avoid infinite loops when deriving parcelers for enum or object classes.
&& !(toplevel && (classifier.isObject || classifier.isEnumClass)) -> {
// We try to use writeToParcel/createFromParcel directly whenever possible, but there are some caveats.
//
// According to the JLS, changing a class from final to non-final is a binary compatible change, hence we
// cannot use the writeToParcel/createFromParcel methods directly when serializing classes from external
// dependencies. This issue was originally reported in KT-20029.
//
// Conversely, we can and should apply this optimization to all classes in the current module which
// implement Parcelable (KT-20030). There are two cases to consider. If the class is annotated with
// @Parcelize, we will create the corresponding methods/fields ourselves, before we generate the code
// for writeToParcel/createFromParcel. For Java classes (or compiled Kotlin classes annotated with
// @Parcelize), we'll have a field in the class itself. Finally, with Parcelable instances which were
// manually implemented in Kotlin, we'll instead have an @JvmField property getter in the companion object.
return if (classifier.modality == Modality.FINAL && classifier.descriptor.source is PsiSourceElement
&& (classifier.isParcelize || classifier.hasCreatorField)
) {
wrapNullableSerializerIfNeeded(irType, IrEfficientParcelableParcelSerializer(classifier))
} else {
// In all other cases, we have to use the generic methods in Parcel, which use reflection internally.
IrGenericParcelableParcelSerializer(parcelizeType)
}
}
classifier.isSubclassOfFqName("android.os.IBinder") ->
return iBinderSerializer
classifier.isObject ->
return IrObjectParcelSerializer(classifier)
classifier.isEnumClass ->
return wrapNullableSerializerIfNeeded(irType, IrEnumParcelSerializer(classifier))
classifier.isSubclassOfFqName("java.io.Serializable") ->
return serializableSerializer
strict() ->
throw IllegalArgumentException("Illegal type, could not find a specific serializer for ${irType.render()}")
else ->
return IrGenericValueParcelSerializer(parcelizeType)
}
}
private fun wrapNullableSerializerIfNeeded(irType: IrType, serializer: IrParcelSerializer) =
if (irType.isNullable()) IrNullAwareParcelSerializer(serializer) else serializer
private val irBuiltIns: IrBuiltIns = symbols.irBuiltIns
private val stringArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArray, symbols.parcelWriteStringArray)
private val stringListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateStringArrayList, symbols.parcelWriteStringList)
private val iBinderSerializer = IrSimpleParcelSerializer(symbols.parcelReadStrongBinder, symbols.parcelWriteStrongBinder)
private val iBinderArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArray, symbols.parcelWriteBinderArray)
private val iBinderListSerializer = IrSimpleParcelSerializer(symbols.parcelCreateBinderArrayList, symbols.parcelWriteBinderList)
private val serializableSerializer = IrSimpleParcelSerializer(symbols.parcelReadSerializable, symbols.parcelWriteSerializable)
private val stringSerializer = IrSimpleParcelSerializer(symbols.parcelReadString, symbols.parcelWriteString)
private val byteSerializer = IrSimpleParcelSerializer(symbols.parcelReadByte, symbols.parcelWriteByte)
private val intSerializer = IrSimpleParcelSerializer(symbols.parcelReadInt, symbols.parcelWriteInt)
private val longSerializer = IrSimpleParcelSerializer(symbols.parcelReadLong, symbols.parcelWriteLong)
private val floatSerializer = IrSimpleParcelSerializer(symbols.parcelReadFloat, symbols.parcelWriteFloat)
private val doubleSerializer = IrSimpleParcelSerializer(symbols.parcelReadDouble, symbols.parcelWriteDouble)
private val intArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateIntArray, symbols.parcelWriteIntArray)
private val booleanArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateBooleanArray, symbols.parcelWriteBooleanArray)
private val byteArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateByteArray, symbols.parcelWriteByteArray)
private val charArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateCharArray, symbols.parcelWriteCharArray)
private val doubleArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateDoubleArray, symbols.parcelWriteDoubleArray)
private val floatArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateFloatArray, symbols.parcelWriteFloatArray)
private val longArraySerializer = IrSimpleParcelSerializer(symbols.parcelCreateLongArray, symbols.parcelWriteLongArray)
// Primitive types without dedicated read/write methods need an additional cast.
private val booleanSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.booleanType, intSerializer)
private val shortSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.shortType, intSerializer)
private val charSerializer = IrWrappedPrimitiveParcelSerializer(irBuiltIns.charType, intSerializer)
private val charSequenceSerializer = IrCharSequenceParcelSerializer()
// TODO The old backend uses the hidden "read/writeRawFileDescriptor" methods.
private val fileDescriptorSerializer = IrSimpleParcelSerializer(symbols.parcelReadFileDescriptor, symbols.parcelWriteFileDescriptor)
private val sizeSerializer = IrSimpleParcelSerializer(symbols.parcelReadSize, symbols.parcelWriteSize)
private val sizeFSerializer = IrSimpleParcelSerializer(symbols.parcelReadSizeF, symbols.parcelWriteSizeF)
private val bundleSerializer = IrSimpleParcelSerializer(symbols.parcelReadBundle, symbols.parcelWriteBundle)
private val persistableBundleSerializer =
IrSimpleParcelSerializer(symbols.parcelReadPersistableBundle, symbols.parcelWritePersistableBundle)
private val sparseBooleanArraySerializer =
IrSimpleParcelSerializer(symbols.parcelReadSparseBooleanArray, symbols.parcelWriteSparseBooleanArray)
}
@@ -0,0 +1,505 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel.ir
import org.jetbrains.kotlin.android.parcel.serializers.ParcelableExtensionBase.Companion.CREATOR_NAME
import org.jetbrains.kotlin.backend.jvm.codegen.isJvmInterface
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.backend.jvm.ir.isBoxedArray
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.symbols.IrConstructorSymbol
import org.jetbrains.kotlin.ir.symbols.IrFunctionSymbol
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
interface IrParcelSerializer {
fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression
fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression
}
fun AndroidIrBuilder.readParcelWith(serializer: IrParcelSerializer, parcel: IrValueDeclaration): IrExpression =
with(serializer) { readParcel(parcel) }
fun AndroidIrBuilder.writeParcelWith(
serializer: IrParcelSerializer,
parcel: IrValueDeclaration,
flags: IrValueDeclaration,
value: IrExpression
): IrExpression =
with(serializer) { writeParcel(parcel, flags, value) }
// Creates a serializer from a pair of parcel methods of the form reader()T and writer(T)V.
class IrSimpleParcelSerializer(private val reader: IrSimpleFunctionSymbol, private val writer: IrSimpleFunctionSymbol) :
IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irCall(reader).apply { dispatchReceiver = irGet(parcel) }
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
irCall(writer).apply {
dispatchReceiver = irGet(parcel)
putValueArgument(0, value)
}
}
// Serialize a value of the primitive type [parcelType] by wrapping the given [serializer] with casts.
class IrWrappedPrimitiveParcelSerializer(private val parcelType: IrType, private val serializer: IrParcelSerializer) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
val deserializedPrimitive = readParcelWith(serializer, parcel)
return if (parcelType.isBoolean()) {
irIfThenElse(
parcelType,
irNotEquals(deserializedPrimitive, irInt(0)),
irInt(1),
irInt(0)
)
} else {
val conversion = deserializedPrimitive.type.getClass()!!.functions.first { function ->
function.name.asString() == "to${parcelType.getClass()!!.name}"
}
irCall(conversion).apply {
dispatchReceiver = deserializedPrimitive
}
}
}
private fun IrBuilderWithScope.irCastIfNeeded(expression: IrExpression, irType: IrType): IrExpression =
if (expression.type == irType) expression else irAs(expression, irType)
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
writeParcelWith(serializer, parcel, flags, irCastIfNeeded(value, parcelType))
}
// Wraps a non-null aware parceler to handle nullable types.
class IrNullAwareParcelSerializer(private val serializer: IrParcelSerializer) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
val nonNullResult = readParcelWith(serializer, parcel)
return irIfThenElse(
nonNullResult.type.makeNullable(),
irEquals(parcelReadInt(irGet(parcel)), irInt(0)),
irNull(),
nonNullResult
)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
irLetS(value) { irValueSymbol ->
irIfNull(
context.irBuiltIns.unitType,
irGet(irValueSymbol.owner),
parcelWriteInt(irGet(parcel), irInt(0)),
irBlock {
+parcelWriteInt(irGet(parcel), irInt(1))
+writeParcelWith(serializer, parcel, flags, irGet(irValueSymbol.owner))
}
)
}
}
// Parcel serializer for object classes. We avoid empty parcels by writing a dummy value. Not null-safe.
class IrObjectParcelSerializer(val objectClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
// Avoid empty parcels
irBlock {
+parcelReadInt(irGet(parcel))
+irGetObject(objectClass.symbol)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteInt(irGet(parcel), irInt(1))
}
// Parcel serializer for classes with a default constructor. We avoid empty parcels by writing a dummy value. Not null-safe.
class IrNoParameterClassParcelSerializer(val irClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
val defaultConstructor = irClass.primaryConstructor!!
return irBlock {
+parcelReadInt(irGet(parcel))
+irCall(defaultConstructor)
}
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteInt(irGet(parcel), irInt(1))
}
// Parcel serializer for enum classes. Not null-safe.
class IrEnumParcelSerializer(val enumClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irCall(enumValueOf).apply {
putValueArgument(0, parcelReadString(irGet(parcel)))
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteString(irGet(parcel), irCall(enumName).apply {
dispatchReceiver = value
})
private val enumValueOf: IrFunctionSymbol =
enumClass.functions.single { function ->
function.name.asString() == "valueOf" && function.dispatchReceiverParameter == null
&& function.extensionReceiverParameter == null && function.valueParameters.size == 1
&& function.valueParameters.single().type.isString()
}.symbol
private val enumName: IrFunctionSymbol =
enumClass.getPropertyGetter("name")!!
}
// Parcel serializer for the java CharSequence interface.
class IrCharSequenceParcelSerializer : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
parcelableCreatorCreateFromParcel(getTextUtilsCharSequenceCreator(), irGet(parcel))
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
textUtilsWriteToParcel(value, irGet(parcel), irGet(flags))
}
// Parcel serializer for Parcelables in the same module, which accesses the writeToParcel/createFromParcel methods without reflection.
class IrEfficientParcelableParcelSerializer(private val irClass: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression {
val creator: IrExpression = irClass.fields.find { it.name == CREATOR_NAME }?.let { creatorField ->
irGetField(null, creatorField)
} ?: irCall(irClass.creatorGetter!!).apply {
dispatchReceiver = irGetObject((irClass.companionObject()!! as IrClass).symbol)
}
return parcelableCreatorCreateFromParcel(creator, irGet(parcel))
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelableWriteToParcel(irClass, value, irGet(parcel), irGet(flags))
}
// Parcel serializer for Parcelables using reflection.
// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027.
class IrGenericParcelableParcelSerializer(private val parcelizeType: IrType) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
parcelReadParcelable(irGet(parcel), classGetClassLoader(javaClassReference(parcelizeType)))
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteParcelable(irGet(parcel), value, irGet(flags))
}
// Fallback parcel serializer for unknown types using Parcel.readValue/writeValue.
// This needs a reference to the parcelize type itself in order to find the correct class loader to use, see KT-20027.
class IrGenericValueParcelSerializer(private val parcelizeType: IrType) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
parcelReadValue(irGet(parcel), classGetClassLoader(javaClassReference(parcelizeType)))
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelWriteValue(irGet(parcel), value)
}
// Parcel serializer using a custom Parceler object.
class IrCustomParcelSerializer(private val parcelerObject: IrClass) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
parcelerCreate(parcelerObject, parcel)
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
parcelerWrite(parcelerObject, parcel, flags, value)
}
// Parcel serializer for array types. This handles both primitive array types (for ShortArray and for primitive arrays using custom element
// parcelers) as well as boxed arrays.
// TODO: Unsigned array types
class IrArrayParcelSerializer(
private val arrayType: IrType,
private val elementType: IrType,
private val elementSerializer: IrParcelSerializer
) : IrParcelSerializer {
private fun AndroidIrBuilder.newArray(size: IrExpression): IrExpression {
val arrayConstructor: IrFunctionSymbol = if (arrayType.isBoxedArray)
androidSymbols.irSymbols.arrayOfNulls
else
arrayType.classOrNull!!.constructors.single { it.owner.valueParameters.size == 1 }
return irCall(arrayConstructor, arrayType).apply {
if (typeArgumentsCount != 0)
putTypeArgument(0, elementType)
putValueArgument(0, size)
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irBlock {
val arraySize = irTemporary(parcelReadInt(irGet(parcel)))
val arrayTemporary = irTemporary(newArray(irGet(arraySize)))
forUntil(irGet(arraySize)) { index ->
val setter = arrayType.classOrNull!!.getSimpleFunction("set")!!
+irCall(setter).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
putValueArgument(1, readParcelWith(elementSerializer, parcel))
}
}
+irGet(arrayTemporary)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
irBlock {
val arrayTemporary = irTemporary(value)
val arraySizeSymbol = arrayType.classOrNull!!.getPropertyGetter("size")!!
val arraySize = irTemporary(irCall(arraySizeSymbol).apply {
dispatchReceiver = irGet(arrayTemporary)
})
+parcelWriteInt(irGet(parcel), irGet(arraySize))
forUntil(irGet(arraySize)) { index ->
val getter = context.irBuiltIns.arrayClass.getSimpleFunction("get")!!
val element = irCall(getter).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
}
+writeParcelWith(elementSerializer, parcel, flags, element)
}
}
}
// Parcel serializer for android SparseArrays. Note that this also needs to handle BooleanSparseArray, in case of a custom element parceler.
class IrSparseArrayParcelSerializer(
private val sparseArrayClass: IrClass,
private val elementType: IrType,
private val elementSerializer: IrParcelSerializer
) : IrParcelSerializer {
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irBlock {
val remainingSizeTemporary = irTemporaryVar(parcelReadInt(irGet(parcel)))
val sparseArrayConstructor = sparseArrayClass.constructors.first { irConstructor ->
irConstructor.valueParameters.size == 1 && irConstructor.valueParameters.single().type.isInt()
}
val constructorCall = if (sparseArrayClass.typeParameters.isEmpty())
irCall(sparseArrayConstructor)
else
irCallConstructor(sparseArrayConstructor.symbol, listOf(elementType))
val arrayTemporary = irTemporary(constructorCall.apply {
putValueArgument(0, irGet(remainingSizeTemporary))
})
+irWhile().apply {
condition = irNotEquals(irGet(remainingSizeTemporary), irInt(0))
body = irBlock {
val sparseArrayPut = sparseArrayClass.functions.first { function ->
function.name.asString() == "put" && function.valueParameters.size == 2
}
+irCall(sparseArrayPut).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, parcelReadInt(irGet(parcel)))
putValueArgument(1, readParcelWith(elementSerializer, parcel))
}
val dec = context.irBuiltIns.intClass.getSimpleFunction("dec")!!
+irSetVar(remainingSizeTemporary.symbol, irCall(dec).apply {
dispatchReceiver = irGet(remainingSizeTemporary)
})
}
}
+irGet(arrayTemporary)
}
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression =
irBlock {
val sizeFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "size" && function.valueParameters.isEmpty()
}
val keyAtFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "keyAt" && function.valueParameters.size == 1
}
val valueAtFunction = sparseArrayClass.functions.first { function ->
function.name.asString() == "valueAt" && function.valueParameters.size == 1
}
val arrayTemporary = irTemporary(value)
val sizeTemporary = irTemporary(irCall(sizeFunction).apply {
dispatchReceiver = irGet(arrayTemporary)
})
+parcelWriteInt(irGet(parcel), irGet(sizeTemporary))
forUntil(irGet(sizeTemporary)) { index ->
+parcelWriteInt(irGet(parcel), irCall(keyAtFunction).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
})
+writeParcelWith(elementSerializer, parcel, flags, irCall(valueAtFunction).apply {
dispatchReceiver = irGet(arrayTemporary)
putValueArgument(0, irGet(index))
})
}
}
}
// Parcel serializer for all lists supported by Parcelize. List interfaces use hard-coded default implementations for deserialization.
// List maps to ArrayList, Set maps to LinkedHashSet, NavigableSet and SortedSet map to TreeSet.
class IrListParcelSerializer(private val irClass: IrClass, private val elementSerializer: IrParcelSerializer) : IrParcelSerializer {
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
val sizeFunction = irClass.getPropertyGetter("size")!!
val iteratorFunction = irClass.getMethodWithoutArguments("iterator")
val iteratorClass = iteratorFunction.returnType.erasedUpperBound
val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext")
val iteratorNext = iteratorClass.getMethodWithoutArguments("next")
return irBlock {
val list = irTemporary(value)
+parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply {
dispatchReceiver = irGet(list)
})
val iterator = irTemporary(irCall(iteratorFunction).apply {
dispatchReceiver = irGet(list)
})
+irWhile().apply {
condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) }
body = writeParcelWith(elementSerializer, parcel, flags, irCall(iteratorNext).apply {
dispatchReceiver = irGet(iterator)
})
}
}
}
private fun listSymbols(symbols: AndroidSymbols): Pair<IrConstructorSymbol, IrSimpleFunctionSymbol> {
// If the IrClass refers to a concrete type, try to find a constructor with capactiy or fall back
// the the default constructor if none exist.
if (!irClass.isJvmInterface) {
val constructor = irClass.constructors.find { constructor ->
constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt()
} ?: irClass.constructors.first { constructor -> constructor.valueParameters.isEmpty() }
val add = irClass.functions.first { function ->
function.name.asString() == "add" && function.valueParameters.size == 1
}
return constructor.symbol to add.symbol
}
return when (irClass.fqNameWhenAvailable?.asString()) {
"kotlin.collections.MutableList", "kotlin.collections.List", "java.util.List" ->
symbols.arrayListConstructor to symbols.arrayListAdd
"kotlin.collections.MutableSet", "kotlin.collections.Set", "java.util.Set" ->
symbols.linkedHashSetConstructor to symbols.linkedHashSetAdd
"java.util.NavigableSet", "java.util.SortedSet" ->
symbols.treeSetConstructor to symbols.treeSetAdd
else -> error("Unknown list interface type: ${irClass.render()}")
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irBlock {
val (constructorSymbol, addSymbol) = listSymbols(androidSymbols)
val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel)))
val list = irTemporary(irCall(constructorSymbol).apply {
if (constructorSymbol.owner.valueParameters.isNotEmpty())
putValueArgument(0, irGet(sizeTemporary))
})
forUntil(irGet(sizeTemporary)) {
+irCall(addSymbol).apply {
dispatchReceiver = irGet(list)
putValueArgument(0, readParcelWith(elementSerializer, parcel))
}
}
+irGet(list)
}
}
// Parcel serializer for all maps supported by Parcelize. Map interfaces use hard-coded default implementations for deserialization.
// Map uses LinkedHashMap, while NavigableMap and SortedMap use to TreeMap.
class IrMapParcelSerializer(
private val irClass: IrClass,
private val keySerializer: IrParcelSerializer,
private val valueSerializer: IrParcelSerializer
) : IrParcelSerializer {
override fun AndroidIrBuilder.writeParcel(parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression): IrExpression {
val sizeFunction = irClass.getPropertyGetter("size")!!
val entriesFunction = irClass.getPropertyGetter("entries")!!
val entrySetClass = entriesFunction.owner.returnType.erasedUpperBound
val iteratorFunction = entrySetClass.getMethodWithoutArguments("iterator")
val iteratorClass = iteratorFunction.returnType.erasedUpperBound
val iteratorHasNext = iteratorClass.getMethodWithoutArguments("hasNext")
val iteratorNext = iteratorClass.getMethodWithoutArguments("next")
val elementClass =
(entriesFunction.owner.returnType as IrSimpleType).arguments.single().upperBound(context.irBuiltIns).erasedUpperBound
val elementKey = elementClass.getPropertyGetter("key")!!
val elementValue = elementClass.getPropertyGetter("value")!!
return irBlock {
val list = irTemporary(value)
+parcelWriteInt(irGet(parcel), irCall(sizeFunction).apply {
dispatchReceiver = irGet(list)
})
val iterator = irTemporary(irCall(iteratorFunction).apply {
dispatchReceiver = irCall(entriesFunction).apply {
dispatchReceiver = irGet(list)
}
})
+irWhile().apply {
condition = irCall(iteratorHasNext).apply { dispatchReceiver = irGet(iterator) }
body = irBlock {
val element = irTemporary(irCall(iteratorNext).apply {
dispatchReceiver = irGet(iterator)
})
+writeParcelWith(keySerializer, parcel, flags, irCall(elementKey).apply {
dispatchReceiver = irGet(element)
})
+writeParcelWith(valueSerializer, parcel, flags, irCall(elementValue).apply {
dispatchReceiver = irGet(element)
})
}
}
}
}
private fun mapSymbols(symbols: AndroidSymbols): Pair<IrConstructorSymbol, IrSimpleFunctionSymbol> {
// If the IrClass refers to a concrete type, try to find a constructor with capactiy or fall back
// the the default constructor if none exist.
if (!irClass.isJvmInterface) {
val constructor = irClass.constructors.find { constructor ->
constructor.valueParameters.size == 1 && constructor.valueParameters.single().type.isInt()
} ?: irClass.constructors.find { constructor ->
constructor.valueParameters.isEmpty()
}!!
val put = irClass.functions.first { function ->
function.name.asString() == "put" && function.valueParameters.size == 2
}
return constructor.symbol to put.symbol
}
return when (irClass.fqNameWhenAvailable?.asString()) {
"kotlin.collections.MutableMap", "kotlin.collections.Map", "java.util.Map" ->
symbols.linkedHashMapConstructor to symbols.linkedHashMapPut
"java.util.SortedMap", "java.util.NavigableMap" ->
symbols.treeMapConstructor to symbols.treeMapPut
else -> error("Unknown map interface type: ${irClass.render()}")
}
}
override fun AndroidIrBuilder.readParcel(parcel: IrValueDeclaration): IrExpression =
irBlock {
val (constructorSymbol, putSymbol) = mapSymbols(androidSymbols)
val sizeTemporary = irTemporary(parcelReadInt(irGet(parcel)))
val map = irTemporary(irCall(constructorSymbol).apply {
if (constructorSymbol.owner.valueParameters.isNotEmpty())
putValueArgument(0, irGet(sizeTemporary))
})
forUntil(irGet(sizeTemporary)) {
+irCall(putSymbol).apply {
dispatchReceiver = irGet(map)
putValueArgument(0, readParcelWith(keySerializer, parcel))
putValueArgument(1, readParcelWith(valueSerializer, parcel))
}
}
+irGet(map)
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel.ir
import org.jetbrains.kotlin.android.parcel.ParcelableAnnotationChecker
import org.jetbrains.kotlin.ir.declarations.IrAnnotationContainer
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.types.IrSimpleType
import org.jetbrains.kotlin.ir.types.IrType
import org.jetbrains.kotlin.ir.types.getClass
import org.jetbrains.kotlin.ir.types.typeOrNull
import org.jetbrains.kotlin.ir.util.constructedClass
import org.jetbrains.kotlin.ir.util.fqNameWhenAvailable
import org.jetbrains.kotlin.ir.util.getAnnotation
// Keep track of all custom parcelers which are currently in scope.
// Note that custom parcelers are resolved in *reverse* lexical order.
class IrParcelerScope(val parent: IrParcelerScope? = null) {
private val typeParcelers = mutableMapOf<IrType, IrClass>()
fun add(type: IrType, parceler: IrClass) {
typeParcelers.putIfAbsent(type, parceler)
}
fun get(type: IrType): IrClass? =
parent?.get(type) ?: typeParcelers[type]
}
fun IrParcelerScope?.getCustomSerializer(irType: IrType): IrClass? =
irType.getAnnotation(ParcelableAnnotationChecker.WRITE_WITH_FQNAME)?.let { writeWith ->
(writeWith.type as IrSimpleType).arguments.single().typeOrNull!!.getClass()!!
} ?: this?.get(irType)
fun IrParcelerScope?.hasCustomSerializer(irType: IrType): Boolean =
getCustomSerializer(irType) != null
fun IrAnnotationContainer.getParcelerScope(parent: IrParcelerScope? = null): IrParcelerScope? {
val typeParcelerAnnotations = annotations.filterTo(mutableListOf()) {
it.symbol.owner.constructedClass.fqNameWhenAvailable == ParcelableAnnotationChecker.TYPE_PARCELER_FQNAME
}
if (typeParcelerAnnotations.isEmpty())
return parent
val scope = IrParcelerScope(parent)
for (annotation in typeParcelerAnnotations) {
val (mappedType, parcelerType) = (annotation.type as IrSimpleType).arguments.map { it.typeOrNull!! }
scope.add(mappedType, parcelerType.getClass()!!)
}
return scope
}
@@ -0,0 +1,241 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel.ir
import org.jetbrains.kotlin.android.parcel.ANDROID_PARCELABLE_CLASS_FQNAME
import org.jetbrains.kotlin.android.parcel.PARCELER_FQNAME
import org.jetbrains.kotlin.android.parcel.serializers.ParcelableExtensionBase
import org.jetbrains.kotlin.backend.common.CommonBackendContext
import org.jetbrains.kotlin.backend.common.ir.createImplicitParameterDeclarationWithWrappedDescriptor
import org.jetbrains.kotlin.backend.common.lower.createIrBuilder
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.Visibilities
import org.jetbrains.kotlin.ir.IrElement
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.builders.declarations.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrField
import org.jetbrains.kotlin.ir.declarations.IrModuleFragment
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.ir.visitors.IrElementVisitorVoid
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
class ParcelableIrTransformer(private val context: CommonBackendContext, private val androidSymbols: AndroidSymbols) :
ParcelableExtensionBase, IrElementVisitorVoid {
private val serializerFactory = IrParcelSerializerFactory(androidSymbols)
private val deferredOperations = mutableListOf<() -> Unit>()
private fun defer(block: () -> Unit) = deferredOperations.add(block)
fun transform(moduleFragment: IrModuleFragment) {
moduleFragment.accept(this, null)
deferredOperations.forEach { it() }
}
override fun visitElement(element: IrElement) = element.acceptChildren(this, null)
override fun visitClass(declaration: IrClass) {
declaration.acceptChildren(this, null)
if (!declaration.isParcelize)
return
val parcelableProperties = declaration.parcelableProperties
// If the companion extends Parceler, it can override parts of the generated implementation.
val parcelerObject = declaration.companionObject()?.safeAs<IrClass>()?.takeIf {
it.isSubclassOfFqName(PARCELER_FQNAME.asString())
}
if (declaration.descriptor.hasSyntheticDescribeContents()) {
declaration.addOverride(
ANDROID_PARCELABLE_CLASS_FQNAME,
"describeContents",
context.irBuiltIns.intType,
modality = Modality.OPEN
).apply {
val flags = if (parcelableProperties.any { it.field.type.containsFileDescriptors }) 1 else 0
body = context.createIrBuilder(symbol).run {
irExprBody(irInt(flags))
}
}
}
if (declaration.descriptor.hasSyntheticWriteToParcel()) {
declaration.addOverride(
ANDROID_PARCELABLE_CLASS_FQNAME,
"writeToParcel",
context.irBuiltIns.unitType,
modality = Modality.OPEN
).apply {
val receiverParameter = dispatchReceiverParameter!!
val parcelParameter = addValueParameter("out", androidSymbols.androidOsParcel.defaultType)
val flagsParameter = addValueParameter("flags", context.irBuiltIns.intType)
// We need to defer the construction of the writer, since it may refer to the [writeToParcel] methods in other
// @Parcelize classes in the current module, which might not be constructed yet at this point.
defer {
body = androidSymbols.createBuilder(symbol).run {
irBlockBody {
when {
parcelerObject != null ->
+parcelerWrite(parcelerObject, parcelParameter, flagsParameter, irGet(receiverParameter))
parcelableProperties.isNotEmpty() ->
for (property in parcelableProperties) {
+writeParcelWith(
property.parceler,
parcelParameter,
flagsParameter,
irGetField(irGet(receiverParameter), property.field)
)
}
else ->
+writeParcelWith(
declaration.classParceler,
parcelParameter,
flagsParameter,
irGet(receiverParameter)
)
}
}
}
}
}
}
val creatorType = androidSymbols.androidOsParcelableCreator.typeWith(declaration.defaultType)
if (!declaration.descriptor.hasCreatorField()) {
declaration.addField {
name = ParcelableExtensionBase.CREATOR_NAME
type = creatorType
isStatic = true
isFinal = true
}.apply {
val irField = this
val creatorClass = buildClass {
name = Name.identifier("Creator")
visibility = Visibilities.LOCAL
}.apply {
parent = irField
superTypes = listOf(creatorType)
createImplicitParameterDeclarationWithWrappedDescriptor()
addConstructor {
isPrimary = true
}.apply {
body = context.createIrBuilder(symbol).irBlockBody {
+irDelegatingConstructorCall(context.irBuiltIns.anyClass.owner.constructors.single())
}
}
val arrayType = context.irBuiltIns.arrayClass.typeWith(declaration.defaultType.makeNullable())
addFunction("newArray", arrayType).apply {
overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!)
val sizeParameter = addValueParameter("size", context.irBuiltIns.intType)
body = context.createIrBuilder(symbol).run {
irExprBody(
parcelerNewArray(parcelerObject, sizeParameter)
?: irCall(androidSymbols.irSymbols.arrayOfNulls, arrayType).apply {
putTypeArgument(0, arrayType)
putValueArgument(0, irGet(sizeParameter))
}
)
}
}
addFunction("createFromParcel", declaration.defaultType).apply {
overriddenSymbols = listOf(androidSymbols.androidOsParcelableCreator.getSimpleFunction(name.asString())!!)
val parcelParameter = addValueParameter("parcel", androidSymbols.androidOsParcel.defaultType)
// We need to defer the construction of the create method, since it may refer to the [Parcelable.Creator]
// instances in other @Parcelize classes in the current module, which may not exist yet.
defer {
body = androidSymbols.createBuilder(symbol).run {
irExprBody(
when {
parcelerObject != null ->
parcelerCreate(parcelerObject, parcelParameter)
parcelableProperties.isNotEmpty() ->
irCall(declaration.primaryConstructor!!).apply {
for ((index, property) in parcelableProperties.withIndex()) {
putValueArgument(index, readParcelWith(property.parceler, parcelParameter))
}
}
else ->
readParcelWith(declaration.classParceler, parcelParameter)
}
)
}
}
}
}
initializer = context.createIrBuilder(symbol).run {
irExprBody(irBlock {
+creatorClass
+irCall(creatorClass.primaryConstructor!!)
})
}
}
}
}
private fun IrClass.addOverride(
baseFqName: FqName,
name: String,
returnType: IrType,
modality: Modality = Modality.FINAL
): IrSimpleFunction = addFunction(name, returnType, modality).apply {
overriddenSymbols = superTypes.mapNotNull { superType ->
superType.classOrNull?.owner?.takeIf { superClass -> superClass.isSubclassOfFqName(baseFqName.asString()) }
}.flatMap { superClass ->
superClass.functions.filter { function ->
function.name.asString() == name && function.overridesFunctionIn(baseFqName)
}.map { it.symbol }.toList()
}
}
private data class ParcelableProperty(val field: IrField, val parceler: IrParcelSerializer)
private val IrClass.classParceler: IrParcelSerializer
get() = if (kind == ClassKind.CLASS) {
IrNoParameterClassParcelSerializer(this)
} else {
serializerFactory.get(defaultType, parcelizeType = defaultType, strict = true, toplevel = true, scope = getParcelerScope())
}
private val IrClass.parcelableProperties: List<ParcelableProperty>
get() {
if (kind != ClassKind.CLASS) return emptyList()
val constructor = primaryConstructor ?: return emptyList()
val toplevelScope = getParcelerScope()
return constructor.valueParameters.map { parameter ->
val property = properties.first { it.name == parameter.name }
val localScope = property.getParcelerScope(toplevelScope)
val parceler = serializerFactory.get(parameter.type, parcelizeType = defaultType, strict = true, scope = localScope)
ParcelableProperty(property.backingField!!, parceler)
}
}
// *Heuristic* to determine if a Parcelable contains file descriptors.
private val IrType.containsFileDescriptors: Boolean
get() = erasedUpperBound.fqNameWhenAvailable == ParcelableExtensionBase.FILE_DESCRIPTOR_FQNAME ||
(this as? IrSimpleType)?.arguments?.any { argument ->
argument.typeOrNull?.containsFileDescriptors == true
} == true
}
@@ -0,0 +1,167 @@
/*
* Copyright 2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.android.parcel.ir
import org.jetbrains.kotlin.android.parcel.ANDROID_PARCELABLE_CLASS_FQNAME
import org.jetbrains.kotlin.android.parcel.ANDROID_PARCELABLE_CREATOR_CLASS_FQNAME
import org.jetbrains.kotlin.android.parcel.PARCELER_FQNAME
import org.jetbrains.kotlin.android.parcel.PARCELIZE_CLASS_FQNAME
import org.jetbrains.kotlin.android.parcel.serializers.ParcelableExtensionBase
import org.jetbrains.kotlin.android.parcel.serializers.ParcelableExtensionBase.Companion.CREATOR_NAME
import org.jetbrains.kotlin.backend.common.lower.allOverridden
import org.jetbrains.kotlin.backend.jvm.ir.erasedUpperBound
import org.jetbrains.kotlin.ir.builders.*
import org.jetbrains.kotlin.ir.declarations.IrClass
import org.jetbrains.kotlin.ir.declarations.IrProperty
import org.jetbrains.kotlin.ir.declarations.IrSimpleFunction
import org.jetbrains.kotlin.ir.declarations.IrValueDeclaration
import org.jetbrains.kotlin.ir.descriptors.IrBuiltIns
import org.jetbrains.kotlin.ir.expressions.IrExpression
import org.jetbrains.kotlin.ir.expressions.impl.IrClassReferenceImpl
import org.jetbrains.kotlin.ir.symbols.IrSimpleFunctionSymbol
import org.jetbrains.kotlin.ir.types.*
import org.jetbrains.kotlin.ir.util.*
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.types.Variance
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
// true if the class should be processed by the parcelize plugin
val IrClass.isParcelize: Boolean
get() = kind in ParcelableExtensionBase.ALLOWED_CLASS_KINDS && hasAnnotation(PARCELIZE_CLASS_FQNAME)
// Finds the getter for a pre-existing CREATOR field on the class companion, which is used for manual Parcelable implementations in Kotlin.
val IrClass.creatorGetter: IrSimpleFunctionSymbol?
get() = companionObject()?.safeAs<IrClass>()?.getPropertyGetter(CREATOR_NAME.asString())?.takeIf {
it.owner.correspondingPropertySymbol?.owner?.backingField?.hasAnnotation(FqName("kotlin.jvm.JvmField")) == true
}
// true if the class has a static CREATOR field
val IrClass.hasCreatorField: Boolean
get() = fields.any { field -> field.name == CREATOR_NAME } || creatorGetter != null
// object P : Parceler<T> { fun T.write(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelerWrite(parceler: IrClass, parcel: IrValueDeclaration, flags: IrValueDeclaration, value: IrExpression) =
irCall(parceler.parcelerSymbolByName("write")!!).apply {
dispatchReceiver = irGetObject(parceler.symbol)
extensionReceiver = value
putValueArgument(0, irGet(parcel))
putValueArgument(1, irGet(flags))
}
// object P : Parceler<T> { fun create(parcel: Parcel): T }
fun IrBuilderWithScope.parcelerCreate(parceler: IrClass, parcel: IrValueDeclaration): IrExpression =
irCall(parceler.parcelerSymbolByName("create")!!).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(parcel))
}
// object P: Parceler<T> { fun newArray(size: Int): Array<T> }
fun IrBuilderWithScope.parcelerNewArray(parceler: IrClass?, size: IrValueDeclaration): IrExpression? =
parceler?.parcelerSymbolByName("newArray")?.let { newArraySymbol ->
irCall(newArraySymbol).apply {
dispatchReceiver = irGetObject(parceler.symbol)
putValueArgument(0, irGet(size))
}
}
// class Parcelable { fun writeToParcel(parcel: Parcel, flags: Int) ...}
fun IrBuilderWithScope.parcelableWriteToParcel(
parcelableClass: IrClass,
parcelable: IrExpression,
parcel: IrExpression,
flags: IrExpression
): IrExpression {
val writeToParcel = parcelableClass.functions.first { function ->
function.name.asString() == "writeToParcel" && function.overridesFunctionIn(ANDROID_PARCELABLE_CLASS_FQNAME)
}
return irCall(writeToParcel).apply {
dispatchReceiver = parcelable
putValueArgument(0, parcel)
putValueArgument(1, flags)
}
}
// class C : Parcelable.Creator<T> { fun createFromParcel(parcel: Parcel): T ...}
fun IrBuilderWithScope.parcelableCreatorCreateFromParcel(creator: IrExpression, parcel: IrExpression): IrExpression {
val createFromParcel = creator.type.getClass()!!.functions.first { function ->
function.name.asString() == "createFromParcel" && function.overridesFunctionIn(ANDROID_PARCELABLE_CREATOR_CLASS_FQNAME)
}
return irCall(createFromParcel).apply {
dispatchReceiver = creator
putValueArgument(0, parcel)
}
}
// Find a named function declaration which overrides the corresponding function in [Parceler].
// This is more reliable than trying to match the functions signature ourselves, since the frontend
// has already done the work.
private fun IrClass.parcelerSymbolByName(name: String): IrSimpleFunctionSymbol? =
functions.firstOrNull { function ->
!function.isFakeOverride && function.name.asString() == name && function.overridesFunctionIn(PARCELER_FQNAME)
}?.symbol
fun IrSimpleFunction.overridesFunctionIn(fqName: FqName): Boolean =
parentClassOrNull?.fqNameWhenAvailable == fqName || allOverridden().any { it.parentClassOrNull?.fqNameWhenAvailable == fqName }
private fun IrBuilderWithScope.kClassReference(classType: IrType) =
IrClassReferenceImpl(
startOffset, endOffset, context.irBuiltIns.kClassClass.starProjectedType, context.irBuiltIns.kClassClass, classType
)
private fun AndroidIrBuilder.kClassToJavaClass(kClassReference: IrExpression) =
irGet(androidSymbols.javaLangClass.starProjectedType, null, androidSymbols.kotlinKClassJava.owner.getter!!.symbol).apply {
extensionReceiver = kClassReference
}
// Produce a static reference to the java class of the given type.
fun AndroidIrBuilder.javaClassReference(classType: IrType) =
kClassToJavaClass(kClassReference(classType))
fun IrClass.isSubclassOfFqName(fqName: String): Boolean =
fqNameWhenAvailable?.asString() == fqName || superTypes.any { it.erasedUpperBound.isSubclassOfFqName(fqName) }
inline fun IrBlockBuilder.forUntil(upperBound: IrExpression, loopBody: IrBlockBuilder.(IrValueDeclaration) -> Unit) {
val indexTemporary = irTemporaryVar(irInt(0))
+irWhile().apply {
condition = irNotEquals(irGet(indexTemporary), upperBound)
body = irBlock {
loopBody(indexTemporary)
val inc = context.irBuiltIns.intClass.getSimpleFunction("inc")!!
+irSetVar(indexTemporary.symbol, irCall(inc).apply {
dispatchReceiver = irGet(indexTemporary)
})
}
}
}
fun IrTypeArgument.upperBound(builtIns: IrBuiltIns): IrType =
when (this) {
is IrStarProjection -> builtIns.anyNType
is IrTypeProjection -> {
if (variance == Variance.OUT_VARIANCE || variance == Variance.INVARIANT)
type
else
builtIns.anyNType
}
else -> error("Unknown type argument: ${render()}")
}
private fun IrClass.getSimpleFunction(name: String): IrSimpleFunctionSymbol? =
findDeclaration<IrSimpleFunction> { it.name.asString() == name }?.symbol
// This is a version of getPropertyGetter which does not throw when applied to broken lazy classes, such as java.util.HashMap,
// which contains two "size" properties with different visibilities.
fun IrClass.getPropertyGetter(name: String): IrSimpleFunctionSymbol? =
declarations.filterIsInstance<IrProperty>().firstOrNull { it.name.asString() == name && it.getter != null }?.getter?.symbol
?: getSimpleFunction("<get-$name>")
fun IrClass.getMethodWithoutArguments(name: String): IrSimpleFunction =
functions.first { function ->
function.name.asString() == name && function.dispatchReceiverParameter != null
&& function.extensionReceiverParameter == null && function.valueParameters.isEmpty()
}
@@ -42,7 +42,7 @@ import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.*
import java.util.concurrent.ConcurrentHashMap
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
internal typealias TypeParcelerMapping = Pair<KotlinType, KotlinType>
@@ -5,6 +5,7 @@
package org.jetbrains.kotlin.android.parcel.serializers
import kotlinx.android.parcel.TypeParceler
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent
import org.jetbrains.kotlin.android.parcel.isParcelize
import org.jetbrains.kotlin.descriptors.ClassDescriptor
@@ -13,13 +14,17 @@ import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.android.parcel.ParcelableSyntheticComponent.ComponentKind.*
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.name.FqName
import java.io.FileDescriptor
interface ParcelableExtensionBase {
companion object {
private val CREATOR_NAME = Name.identifier("CREATOR")
val FILE_DESCRIPTOR_FQNAME = FqName(FileDescriptor::class.java.canonicalName)
private val ALLOWED_CLASS_KINDS = listOf(ClassKind.CLASS, ClassKind.OBJECT, ClassKind.ENUM_CLASS)
val CREATOR_NAME = Name.identifier("CREATOR")
val ALLOWED_CLASS_KINDS = listOf(ClassKind.CLASS, ClassKind.OBJECT, ClassKind.ENUM_CLASS)
}
fun ClassDescriptor.hasCreatorField(): Boolean {
@@ -19,10 +19,7 @@ package org.jetbrains.kotlin.android.synthetic
import com.intellij.mock.MockProject
import com.intellij.openapi.project.Project
import kotlinx.android.extensions.CacheImplementation
import org.jetbrains.kotlin.android.parcel.ParcelableAnnotationChecker
import org.jetbrains.kotlin.android.parcel.ParcelableCodegenExtension
import org.jetbrains.kotlin.android.parcel.ParcelableDeclarationChecker
import org.jetbrains.kotlin.android.parcel.ParcelableResolveExtension
import org.jetbrains.kotlin.android.parcel.*
import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidExtensionsExpressionCodegenExtension
import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidIrExtension
import org.jetbrains.kotlin.android.synthetic.codegen.CliAndroidOnDestroyClassBuilderInterceptorExtension
@@ -33,6 +30,7 @@ import org.jetbrains.kotlin.android.synthetic.res.AndroidVariant
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidLayoutXmlFileManager
import org.jetbrains.kotlin.android.synthetic.res.CliAndroidPackageFragmentProviderExtension
import org.jetbrains.kotlin.backend.common.extensions.IrGenerationExtension
import org.jetbrains.kotlin.backend.common.extensions.PureIrGenerationExtension
import org.jetbrains.kotlin.codegen.extensions.ClassBuilderInterceptorExtension
import org.jetbrains.kotlin.codegen.extensions.ExpressionCodegenExtension
import org.jetbrains.kotlin.compiler.plugin.*
@@ -107,6 +105,7 @@ class AndroidComponentRegistrar : ComponentRegistrar {
companion object {
fun registerParcelExtensions(project: Project) {
ExpressionCodegenExtension.registerExtension(project, ParcelableCodegenExtension())
PureIrGenerationExtension.registerExtension(project, ParcelableIrGeneratorExtension())
SyntheticResolveExtension.registerExtension(project, ParcelableResolveExtension())
ClassBuilderInterceptorExtension.registerExtension(project, ParcelableClinitClassBuilderInterceptorExtension())
}