Parcelize: Fix infinite recursive loop for zero-parameter Parcelable classes (KT-25839)

This commit is contained in:
Yan Zhulanow
2018-06-04 17:40:55 +03:00
parent 475a80b641
commit 8c05769745
7 changed files with 157 additions and 11 deletions
@@ -168,7 +168,12 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
if (properties.isEmpty()) {
val entityType = this@writeWriteToParcel.defaultType
val asmType = codegen.state.typeMapper.mapType(entityType)
val serializer = ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
val serializer = if (this@writeWriteToParcel.kind == ClassKind.CLASS) {
NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType))
} else {
ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
}
v.load(1, parcelAsmType)
v.load(0, containerAsmType)
serializer.writeValue(v)
@@ -220,6 +225,10 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
codegen: ImplementationBodyCodegen,
parcelableClass: ClassDescriptor
): List<PropertyToSerialize> {
if (parcelableClass.kind != ClassKind.CLASS) {
return emptyList()
}
val constructor = parcelableClass.constructors.firstOrNull { it.isPrimary } ?: return emptyList()
val propertiesToSerialize = constructor.valueParameters.mapNotNull { param ->
@@ -271,7 +280,11 @@ open class ParcelableCodegenExtension : ExpressionCodegenExtension {
if (properties.isEmpty()) {
val entityType = parcelableClass.defaultType
val asmType = codegen.state.typeMapper.mapType(entityType)
val serializer = ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
val serializer = if (parcelableClass.kind == ClassKind.CLASS) {
NullAwareParcelSerializerWrapper(ZeroParameterClassSerializer(asmType, entityType))
} else {
ParcelSerializer.get(entityType, asmType, globalContext, strict = true)
}
v.load(1, parcelAsmType)
serializer.readValue(v)
} else {
@@ -23,6 +23,7 @@ import org.jetbrains.kotlin.codegen.FrameMap
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.codegen.useTmpVar
import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.org.objectweb.asm.Label
import org.jetbrains.org.objectweb.asm.Type
@@ -56,7 +57,7 @@ internal class TypeParcelerParcelSerializer(
boxTypeIfNeeded(v) // -> parcel, (boxed)value
v.swap() // -> value, parcel
putObjectInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> value, parcel, parceler
putObjectOrClassInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> value, parcel, parceler
v.dupX2() // -> parceler, value, parcel, parceler
v.pop() // -> parceler, value, parcel
v.load(2, Type.INT_TYPE) // -> parceler, value, parcel, flags
@@ -65,7 +66,7 @@ internal class TypeParcelerParcelSerializer(
override fun readValue(v: InstructionAdapter) {
// -> parcel
putObjectInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> parcel, parceler
putObjectOrClassInstanceOnStack(parcelerType, parcelerAsmType, typeMapper, v) // -> parcel, parceler
v.swap() // -> parceler, parcel
v.invokeinterface(PARCELER_TYPE.internalName, "create", "(Landroid/os/Parcel;)Ljava/lang/Object;") // -> obj
unboxTypeIfNeeded(v)
@@ -475,17 +476,45 @@ internal class ObjectParcelSerializer(
override fun readValue(v: InstructionAdapter) {
v.pop()
putObjectInstanceOnStack(type, asmType, typeMapper, v)
putObjectOrClassInstanceOnStack(type, asmType, typeMapper, v)
}
}
private fun putObjectInstanceOnStack(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, v: InstructionAdapter) {
internal class ZeroParameterClassSerializer(
override val asmType: Type,
type: KotlinType
) : ParcelSerializer {
val clazz = type.constructor.declarationDescriptor as ClassDescriptor
init {
assert(clazz.kind == ClassKind.CLASS)
}
override fun writeValue(v: InstructionAdapter) {
v.pop2()
}
override fun readValue(v: InstructionAdapter) {
v.pop()
val constructor = clazz.unsubstitutedPrimaryConstructor
assert(constructor == null || constructor.valueParameters.isEmpty())
v.anew(asmType)
v.dup()
v.invokespecial(asmType.internalName, "<init>", "()V", false)
}
}
private fun putObjectOrClassInstanceOnStack(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, v: InstructionAdapter) {
val clazz = type.constructor.declarationDescriptor as? ClassDescriptor
if (clazz != null && clazz.isCompanionObject) {
val outerClass = clazz.containingDeclaration as? ClassDescriptor
if (outerClass != null) {
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
return
if (clazz != null) {
if (clazz.isCompanionObject) {
val outerClass = clazz.containingDeclaration as? ClassDescriptor
if (outerClass != null) {
v.getstatic(typeMapper.mapType(outerClass.defaultType).internalName, clazz.name.asString(), asmType.descriptor)
return
}
}
}
@@ -60,6 +60,7 @@ class ParcelBoxTest : AbstractParcelBoxTest() {
fun testEnumObject() = doTest("enumObject")
fun testIntArray() = doTest("intArray")
fun testOpenParcelize() = doTest("openParcelize")
fun testKt25839() = doTest("kt25839")
}
class ParcelBoxTestWithSerializableLikeExtension : AbstractParcelBoxTest() {
@@ -69,6 +69,11 @@ public class ParcelBytecodeListingTestGenerated extends AbstractParcelBytecodeLi
runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/IBinderIInterface.kt");
}
@TestMetadata("kt25839.kt")
public void testKt25839() throws Exception {
runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/kt25839.kt");
}
@TestMetadata("listInsideList.kt")
public void testListInsideList() throws Exception {
runTest("plugins/android-extensions/android-extensions-compiler/testData/parcel/codegen/listInsideList.kt");
@@ -0,0 +1,28 @@
// WITH_RUNTIME
@file:JvmName("TestKt")
package test
import kotlinx.android.parcel.*
import android.os.Parcel
import android.os.Parcelable
@Parcelize
class User : Parcelable
@Parcelize
class User2() : Parcelable
fun box() = parcelTest { parcel ->
val user = User()
val user2 = User2()
user.writeToParcel(parcel, 0)
user2.writeToParcel(parcel, 0)
val bytes = parcel.marshall()
parcel.unmarshall(bytes, 0, bytes.size)
readFromParcel<User>(parcel)
readFromParcel<User2>(parcel)
}
@@ -0,0 +1,10 @@
// CURIOUS_ABOUT writeToParcel, createFromParcel
// WITH_RUNTIME
package test
import kotlinx.android.parcel.*
import android.os.Parcelable
@Parcelize
class SomeClass : Parcelable
@@ -0,0 +1,60 @@
public final class test/SomeClass$Creator : java/lang/Object, android/os/Parcelable$Creator {
public void <init>()
public final java.lang.Object createFromParcel(android.os.Parcel p0) {
LABEL (L0)
ALOAD (1)
LDC (in)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
LABEL (L1)
NEW
DUP
ALOAD (1)
INVOKEVIRTUAL (android/os/Parcel, readInt, ()I)
IFEQ (L2)
NEW
DUP
INVOKESPECIAL (test/SomeClass, <init>, ()V)
GOTO (L3)
LABEL (L2)
ACONST_NULL
LABEL (L3)
ARETURN
LABEL (L4)
}
public final java.lang.Object[] newArray(int p0)
}
public final class test/SomeClass : java/lang/Object, android/os/Parcelable {
public final static android.os.Parcelable$Creator CREATOR
static void <clinit>()
public void <init>()
public int describeContents()
public void writeToParcel(android.os.Parcel p0, int p1) {
LABEL (L0)
ALOAD (1)
LDC (parcel)
INVOKESTATIC (kotlin/jvm/internal/Intrinsics, checkParameterIsNotNull, (Ljava/lang/Object;Ljava/lang/String;)V)
ALOAD (1)
ALOAD (0)
DUP
IFNULL (L1)
ALOAD (1)
LDC (1)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
POP2
GOTO (L2)
LABEL (L1)
POP
LDC (0)
INVOKEVIRTUAL (android/os/Parcel, writeInt, (I)V)
LABEL (L2)
RETURN
LABEL (L3)
}
}