Skip to content

Commit bd39684

Browse files
Fix recursion detection
1 parent 9de0755 commit bd39684

File tree

2 files changed

+47
-46
lines changed
  • jupyter-lib/api/src/main/kotlin/org/jetbrains/kotlinx/jupyter/api
  • src/test/kotlin/org/jetbrains/kotlinx/jupyter/test/repl

2 files changed

+47
-46
lines changed

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

+20-46
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ package org.jetbrains.kotlinx.jupyter.api
22

33
import java.lang.reflect.Field
44
import kotlin.reflect.KProperty
5-
import kotlin.reflect.KProperty1
6-
import kotlin.reflect.full.declaredMemberProperties
7-
import kotlin.reflect.jvm.isAccessible
85

96
interface VariableState {
107
val property: Field
@@ -32,6 +29,7 @@ data class VariableStateImpl(
3229
) : VariableState {
3330
private var cachedValue: Result<Any?> = Result.success(null)
3431
override var isRecursive: Boolean = false
32+
private var isLargeForString: Boolean = false
3533

3634
// use of Java 9 required
3735
@SuppressWarnings("DEPRECATION")
@@ -58,10 +56,28 @@ data class VariableStateImpl(
5856
if (cachedValue.getOrNull() == null) {
5957
return@DependentLazyDelegate null
6058
}
61-
handleIfRecursiveStructure()
59+
try {
60+
cachedValue.getOrNull().toString()
61+
isRecursive = false
62+
isLargeForString = false
63+
} catch (e: VirtualMachineError) {
64+
when (e) {
65+
is StackOverflowError -> {
66+
isRecursive = true
67+
}
68+
is OutOfMemoryError -> {
69+
isLargeForString = true
70+
}
71+
else -> {
72+
return@DependentLazyDelegate null
73+
}
74+
}
75+
}
6276

6377
if (!isRecursive) {
6478
cachedValue.getOrNull().toString()
79+
} else if (isLargeForString) {
80+
"${cachedValue.getOrNull()!!::class.simpleName}: too large structure"
6581
} else {
6682
getRecursiveObjectName()
6783
}
@@ -72,46 +88,4 @@ data class VariableStateImpl(
7288

7389
override val value: Result<Any?>
7490
get() = cachedValue
75-
76-
private fun handleIfRecursiveStructure() {
77-
if (cachedValue.getOrNull() == null) return
78-
traverseObject(cachedValue.getOrNull(), mutableSetOf())
79-
}
80-
81-
private fun traverseObject(value: Any?, seenNodes: MutableSet<Any>) {
82-
if (value == null) return
83-
val membersProperties = try {
84-
value::class.declaredMemberProperties
85-
} catch (ex: Throwable) {
86-
emptyList<Collection<KProperty1<Any, *>>>()
87-
}
88-
89-
val receivedInstances: MutableList<Any?> = mutableListOf()
90-
for (property in membersProperties) {
91-
@Suppress("UNCHECKED_CAST")
92-
property as KProperty1<Any, *>
93-
try {
94-
val wasAccessible = property.isAccessible
95-
property.isAccessible = true
96-
val callInstance = property.get(value)
97-
property.isAccessible = wasAccessible
98-
val wasSeen = callInstance != null && !seenNodes.add(callInstance)
99-
100-
if (wasSeen) {
101-
isRecursive = true
102-
return
103-
}
104-
receivedInstances.add(callInstance)
105-
} catch (ex: Throwable) {
106-
// there might be recursive elements inside the container
107-
if (ex is StackOverflowError) {
108-
isRecursive = true
109-
}
110-
return
111-
}
112-
}
113-
receivedInstances.forEach {
114-
traverseObject(it, seenNodes)
115-
}
116-
}
11791
}

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

+27
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,33 @@ class ReplVarsTest : AbstractSingleReplTest() {
771771
assertEquals(2, newData.fieldDescriptor.size)
772772
}
773773

774+
@Test
775+
fun testProperBiRecursionHandling() {
776+
eval(
777+
"""
778+
val l = mutableListOf<Any>()
779+
l.add(listOf(l))
780+
781+
val m = mutableMapOf<Int, Any>(1 to l)
782+
783+
val z = setOf(1, 2, 4)
784+
""".trimIndent(),
785+
jupyterId = 1
786+
)
787+
var state = repl.notebook.variablesState
788+
assertEquals("ArrayList: recursive structure", state["l"]!!.stringValue)
789+
assertEquals("LinkedHashMap: recursive structure", state["m"]!!.stringValue)
790+
eval(
791+
"""
792+
val m = mutableMapOf<Int, Any>(1 to "abc")
793+
""".trimIndent(),
794+
jupyterId = 2
795+
)
796+
state = repl.notebook.variablesState
797+
assertEquals("ArrayList: recursive structure", state["l"]!!.stringValue)
798+
assertNotEquals("LinkedHashMap: recursive structure", state["m"]!!.stringValue)
799+
}
800+
774801
@Test
775802
fun testUnchangedVars() {
776803
eval(

0 commit comments

Comments
 (0)