diff --git a/plugins/android-extensions/android-extensions-compiler/build.gradle.kts b/plugins/android-extensions/android-extensions-compiler/build.gradle.kts index 7151d138796..74e9da7e26b 100644 --- a/plugins/android-extensions/android-extensions-compiler/build.gradle.kts +++ b/plugins/android-extensions/android-extensions-compiler/build.gradle.kts @@ -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")) diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt index d72df10b1a5..f8a38d4a334 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt @@ -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"), diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableIrGeneratorExtension.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableIrGeneratorExtension.kt new file mode 100644 index 00000000000..5f8adfd8f32 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableIrGeneratorExtension.kt @@ -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) + } +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidIrBuilder.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidIrBuilder.kt new file mode 100644 index 00000000000..1385e284dfd --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidIrBuilder.kt @@ -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) +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidSymbols.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidSymbols.kt new file mode 100644 index 00000000000..2c640638bc6 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/AndroidSymbols.kt @@ -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 = 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) +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt new file mode 100644 index 00000000000..ae2ecc609fc --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializerFactory.kt @@ -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) +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt new file mode 100644 index 00000000000..57541e3af60 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelSerializers.kt @@ -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 { + // 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 { + // 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) + } +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelerScope.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelerScope.kt new file mode 100644 index 00000000000..78277e2ab90 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/IrParcelerScope.kt @@ -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() + + 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 +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/ParcelableIrTransformer.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/ParcelableIrTransformer.kt new file mode 100644 index 00000000000..b0a428d32cc --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/ParcelableIrTransformer.kt @@ -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()?.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 + 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 +} diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt new file mode 100644 index 00000000000..c78e6ddfc60 --- /dev/null +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ir/irUtils.kt @@ -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()?.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 { 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 { 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 { fun newArray(size: Int): Array } +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 { 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 { 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().firstOrNull { it.name.asString() == name && it.getter != null }?.getter?.symbol + ?: getSimpleFunction("") + +fun IrClass.getMethodWithoutArguments(name: String): IrSimpleFunction = + functions.first { function -> + function.name.asString() == name && function.dispatchReceiverParameter != null + && function.extensionReceiverParameter == null && function.valueParameters.isEmpty() + } diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt index 8333a546c6d..a16fd1dcbcc 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt @@ -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 diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelableExtensionBase.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelableExtensionBase.kt index 8285ba06fd3..94bb82b7e1f 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelableExtensionBase.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelableExtensionBase.kt @@ -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 { diff --git a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt index bfa050c0863..23e50e263f1 100644 --- a/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt +++ b/plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/AndroidComponentRegistrar.kt @@ -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()) }