Skip to content

Commit fa7d94d

Browse files
committed
Important bug fix: Recursive class references to themselves caused Stack overflow exception
1 parent 7fab4ef commit fa7d94d

File tree

9 files changed

+178
-53
lines changed

9 files changed

+178
-53
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Changelog
22

3+
## v0.5.2.1
4+
- Important bug fix: Recursive class references to themselves caused Stack overflow exception
5+
36
## v0.5.2
47
- `Deserializer` reformat. `JsonItem` instances now don't need to know deserializer types except `Deserializer` itself.
58
- `@JsonField` support to let json-fields be named in other way than in deserialized object fields

src/iris/json/serialization/DeserializerClassBuilder.kt

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import kotlin.reflect.full.memberProperties
1212
* @author [Ivan Ivanov](https://vk.com/irisism)
1313
*/
1414
object DeserializerClassBuilder {
15-
fun build(d: KClass<*>): DeserializerClassImpl {
15+
fun build(d: KClass<*>, targetClassImpl: DeserializerClassImpl = DeserializerClassImpl()): DeserializerClassImpl {
1616
var hasPolymorphies = false
17-
val constructorInfo = getFieldsOrder(d.constructors)
17+
val constructorInfo = getBestConstructor(d)
1818
val (constr, constructorFields) = constructorInfo
1919

2020
val mProperties = d.memberProperties
@@ -29,23 +29,28 @@ object DeserializerClassBuilder {
2929
jsonItemName to p
3030
}
3131

32-
return DeserializerClassImpl(constr, fieldsList, hasPolymorphies)
32+
targetClassImpl.constructorFunction = constr
33+
targetClassImpl.fields = fieldsList
34+
targetClassImpl.hasPolymorphisms = hasPolymorphies
35+
return targetClassImpl
3336
}
3437

3538
private fun getPropertyInfo(it: KProperty<*>, constructorParameter: KParameter?): DeserializerClassImpl.PropertyInfo {
36-
val tType = DeserializerPrimitiveImpl.convertType(it.returnType, null)
39+
3740
var type: DeserializerPrimitiveImpl? = null
3841
var inheritInfo: DeserializerClassImpl.PolymorphInfo? = null
3942
var innerClass: Deserializer? = null
40-
if (tType != null) { // simple type int/string/boolean
41-
type = tType
42-
} else { // there some complex class
43-
val data = it.findAnnotation<PolymorphData>()
44-
if (data != null) { // is polymorphic
45-
val cases = mutableMapOf<Any, Deserializer>()
46-
data.strings.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) }
47-
data.ints.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) }
48-
inheritInfo = DeserializerClassImpl.PolymorphInfo(data.sourceField, cases)
43+
44+
val data = it.findAnnotation<PolymorphData>()
45+
if (data != null) { // is polymorphic
46+
val cases = mutableMapOf<Any, Deserializer>()
47+
data.strings.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) }
48+
data.ints.associateTo(cases) { it.label to DeserializerFactory.getDeserializer(it.instance) }
49+
inheritInfo = DeserializerClassImpl.PolymorphInfo(data.sourceField, cases)
50+
} else {
51+
val tType = DeserializerPrimitiveImpl.convertType(it.returnType, null)
52+
if (tType != null) { // simple type int/string/boolean
53+
type = tType
4954
} else {
5055
innerClass = DeserializerFactory.getDeserializer(it.returnType)
5156
}
@@ -54,13 +59,14 @@ object DeserializerClassBuilder {
5459
return DeserializerClassImpl.PropertyInfo(/*it.name, */it, constructorParameter, type, innerClass, inheritInfo)
5560
}
5661

57-
private fun getFieldsOrder(constructors: Collection<KFunction<*>>): Pair<KFunction<*>, List<KParameter>> {
58-
var best: KFunction<*>? = null
62+
private fun getBestConstructor(d: KClass<*>): Pair<KFunction<*>, List<KParameter>> {
63+
val constructors = d.constructors
64+
if (constructors.isEmpty())
65+
throw IllegalArgumentException("No any constructor for $d")
66+
var best: KFunction<*> = constructors.first()
5967
for (c in constructors)
60-
if (best == null || c.parameters.size > best.parameters.size)
68+
if (c.parameters.size > best.parameters.size)
6169
best = c
62-
if (best == null)
63-
throw IllegalArgumentException("No any constructor")
6470
return best to best.parameters
6571
}
6672
}

src/iris/json/serialization/DeserializerClassImpl.kt

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import iris.json.JsonEntry
44
import iris.json.JsonItem
55
import iris.json.JsonObject
66
import java.util.*
7+
import kotlin.collections.ArrayList
78
import kotlin.collections.HashMap
89
import kotlin.reflect.*
910

@@ -12,7 +13,11 @@ import kotlin.reflect.*
1213
* @author [Ivan Ivanov](https://vk.com/irisism)
1314
*/
1415

15-
class DeserializerClassImpl(private val constructorFunction: KFunction<*>, private val fields: Map<String, PropertyInfo>, private val hasPolymorphisms: Boolean) : DeserializerClass {
16+
class DeserializerClassImpl : DeserializerClass {
17+
18+
var hasPolymorphisms = false
19+
lateinit var fields: Map<String, PropertyInfo>
20+
lateinit var constructorFunction: KFunction<*>
1621

1722
class PolymorphInfo(val sourceField: String, val inheritClasses: Map<Any, Deserializer>)
1823

@@ -22,6 +27,8 @@ class DeserializerClassImpl(private val constructorFunction: KFunction<*>, priva
2227
, val polymorphInfo: PolymorphInfo? = null
2328
)
2429

30+
class SetterPropertyException(message: String, cause: Throwable) : Throwable(message, cause)
31+
2532
override fun <T> deserialize(item: JsonItem): T {
2633
return getObject((item as JsonObject).getEntries())
2734
}
@@ -44,8 +51,14 @@ class DeserializerClassImpl(private val constructorFunction: KFunction<*>, priva
4451
result = null
4552
}
4653

47-
val otherFields = mutableListOf<Pair<KProperty<*>, Any?>>()
48-
val constructorMap = HashMap<KParameter, Any?>(info.constructorFunction.parameters.size)
54+
val conscructorParametersSize = info.constructorFunction.parameters.size
55+
val otherSize = fields.size - conscructorParametersSize
56+
val constructorMap = HashMap<KParameter, Any?>(conscructorParametersSize)
57+
val otherFields = (if (otherSize == 0)
58+
null
59+
else
60+
ArrayList<Pair<KProperty<*>, Any?>>(fields.size - conscructorParametersSize))
61+
4962
for ((key, jsonItem) in entries) {
5063
val field = key.toString()
5164
val param = fields[field]?: continue
@@ -54,10 +67,10 @@ class DeserializerClassImpl(private val constructorFunction: KFunction<*>, priva
5467
val sourceValue = result!![polymorphInfo.sourceField]
5568
if (sourceValue != null) { // already know what type is it
5669
val inherit = polymorphInfo.inheritClasses[sourceValue]!!
57-
val newValue: Any? = inherit.deserialize(jsonItem)//jsonItem.asObject(inherit)
70+
val newValue: Any? = inherit.deserialize(jsonItem)
5871
result[field] = newValue
5972
param.constructorParameter?.let { constructorMap[it] = newValue }
60-
?: run { otherFields += param.property to newValue }
73+
?: run { otherFields!! += param.property to newValue }
6174

6275
} else { // need delay initialization until we know source info
6376
val item = delayedInit!![polymorphInfo.sourceField]
@@ -76,28 +89,34 @@ class DeserializerClassImpl(private val constructorFunction: KFunction<*>, priva
7689
val inherit = property.polymorphInfo!!.inheritClasses[value]!!
7790
val newValue: Any = inherit.deserialize(item.json)
7891
item.propertyInfo.constructorParameter?.let { constructorMap[it] = newValue }
79-
?: run { otherFields += property.property to newValue }
92+
?: run { otherFields!! += property.property to newValue }
8093

8194
if (delayed.items != null) {
8295
for (item in delayed.items!!) {
8396
val property = item.propertyInfo
8497
val inherit = property.polymorphInfo!!.inheritClasses[value]!!
8598
val newValue: Any = inherit.deserialize(item.json)
8699
item.propertyInfo.constructorParameter?.let { constructorMap[it] = newValue }
87-
?: run { otherFields += property.property to newValue }
100+
?: run { otherFields!! += property.property to newValue }
88101
}
89102
}
90103
}
91104
result!![field] = value
92105
}
93106

94107
param.constructorParameter?.let { constructorMap[it] = value }
95-
?: run{ otherFields += param.property to value }
108+
?: run{ otherFields!! += param.property to value }
96109
}
97110
}
98111
val item = info.constructorFunction.callBy(constructorMap) as T
99-
for (field in otherFields) {
100-
(field.first as KMutableProperty<*>).setter.call(item, field.second)
112+
if (otherFields != null) {
113+
for (field in otherFields) {
114+
try {
115+
(field.first as KMutableProperty<*>).setter.call(item, field.second)
116+
} catch (e: ClassCastException) {
117+
throw SetterPropertyException("Property $field does not have available setter", e)
118+
}
119+
}
101120
}
102121
return item
103122
}

src/iris/json/serialization/DeserializerCollectionImpl.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,12 @@ import kotlin.reflect.KClass
99
* @author [Ivan Ivanov](https://vk.com/irisism)
1010
*/
1111

12-
class DeserializerCollectionImpl(val typeDeserializer: Deserializer) : DeserializerCollection {
12+
class DeserializerCollectionImpl : DeserializerCollection {
13+
14+
lateinit var typeDeserializer: Deserializer
15+
1316
override fun getObject(items: Collection<JsonItem>): Collection<*> {
14-
val res = mutableListOf<Any?>()
17+
val res = ArrayList<Any?>(items.size)
1518
for (item in items)
1619
res.add(typeDeserializer.deserialize(item))
1720
return res

src/iris/json/serialization/DeserializerFactory.kt

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -17,37 +17,59 @@ import kotlin.reflect.jvm.jvmErasure
1717
object DeserializerFactory {
1818
private val cache = mutableMapOf<KClass<*>, Deserializer>()
1919
private val typeCache = mutableMapOf<KType, Deserializer>()
20+
private val anyClass = Any::class
2021

2122
fun getDeserializer(d: KClass<*>, allowSuperclasses: Boolean = true): Deserializer {
22-
return cache.getOrPut(d) {
23-
if (allowSuperclasses) {
24-
for (supers in d.superclasses)
25-
cache[supers]?.let { return@getOrPut it.forSubclass(d) }
23+
cache[d]?.let { return it }
24+
if (allowSuperclasses) {
25+
for (supers in d.superclasses) {
26+
if (supers == anyClass) continue
27+
cache[supers]?.let {
28+
return it.forSubclass(d).also { cache[d] = it }
29+
}
2630
}
27-
DeserializerClassBuilder.build(d)
2831
}
32+
val deser = DeserializerClassImpl()
33+
cache[d] = deser
34+
DeserializerClassBuilder.build(d, deser)
35+
return deser
2936
}
3037

3138
fun registerDeserializer(d: KClass<*>, deserializer: Deserializer) {
3239
cache[d] = deserializer
3340
}
3441

42+
fun registerDeserializer(type: KType, deserializer: Deserializer) {
43+
typeCache[type] = deserializer
44+
}
45+
3546
fun getDeserializer(type: KType): Deserializer {
36-
return typeCache.getOrPut(type) {
37-
DeserializerPrimitiveImpl.convertType(type, null)
38-
?.let { return@getOrPut it }
39-
40-
with(type.jvmErasure) { when {
41-
isSubclassOf(Collection::class) ->
42-
DeserializerCollectionImpl(getDeserializer(type.arguments.firstOrNull()?.type?: throw IllegalStateException("Don't know how I got here")))
43-
isSubclassOf(Map::class) ->
44-
DeserializerMapImpl(getDeserializer(getMapType(type)))
45-
isSubclassOf(JsonItem::class) ->
46-
DeserializerJsonItem()
47-
else ->
48-
getDeserializer(this)
49-
}}
50-
}
47+
typeCache[type]
48+
?.let { return it }
49+
50+
DeserializerPrimitiveImpl.convertType(type, null)
51+
?.let {
52+
return it.also { typeCache[type] = it }
53+
}
54+
55+
return with(type.jvmErasure) { when {
56+
isSubclassOf(Collection::class) -> {
57+
val deser = DeserializerCollectionImpl()
58+
typeCache[type] = deser
59+
deser.typeDeserializer = getDeserializer(type.arguments.firstOrNull()?.type?: throw IllegalStateException("Don't know how I got here"))
60+
deser
61+
}
62+
isSubclassOf(Map::class) -> {
63+
val deser = DeserializerMapImpl()
64+
typeCache[type] = deser
65+
deser.valueDeserializer = getDeserializer(getMapType(type))
66+
deser
67+
}
68+
isSubclassOf(JsonItem::class) ->
69+
DeserializerJsonItem()
70+
else ->
71+
getDeserializer(this)
72+
}}
5173
}
5274

5375
private fun getMapType(type: KType): KType {

src/iris/json/serialization/DeserializerMapImpl.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ import kotlin.reflect.KClass
99
* @created 09.10.2020
1010
* @author [Ivan Ivanov](https://vk.com/irisism)
1111
*/
12-
class DeserializerMapImpl(val valueDeserializer: Deserializer) : DeserializerMap {
12+
class DeserializerMapImpl : DeserializerMap {
13+
14+
lateinit var valueDeserializer: Deserializer
15+
1316
override fun <T> getMap(entries: Collection<JsonEntry>): Map<String, T> {
1417
return entries.associate {(key, value) ->
1518
key.toString() to (valueDeserializer.deserialize(value) as T)

src/iris/json/serialization/annotations.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,4 +15,5 @@ annotation class PolymorphCaseString(val label: String, val instance: KClass<*>)
1515
@Target(AnnotationTarget.PROPERTY)
1616
annotation class PolymorphCaseInt(val label: Int, val instance: KClass<*>)
1717

18+
@Target(AnnotationTarget.PROPERTY)
1819
annotation class PolymorphData(val sourceField: String, val strings: Array<PolymorphCaseString> = [], val ints: Array<PolymorphCaseInt> = [])

test/iris/json/flow/serialization.kt

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,40 @@ fun main() {
5656
testRegisteredDeserializer(); println()
5757
testQuotelessFieldNames(); println()
5858
testSubclassRegister(); println()
59+
testRecursiveClassFields(); println()
60+
testRecursiveGenericClassFields(); println()
61+
}
62+
63+
fun createJsonItem(text: String): JsonItem {
64+
return JsonFlowParser.start(text)
65+
}
66+
67+
data class RecursiveGenericClass (
68+
var inner: List<RecursiveGenericClass>? = null,
69+
var someData: String? = null
70+
)
71+
72+
fun testRecursiveGenericClassFields() {
73+
println("testRecursiveGenericClassFields:")
74+
val item = createJsonItem("""{
75+
inner: [{inner: [{someData: "3 level"}], someData: "2 level"}], someData: "1 level"
76+
}""".trimMargin())
77+
val list = item.asObject<RecursiveGenericClass>()
78+
println(list)
79+
}
80+
81+
data class RecursiveClass (
82+
var inner: RecursiveClass? = null,
83+
var someData: String? = null
84+
)
85+
86+
fun testRecursiveClassFields() {
87+
println("testRecursiveClassFields:")
88+
val item = createJsonItem("""{
89+
inner: {inner: {someData: "3 level"}, someData: "2 level"}, someData: "1 level"
90+
}""".trimMargin())
91+
val list = item.asObject<RecursiveClass>()
92+
println(list)
5993
}
6094

6195
fun testSubclassRegister() {

test/iris/json/plain/serialization.kt

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,41 @@ fun main() {
5656
testRegisteredDeserializer(); println()
5757
testQuotelessFieldNames(); println()
5858
testSubclassRegister(); println()
59+
testRecursiveClassFields(); println()
60+
testRecursiveGenericClassFields(); println()
61+
}
62+
63+
fun createJsonItem(text: String): JsonItem {
64+
val parser = IrisJsonParser(text)
65+
return parser.parse()
66+
}
67+
68+
data class RecursiveGenericClass (
69+
var inner: List<RecursiveGenericClass>? = null,
70+
var someData: String? = null
71+
)
72+
73+
fun testRecursiveGenericClassFields() {
74+
println("testRecursiveGenericClassFields:")
75+
val item = createJsonItem("""{
76+
inner: [{inner: [{someData: "3 level"}], someData: "2 level"}], someData: "1 level"
77+
}""".trimMargin())
78+
val list = item.asObject<RecursiveGenericClass>()
79+
println(list)
80+
}
81+
82+
data class RecursiveClass (
83+
var inner: RecursiveClass? = null,
84+
var someData: String? = null
85+
)
86+
87+
fun testRecursiveClassFields() {
88+
println("testRecursiveClassFields:")
89+
val item = createJsonItem("""{
90+
inner: {inner: {someData: "3 level"}, someData: "2 level"}, someData: "1 level"
91+
}""".trimMargin())
92+
val list = item.asObject<RecursiveClass>()
93+
println(list)
5994
}
6095

6196
fun testSubclassRegister() {
@@ -80,11 +115,10 @@ fun testSubclassRegister() {
80115
}
81116
})
82117

83-
val parser = IrisJsonParser("""{
118+
val item = createJsonItem("""{
84119
|person1: {"type": "male", name: "Akbar", age: 35, "cashAmount": 12200.12, "property": {"name": "Домик в деревне"}},
85120
|"person2": {"type": "female","name": "Alla Who", "height": 170, "income": 1214.81}
86121
|}""".trimMargin())
87-
val item = parser.parse()
88122
val list = item.asObject<Map<String, Human>>()
89123
println(list)
90124
}

0 commit comments

Comments
 (0)