Skip to content

Commit dcc9d91

Browse files
Fix top level recursive serialization
1 parent 3976cfb commit dcc9d91

File tree

4 files changed

+71
-36
lines changed

4 files changed

+71
-36
lines changed

jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api/VariableState.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,24 @@
11
package org.jetbrains.kotlinx.jupyter.api
22

3+
import java.lang.reflect.Field
34
import kotlin.reflect.KProperty1
45
import kotlin.reflect.full.declaredMemberProperties
56
import kotlin.reflect.jvm.isAccessible
6-
import java.lang.reflect.Field
77

88
interface VariableState {
99
val property: Field
1010
val scriptInstance: Any?
1111
val stringValue: String?
1212
val value: Result<Any?>
13+
val isRecursive: Boolean
1314
}
1415

1516
data class VariableStateImpl(
1617
override val property: Field,
1718
override val scriptInstance: Any,
1819
) : VariableState {
1920
private var cachedValue: Result<Any?> = Result.success(null)
20-
private var isRecursive: Boolean = false
21+
override var isRecursive: Boolean = false
2122

2223
// use of Java 9 required
2324
@SuppressWarnings("DEPRECATION")

src/main/kotlin/org/jetbrains/kotlinx/jupyter/serializationUtils.kt

Lines changed: 62 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import java.lang.reflect.Field
1010
import kotlin.contracts.ExperimentalContracts
1111
import kotlin.contracts.contract
1212
import kotlin.math.abs
13+
import kotlin.random.Random
1314
import kotlin.reflect.KClass
1415
import kotlin.reflect.KProperty
1516
import kotlin.reflect.KProperty1
@@ -59,7 +60,8 @@ data class ProcessedDescriptorsState(
5960
)
6061

6162
data class RuntimeObjectWrapper(
62-
val objectInstance: Any?
63+
val objectInstance: Any?,
64+
val isRecursive: Boolean = false
6365
) {
6466
override fun equals(other: Any?): Boolean {
6567
if (other == null) return objectInstance == null
@@ -69,11 +71,24 @@ data class RuntimeObjectWrapper(
6971
}
7072

7173
override fun hashCode(): Int {
72-
return objectInstance?.hashCode() ?: 0
74+
return if (isRecursive) Random.hashCode() else objectInstance?.hashCode() ?: 0
7375
}
7476
}
7577

76-
fun Any?.toObjectWrapper(): RuntimeObjectWrapper = RuntimeObjectWrapper(this)
78+
fun Any?.toObjectWrapper(isRecursive: Boolean = false): RuntimeObjectWrapper = RuntimeObjectWrapper(this, isRecursive)
79+
80+
81+
fun Any?.getToStringValue(isRecursive: Boolean = false): String {
82+
return if (isRecursive) {
83+
"${this!!::class.simpleName}: recursive structure"
84+
} else {
85+
try {
86+
this?.toString() ?: "null"
87+
} catch (e: StackOverflowError) {
88+
"${this!!::class.simpleName}: recursive structure"
89+
}
90+
}
91+
}
7792

7893
/**
7994
* Provides contract for using threshold-based removal heuristic.
@@ -125,7 +140,8 @@ class VariablesSerializer(
125140
"Map",
126141
"Set",
127142
"Collection",
128-
"LinkedValues"
143+
"LinkedValues",
144+
"LinkedEntrySet"
129145
)
130146

131147
fun isStandardType(type: String): Boolean = containersTypes.contains(type)
@@ -382,25 +398,27 @@ class VariablesSerializer(
382398
it.name == propertyName
383399
} ?: return serializedVariablesState
384400

385-
return serializeVariableState(cellId, propertyName, property, value, false)
401+
return serializeVariableState(cellId, propertyName, property, value, isRecursive = false, false)
386402
}
387403

388404
private fun serializeVariableState(cellId: Int, name: String?, variableState: VariableState?, isOverride: Boolean = true): SerializedVariablesState {
389405
if (!isSerializationActive || variableState == null || name == null) return SerializedVariablesState()
390-
return serializeVariableState(cellId, name, variableState.property, variableState.value, isOverride)
406+
// force recursive check
407+
variableState.stringValue
408+
return serializeVariableState(cellId, name, variableState.property, variableState.value.getOrNull(), variableState.isRecursive, isOverride)
391409
}
392410

393-
private fun serializeVariableState(cellId: Int, name: String, property: Field?, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
411+
private fun serializeVariableState(cellId: Int, name: String, property: Field?, value: Any?, isRecursive: Boolean, isOverride: Boolean = true): SerializedVariablesState {
394412
val processedData = createSerializeVariableState(name, getSimpleTypeNameFrom(property, value), value)
395-
return doActualSerialization(cellId, processedData, value.toObjectWrapper(), isOverride)
413+
return doActualSerialization(cellId, processedData, value.toObjectWrapper(isRecursive), isRecursive, isOverride)
396414
}
397415

398-
private fun serializeVariableState(cellId: Int, name: String, property: KProperty<*>, value: Any?, isOverride: Boolean = true): SerializedVariablesState {
416+
private fun serializeVariableState(cellId: Int, name: String, property: KProperty<*>, value: Any?, isRecursive: Boolean, isOverride: Boolean = true): SerializedVariablesState {
399417
val processedData = createSerializeVariableState(name, getSimpleTypeNameFrom(property, value), value)
400-
return doActualSerialization(cellId, processedData, value.toObjectWrapper(), isOverride)
418+
return doActualSerialization(cellId, processedData, value.toObjectWrapper(isRecursive), isRecursive, isOverride)
401419
}
402420

403-
private fun doActualSerialization(cellId: Int, processedData: ProcessedSerializedVarsState, value: RuntimeObjectWrapper, isOverride: Boolean = true): SerializedVariablesState {
421+
private fun doActualSerialization(cellId: Int, processedData: ProcessedSerializedVarsState, value: RuntimeObjectWrapper, isRecursive: Boolean, isOverride: Boolean = true): SerializedVariablesState {
404422
val serializedVersion = processedData.serializedVariablesState
405423

406424
seenObjectsPerCell.putIfAbsent(cellId, mutableMapOf())
@@ -428,9 +446,13 @@ class VariablesSerializer(
428446
}
429447
val type = processedData.propertiesType
430448
if (type == PropertiesType.KOTLIN) {
431-
iterateThroughContainerMembers(cellId, value.objectInstance, serializedVersion.fieldDescriptor, kProperties = currentCellDescriptors.processedSerializedVarsToKTProperties[serializedVersion])
449+
val kProperties = currentCellDescriptors.processedSerializedVarsToKTProperties[serializedVersion]
450+
if (kProperties?.size == 1 && kProperties.first().name == "size" ) {
451+
serializedVersion.fieldDescriptor.addDescriptor(value.objectInstance, "data")
452+
}
453+
iterateThroughContainerMembers(cellId, value.objectInstance, serializedVersion.fieldDescriptor, isRecursive = isRecursive, kProperties = currentCellDescriptors.processedSerializedVarsToKTProperties[serializedVersion])
432454
} else {
433-
iterateThroughContainerMembers(cellId, value.objectInstance, serializedVersion.fieldDescriptor, currentCellDescriptors.processedSerializedVarsToJavaProperties[serializedVersion])
455+
iterateThroughContainerMembers(cellId, value.objectInstance, serializedVersion.fieldDescriptor, isRecursive = isRecursive, currentCellDescriptors.processedSerializedVarsToJavaProperties[serializedVersion])
434456
}
435457
}
436458

@@ -441,18 +463,19 @@ class VariablesSerializer(
441463
cellId: Int,
442464
callInstance: Any?,
443465
descriptor: MutableFieldDescriptor,
466+
isRecursive: Boolean = false,
444467
properties: PropertiesData? = null,
445468
kProperties: KPropertiesData? = null,
446469
currentDepth: Int = 0
447470
) {
448471
fun iterateAndStoreValues(callInstance: Any, descriptorsState: MutableMap<String, SerializedVariablesState?>) {
449472
if (callInstance is Collection<*>) {
450473
callInstance.forEach {
451-
descriptorsState.addDescriptor(it)
474+
descriptorsState.addDescriptor(it, name = it.getToStringValue())
452475
}
453476
} else if (callInstance is Array<*>) {
454477
callInstance.forEach {
455-
descriptorsState.addDescriptor(it)
478+
descriptorsState.addDescriptor(it, name = it.getToStringValue())
456479
}
457480
}
458481
}
@@ -472,15 +495,15 @@ class VariablesSerializer(
472495
if (currentSerializeCount > serializationLimit) {
473496
break
474497
}
475-
iterateThrough(it, seenObjectsPerCell, serializedIteration, descriptor, instancesPerState, callInstance)
498+
iterateThrough(it, seenObjectsPerCell, serializedIteration, descriptor, instancesPerState, callInstance, isRecursive)
476499
currentSerializeCount++
477500
}
478501
} else if (kProperties != null) {
479502
for (it in kProperties) {
480503
if (currentSerializeCount > serializationLimit) {
481504
break
482505
}
483-
iterateThrough(it, seenObjectsPerCell, serializedIteration, descriptor, instancesPerState, callInstance)
506+
iterateThrough(it, seenObjectsPerCell, serializedIteration, descriptor, instancesPerState, callInstance, isRecursive)
484507
currentSerializeCount++
485508
}
486509
}
@@ -506,6 +529,10 @@ class VariablesSerializer(
506529
}
507530
}
508531

532+
// if (isRecursive) {
533+
// return
534+
// }
535+
509536
serializedIteration.forEach {
510537
val serializedVariablesState = it.value.serializedVariablesState
511538
val name = it.key
@@ -520,14 +547,15 @@ class VariablesSerializer(
520547
else -> {
521548
null
522549
}
523-
}.toObjectWrapper()
550+
}.toObjectWrapper(isRecursive)
524551

525552
computedDescriptorsPerCell[cellId]!!.instancesPerState += instancesPerState
526553
iterateThroughContainerMembers(
527554
cellId,
528555
neededCallInstance.objectInstance,
529556
serializedVariablesState.fieldDescriptor,
530-
it.value.propertiesData,
557+
isRecursive = isRecursive,
558+
properties = it.value.propertiesData,
531559
currentDepth = currentDepth + 1
532560
)
533561
}
@@ -545,17 +573,18 @@ class VariablesSerializer(
545573
serializedIteration: MutableMap<String, ProcessedSerializedVarsState>,
546574
descriptor: MutableFieldDescriptor,
547575
instancesPerState: MutableMap<SerializedVariablesState, Any?>,
548-
callInstance: Any
576+
callInstance: Any,
577+
isRecursive: Boolean = false
549578
) {
550579
contract {
551580
returns() implies (elem is Field || elem is KProperty1<*, *>)
552581
}
553582

554583
val name = if (elem is Field) elem.name else (elem as KProperty1<Any, *>).name
555-
val value = if (elem is Field) tryGetValueFromProperty(elem, callInstance).toObjectWrapper()
584+
val value = if (elem is Field) tryGetValueFromProperty(elem, callInstance).toObjectWrapper(isRecursive)
556585
else {
557586
elem as KProperty1<Any, *>
558-
tryGetValueFromProperty(elem, callInstance).toObjectWrapper()
587+
tryGetValueFromProperty(elem, callInstance).toObjectWrapper(isRecursive)
559588
}
560589

561590
val simpleType = if (elem is Field) getSimpleTypeNameFrom(elem, value.objectInstance) ?: "null"
@@ -564,6 +593,7 @@ class VariablesSerializer(
564593
getSimpleTypeNameFrom(elem, value.objectInstance) ?: "null"
565594
}
566595
serializedIteration[name] = if (standardContainersUtilizer.isStandardType(simpleType)) {
596+
// todo might add isRecursive
567597
standardContainersUtilizer.serializeContainer(simpleType, value.objectInstance, true)
568598
} else {
569599
createSerializeVariableState(name, simpleType, value)
@@ -612,7 +642,7 @@ class VariablesSerializer(
612642
if (value != null) {
613643
value::class.simpleName
614644
} else {
615-
value?.toString()
645+
value?.getToStringValue()
616646
}
617647
}
618648
}
@@ -627,7 +657,7 @@ class VariablesSerializer(
627657
(classifier as KClass<*>).simpleName
628658
}
629659
} else {
630-
value?.toString()
660+
value?.getToStringValue()
631661
}
632662
}
633663

@@ -742,7 +772,8 @@ fun getProperString(value: Any?): String {
742772

743773
val kClass = value::class
744774
val isFromJavaArray = kClass.java.isArray
745-
try {
775+
776+
return try {
746777
if (isFromJavaArray || kClass.isArray()) {
747778
value as Array<*>
748779
return buildString {
@@ -780,11 +811,14 @@ fun getProperString(value: Any?): String {
780811
}
781812
}
782813
}
783-
} catch (e: Throwable) {
784814
value.toString()
815+
} catch (e: Throwable) {
816+
if (e is StackOverflowError) {
817+
"${value::class.simpleName}: recursive structure"
818+
} else {
819+
value.toString()
820+
}
785821
}
786-
787-
return value.toString()
788822
}
789823

790824
fun KClass<*>.isArray(): Boolean = this.isSubclassOf(Array::class)

src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl/ReplTests.kt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,12 +541,12 @@ class ReplVarsTest : AbstractSingleReplTest() {
541541
""".trimIndent(),
542542
jupyterId = 2
543543
)
544-
544+
val toStringValues = varsState.mapToStringValues()
545545
assertTrue(varsState.isNotEmpty())
546-
assertEquals(3, varsState.size)
547-
assertEquals("1024", varsState.getStringValue("x"))
548-
assertEquals("${123 * 2}", varsState.getStringValue("y"))
549-
assertEquals("abc", varsState.getValue("z"))
546+
assertEquals(3, toStringValues.size)
547+
assertEquals("1024", toStringValues["x"])
548+
assertEquals("${123 * 2}", toStringValues["y"])
549+
assertEquals("abc", toStringValues["z"])
550550
}
551551

552552
@Test

src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/testUtil.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ fun CompletionResult.getOrFail(): CompletionResult.Success = when (this) {
104104
}
105105

106106
fun Map<String, VariableState>.mapToStringValues(): Map<String, String?> {
107-
return mapValues { it.value.stringValue }
107+
return mapValues { it.value.value.getOrNull().toString() }
108108
}
109109

110110
fun Map<String, VariableState>.getStringValue(variableName: String): String? {

0 commit comments

Comments
 (0)