Parcelable: Report error on unsupported parameter types, add @RawValue annotation support

This commit is contained in:
Yan Zhulanow
2017-07-03 23:44:13 +03:00
parent 4200629347
commit cf607a0f14
6 changed files with 110 additions and 16 deletions
@@ -188,7 +188,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
val asmType = codegen.typeMapper.mapType(type) val asmType = codegen.typeMapper.mapType(type)
asmConstructorParameters.append(asmType.descriptor) asmConstructorParameters.append(asmType.descriptor)
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper) val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper, forceBoxed = false, strict = false)
v.load(1, parcelAsmType) v.load(1, parcelAsmType)
serializer.readValue(v) serializer.readValue(v)
} }
@@ -16,20 +16,27 @@
package org.jetbrains.kotlin.android.parcel package org.jetbrains.kotlin.android.parcel
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid
import org.jetbrains.kotlin.codegen.ClassBuilderMode
import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
import org.jetbrains.kotlin.descriptors.ClassDescriptor import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
import org.jetbrains.kotlin.descriptors.PropertyDescriptor import org.jetbrains.kotlin.descriptors.PropertyDescriptor
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
import org.jetbrains.kotlin.diagnostics.DiagnosticSink import org.jetbrains.kotlin.diagnostics.DiagnosticSink
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.psi.* import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.resolve.BindingContext import org.jetbrains.kotlin.resolve.BindingContext
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.resolve.descriptorUtil.module
import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmFieldAnnotation import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmFieldAnnotation
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isError
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable") private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel") internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
@@ -46,7 +53,7 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
bindingContext: BindingContext bindingContext: BindingContext
) { ) {
when (descriptor) { when (descriptor) {
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder) is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder, bindingContext)
is SimpleFunctionDescriptor -> { is SimpleFunctionDescriptor -> {
val containingClass = descriptor.containingDeclaration as? ClassDescriptor val containingClass = descriptor.containingDeclaration as? ClassDescriptor
val ktFunction = declaration as? KtFunction val ktFunction = declaration as? KtFunction
@@ -102,7 +109,12 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
} }
} }
private fun checkParcelableClass(descriptor: ClassDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) { private fun checkParcelableClass(
descriptor: ClassDescriptor,
declaration: KtDeclaration,
diagnosticHolder: DiagnosticSink,
bindingContext: BindingContext
) {
if (!descriptor.isMagicParcelable) return if (!descriptor.isMagicParcelable) return
if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) { if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) {
@@ -135,10 +147,39 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(declaration.nameIdentifier ?: declaration)) diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(declaration.nameIdentifier ?: declaration))
} }
val typeMapper = KotlinTypeMapper(
bindingContext,
ClassBuilderMode.full(false),
NoResolveFileClassesProvider,
IncompatibleClassTracker.DoNothing,
descriptor.module.name.asString(),
/* isJvm8Target */ false,
/* isJvm8TargetWithDefaults */ false)
for (parameter in primaryConstructor?.valueParameters.orEmpty()) { for (parameter in primaryConstructor?.valueParameters.orEmpty()) {
if (!parameter.hasValOrVar()) { checkParcelableClassProperty(parameter, diagnosticHolder, typeMapper)
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on( }
parameter.nameIdentifier ?: parameter)) }
private fun checkParcelableClassProperty(parameter: KtParameter, diagnosticHolder: DiagnosticSink, typeMapper: KotlinTypeMapper) {
if (!parameter.hasValOrVar()) {
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
parameter.nameIdentifier ?: parameter))
}
val descriptor = typeMapper.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter] ?: return
val type = descriptor.type
if (!type.isError) {
val asmType = typeMapper.mapType(type)
try {
ParcelSerializer.get(type, asmType, typeMapper, strict = true)
}
catch (e: IllegalArgumentException) {
// get() throws IllegalArgumentException on unknown types
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED.on(
parameter.typeReference ?: parameter.nameIdentifier ?: parameter))
} }
} }
} }
@@ -22,18 +22,22 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
import org.jetbrains.kotlin.descriptors.ClassKind import org.jetbrains.kotlin.descriptors.ClassKind
import org.jetbrains.kotlin.descriptors.Modality import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.incremental.components.NoLookupLocation
import org.jetbrains.kotlin.name.FqName
import org.jetbrains.kotlin.name.Name import org.jetbrains.kotlin.name.Name
import org.jetbrains.kotlin.resolve.DescriptorUtils import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.synthetic.isVisibleOutside import org.jetbrains.kotlin.synthetic.isVisibleOutside
import org.jetbrains.kotlin.types.KotlinType import org.jetbrains.kotlin.types.KotlinType
import org.jetbrains.kotlin.types.TypeUtils import org.jetbrains.kotlin.types.TypeUtils
import org.jetbrains.kotlin.types.isError
import org.jetbrains.kotlin.types.typeUtil.builtIns import org.jetbrains.kotlin.types.typeUtil.builtIns
import org.jetbrains.org.objectweb.asm.Type import org.jetbrains.org.objectweb.asm.Type
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
import java.util.* import java.util.*
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
interface ParcelSerializer { interface ParcelSerializer {
val asmType: Type val asmType: Type
@@ -41,16 +45,26 @@ interface ParcelSerializer {
fun readValue(v: InstructionAdapter) fun readValue(v: InstructionAdapter)
companion object { companion object {
fun get(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, forceBoxed: Boolean = false): ParcelSerializer { private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType): Type {
return if (type.isError) Type.getObjectType("java/lang/Object") else mapType(type)
}
fun get(
type: KotlinType,
asmType: Type,
typeMapper: KotlinTypeMapper,
forceBoxed: Boolean = false,
strict: Boolean = false
): ParcelSerializer {
val className = asmType.className val className = asmType.className
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
return when { return when {
asmType.sort == Type.ARRAY -> { asmType.sort == Type.ARRAY -> {
val elementType = type.builtIns.getArrayElementType(type) val elementType = type.builtIns.getArrayElementType(type)
val elementSerializer = get(elementType, typeMapper.mapTypeSafe(elementType), typeMapper, strict = strict())
wrapToNullAwareIfNeeded( wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
type,
ArrayParcelSerializer(asmType, get(elementType, typeMapper.mapType(elementType), typeMapper)))
} }
asmType.isPrimitive() -> { asmType.isPrimitive() -> {
@@ -73,7 +87,8 @@ interface ParcelSerializer {
|| className == TreeSet::class.java.canonicalName || className == TreeSet::class.java.canonicalName
-> { -> {
val elementType = type.arguments.single().type val elementType = type.arguments.single().type
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true) val elementSerializer = get(
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer)) wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer))
} }
@@ -84,8 +99,10 @@ interface ParcelSerializer {
|| className == ConcurrentHashMap::class.java.canonicalName || className == ConcurrentHashMap::class.java.canonicalName
-> { -> {
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) } val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
val keySerializer = get(keyType.type, typeMapper.mapType(keyType.type), typeMapper, forceBoxed = true) val keySerializer = get(
val valueSerializer = get(valueType.type, typeMapper.mapType(valueType.type), typeMapper, forceBoxed = true) keyType.type, typeMapper.mapTypeSafe(keyType.type), typeMapper, forceBoxed = true, strict = strict())
val valueSerializer = get(
valueType.type, typeMapper.mapTypeSafe(valueType.type), typeMapper, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer)) wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
} }
@@ -131,7 +148,8 @@ interface ParcelSerializer {
asmType.isSparseArray() -> { asmType.isSparseArray() -> {
val elementType = type.arguments.single().type val elementType = type.arguments.single().type
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true) val elementSerializer = get(
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer)) wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
} }
@@ -158,7 +176,7 @@ interface ParcelSerializer {
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull() Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
val creatorAsmType = when { val creatorAsmType = when {
creatorVar != null -> typeMapper.mapType(creatorVar.type) creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator") clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
else -> null else -> null
} }
@@ -175,7 +193,12 @@ interface ParcelSerializer {
Method("writeSerializable"), Method("writeSerializable"),
Method("readSerializable")) Method("readSerializable"))
else -> GenericParcelSerializer else -> {
if (strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME))
throw IllegalArgumentException("Illegal type")
else
GenericParcelSerializer
}
} }
} }
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when { private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when {
@@ -69,6 +69,10 @@ class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN, MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN,
"'CREATOR' definition is forbidden. Use 'Parceler' companion object instead.") "'CREATOR' definition is forbidden. Use 'Parceler' companion object instead.")
MAP.put(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED,
"Type is not supported by 'Parcelable'. " +
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'")
} }
} }
@@ -41,6 +41,7 @@ public interface ErrorsAndroid {
DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING); DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR); DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
DiagnosticFactory0<PsiElement> PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
@SuppressWarnings("UnusedDeclaration") @SuppressWarnings("UnusedDeclaration")
Object _initializer = new Object() { Object _initializer = new Object() {
@@ -0,0 +1,25 @@
/*
* Copyright 2010-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package kotlinx.android.parcel
/**
* Write the corresponding property value with [android.os.Parcel.writeValue].
* Serialization may fail at runtime, depending on actual property value type.
*/
@Target(AnnotationTarget.TYPE)
@Retention(AnnotationRetention.BINARY)
annotation class RawValue