Skip to content

Commit 70efc92

Browse files
committed
Parcelable: Report error on unsupported parameter types, add @rawvalue annotation support
1 parent 39cfcbc commit 70efc92

File tree

6 files changed

+110
-16
lines changed

6 files changed

+110
-16
lines changed

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableCodegenExtension.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ class ParcelableCodegenExtension : ExpressionCodegenExtension {
188188
val asmType = codegen.typeMapper.mapType(type)
189189
asmConstructorParameters.append(asmType.descriptor)
190190

191-
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper)
191+
val serializer = ParcelSerializer.get(type, asmType, codegen.typeMapper, forceBoxed = false, strict = false)
192192
v.load(1, parcelAsmType)
193193
serializer.readValue(v)
194194
}

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/ParcelableDeclarationChecker.kt

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,27 @@
1616

1717
package org.jetbrains.kotlin.android.parcel
1818

19+
import org.jetbrains.kotlin.android.parcel.serializers.ParcelSerializer
1920
import org.jetbrains.kotlin.android.synthetic.diagnostic.ErrorsAndroid
21+
import org.jetbrains.kotlin.codegen.ClassBuilderMode
22+
import org.jetbrains.kotlin.codegen.state.IncompatibleClassTracker
23+
import org.jetbrains.kotlin.codegen.state.KotlinTypeMapper
2024
import org.jetbrains.kotlin.descriptors.ClassDescriptor
2125
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
2226
import org.jetbrains.kotlin.descriptors.PropertyDescriptor
2327
import org.jetbrains.kotlin.descriptors.SimpleFunctionDescriptor
2428
import org.jetbrains.kotlin.diagnostics.DiagnosticSink
29+
import org.jetbrains.kotlin.fileClasses.NoResolveFileClassesProvider
2530
import org.jetbrains.kotlin.lexer.KtTokens
2631
import org.jetbrains.kotlin.name.FqName
2732
import org.jetbrains.kotlin.psi.*
2833
import org.jetbrains.kotlin.resolve.BindingContext
2934
import org.jetbrains.kotlin.resolve.checkers.SimpleDeclarationChecker
3035
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
36+
import org.jetbrains.kotlin.resolve.descriptorUtil.module
3137
import org.jetbrains.kotlin.resolve.jvm.annotations.findJvmFieldAnnotation
3238
import org.jetbrains.kotlin.types.TypeUtils
39+
import org.jetbrains.kotlin.types.isError
3340

3441
private val ANDROID_PARCELABLE_CLASS_FQNAME = FqName("android.os.Parcelable")
3542
internal val ANDROID_PARCEL_CLASS_FQNAME = FqName("android.os.Parcel")
@@ -46,7 +53,7 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
4653
bindingContext: BindingContext
4754
) {
4855
when (descriptor) {
49-
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder)
56+
is ClassDescriptor -> checkParcelableClass(descriptor, declaration, diagnosticHolder, bindingContext)
5057
is SimpleFunctionDescriptor -> {
5158
val containingClass = descriptor.containingDeclaration as? ClassDescriptor
5259
val ktFunction = declaration as? KtFunction
@@ -102,7 +109,12 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
102109
}
103110
}
104111

105-
private fun checkParcelableClass(descriptor: ClassDescriptor, declaration: KtDeclaration, diagnosticHolder: DiagnosticSink) {
112+
private fun checkParcelableClass(
113+
descriptor: ClassDescriptor,
114+
declaration: KtDeclaration,
115+
diagnosticHolder: DiagnosticSink,
116+
bindingContext: BindingContext
117+
) {
106118
if (!descriptor.isMagicParcelable) return
107119

108120
if (declaration !is KtClass || (declaration.isAnnotation() || declaration.isInterface() || declaration.isEnum())) {
@@ -135,10 +147,39 @@ class ParcelableDeclarationChecker : SimpleDeclarationChecker {
135147
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_SHOULD_HAVE_PRIMARY_CONSTRUCTOR.on(declaration.nameIdentifier ?: declaration))
136148
}
137149

150+
val typeMapper = KotlinTypeMapper(
151+
bindingContext,
152+
ClassBuilderMode.full(false),
153+
NoResolveFileClassesProvider,
154+
IncompatibleClassTracker.DoNothing,
155+
descriptor.module.name.asString(),
156+
/* isJvm8Target */ false,
157+
/* isJvm8TargetWithDefaults */ false)
158+
138159
for (parameter in primaryConstructor?.valueParameters.orEmpty()) {
139-
if (!parameter.hasValOrVar()) {
140-
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
141-
parameter.nameIdentifier ?: parameter))
160+
checkParcelableClassProperty(parameter, diagnosticHolder, typeMapper)
161+
}
162+
}
163+
164+
private fun checkParcelableClassProperty(parameter: KtParameter, diagnosticHolder: DiagnosticSink, typeMapper: KotlinTypeMapper) {
165+
if (!parameter.hasValOrVar()) {
166+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_CONSTRUCTOR_PARAMETER_SHOULD_BE_VAL_OR_VAR.on(
167+
parameter.nameIdentifier ?: parameter))
168+
}
169+
170+
val descriptor = typeMapper.bindingContext[BindingContext.PRIMARY_CONSTRUCTOR_PARAMETER, parameter] ?: return
171+
val type = descriptor.type
172+
173+
if (!type.isError) {
174+
val asmType = typeMapper.mapType(type)
175+
176+
try {
177+
ParcelSerializer.get(type, asmType, typeMapper, strict = true)
178+
}
179+
catch (e: IllegalArgumentException) {
180+
// get() throws IllegalArgumentException on unknown types
181+
diagnosticHolder.report(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED.on(
182+
parameter.typeReference ?: parameter.nameIdentifier ?: parameter))
142183
}
143184
}
144185
}

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/parcel/serializers/ParcelSerializer.kt

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,35 +22,49 @@ import org.jetbrains.kotlin.descriptors.ClassDescriptor
2222
import org.jetbrains.kotlin.descriptors.ClassKind
2323
import org.jetbrains.kotlin.descriptors.Modality
2424
import org.jetbrains.kotlin.incremental.components.NoLookupLocation
25+
import org.jetbrains.kotlin.name.FqName
2526
import org.jetbrains.kotlin.name.Name
2627
import org.jetbrains.kotlin.resolve.DescriptorUtils
2728
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
2829
import org.jetbrains.kotlin.synthetic.isVisibleOutside
2930
import org.jetbrains.kotlin.types.KotlinType
3031
import org.jetbrains.kotlin.types.TypeUtils
32+
import org.jetbrains.kotlin.types.isError
3133
import org.jetbrains.kotlin.types.typeUtil.builtIns
3234
import org.jetbrains.org.objectweb.asm.Type
3335
import org.jetbrains.org.objectweb.asm.commons.InstructionAdapter
3436
import java.util.*
3537
import java.util.concurrent.ConcurrentHashMap
3638

39+
private val RAWVALUE_ANNOTATION_FQNAME = FqName("kotlinx.android.parcel.RawValue")
40+
3741
interface ParcelSerializer {
3842
val asmType: Type
3943

4044
fun writeValue(v: InstructionAdapter)
4145
fun readValue(v: InstructionAdapter)
4246

4347
companion object {
44-
fun get(type: KotlinType, asmType: Type, typeMapper: KotlinTypeMapper, forceBoxed: Boolean = false): ParcelSerializer {
48+
private fun KotlinTypeMapper.mapTypeSafe(type: KotlinType): Type {
49+
return if (type.isError) Type.getObjectType("java/lang/Object") else mapType(type)
50+
}
51+
52+
fun get(
53+
type: KotlinType,
54+
asmType: Type,
55+
typeMapper: KotlinTypeMapper,
56+
forceBoxed: Boolean = false,
57+
strict: Boolean = false
58+
): ParcelSerializer {
4559
val className = asmType.className
60+
fun strict() = strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME)
4661

4762
return when {
4863
asmType.sort == Type.ARRAY -> {
4964
val elementType = type.builtIns.getArrayElementType(type)
65+
val elementSerializer = get(elementType, typeMapper.mapTypeSafe(elementType), typeMapper, strict = strict())
5066

51-
wrapToNullAwareIfNeeded(
52-
type,
53-
ArrayParcelSerializer(asmType, get(elementType, typeMapper.mapType(elementType), typeMapper)))
67+
wrapToNullAwareIfNeeded(type, ArrayParcelSerializer(asmType, elementSerializer))
5468
}
5569

5670
asmType.isPrimitive() -> {
@@ -73,7 +87,8 @@ interface ParcelSerializer {
7387
|| className == TreeSet::class.java.canonicalName
7488
-> {
7589
val elementType = type.arguments.single().type
76-
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
90+
val elementSerializer = get(
91+
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
7792
wrapToNullAwareIfNeeded(type, ListSetParcelSerializer(asmType, elementSerializer))
7893
}
7994

@@ -84,8 +99,10 @@ interface ParcelSerializer {
8499
|| className == ConcurrentHashMap::class.java.canonicalName
85100
-> {
86101
val (keyType, valueType) = type.arguments.apply { assert(this.size == 2) }
87-
val keySerializer = get(keyType.type, typeMapper.mapType(keyType.type), typeMapper, forceBoxed = true)
88-
val valueSerializer = get(valueType.type, typeMapper.mapType(valueType.type), typeMapper, forceBoxed = true)
102+
val keySerializer = get(
103+
keyType.type, typeMapper.mapTypeSafe(keyType.type), typeMapper, forceBoxed = true, strict = strict())
104+
val valueSerializer = get(
105+
valueType.type, typeMapper.mapTypeSafe(valueType.type), typeMapper, forceBoxed = true, strict = strict())
89106
wrapToNullAwareIfNeeded(type, MapParcelSerializer(asmType, keySerializer, valueSerializer))
90107
}
91108

@@ -131,7 +148,8 @@ interface ParcelSerializer {
131148

132149
asmType.isSparseArray() -> {
133150
val elementType = type.arguments.single().type
134-
val elementSerializer = get(elementType, typeMapper.mapType(elementType), typeMapper, forceBoxed = true)
151+
val elementSerializer = get(
152+
elementType, typeMapper.mapTypeSafe(elementType), typeMapper, forceBoxed = true, strict = strict())
135153
wrapToNullAwareIfNeeded(type, SparseArrayParcelSerializer(asmType, elementSerializer))
136154
}
137155

@@ -158,7 +176,7 @@ interface ParcelSerializer {
158176
Name.identifier("CREATOR"), NoLookupLocation.WHEN_GET_ALL_DESCRIPTORS).firstOrNull()
159177

160178
val creatorAsmType = when {
161-
creatorVar != null -> typeMapper.mapType(creatorVar.type)
179+
creatorVar != null -> typeMapper.mapTypeSafe(creatorVar.type)
162180
clazz.isMagicParcelable -> Type.getObjectType(asmType.internalName + "\$Creator")
163181
else -> null
164182
}
@@ -175,7 +193,12 @@ interface ParcelSerializer {
175193
Method("writeSerializable"),
176194
Method("readSerializable"))
177195

178-
else -> GenericParcelSerializer
196+
else -> {
197+
if (strict && !type.annotations.hasAnnotation(RAWVALUE_ANNOTATION_FQNAME))
198+
throw IllegalArgumentException("Illegal type")
199+
else
200+
GenericParcelSerializer
201+
}
179202
}
180203
}
181204
private fun wrapToNullAwareIfNeeded(type: KotlinType, serializer: ParcelSerializer) = when {

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/DefaultErrorMessagesAndroid.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ class DefaultErrorMessagesAndroid : DefaultErrorMessages.Extension {
6969

7070
MAP.put(ErrorsAndroid.CREATOR_DEFINITION_IS_FORBIDDEN,
7171
"'CREATOR' definition is forbidden. Use 'Parceler' companion object instead.")
72+
73+
MAP.put(ErrorsAndroid.PARCELABLE_TYPE_NOT_SUPPORTED,
74+
"Type is not supported by 'Parcelable'. " +
75+
"Annotate the parameter type with '@RawValue' if you want it to be serialized using 'writeValue()'")
7276
}
7377
}
7478

plugins/android-extensions/android-extensions-compiler/src/org/jetbrains/kotlin/android/synthetic/diagnostic/ErrorsAndroid.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ public interface ErrorsAndroid {
4141
DiagnosticFactory0<PsiElement> PROPERTY_WONT_BE_SERIALIZED = DiagnosticFactory0.create(WARNING);
4242
DiagnosticFactory0<PsiElement> OVERRIDING_WRITE_TO_PARCEL_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
4343
DiagnosticFactory0<PsiElement> CREATOR_DEFINITION_IS_FORBIDDEN = DiagnosticFactory0.create(ERROR);
44+
DiagnosticFactory0<PsiElement> PARCELABLE_TYPE_NOT_SUPPORTED = DiagnosticFactory0.create(ERROR);
4445

4546
@SuppressWarnings("UnusedDeclaration")
4647
Object _initializer = new Object() {
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright 2010-2017 JetBrains s.r.o.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package kotlinx.android.parcel
18+
19+
/**
20+
* Write the corresponding property value with [android.os.Parcel.writeValue].
21+
* Serialization may fail at runtime, depending on actual property value type.
22+
*/
23+
@Target(AnnotationTarget.TYPE)
24+
@Retention(AnnotationRetention.BINARY)
25+
annotation class RawValue

0 commit comments

Comments
 (0)