From c0fab3d60e27e4d7a97223c9626db47e960ca047 Mon Sep 17 00:00:00 2001 From: sergeych Date: Tue, 9 Dec 2025 07:33:05 +0100 Subject: [PATCH] bump version to 1.0.7-SNAPSHOT; fix potential infinite loops in Scope traversal --- lynglib/build.gradle.kts | 2 +- .../kotlin/net/sergeych/lyng/obj/ObjRef.kt | 59 +++++++++++++++---- lynglib/src/commonTest/kotlin/ScriptTest.kt | 14 +++++ 3 files changed, 61 insertions(+), 14 deletions(-) diff --git a/lynglib/build.gradle.kts b/lynglib/build.gradle.kts index 84e2c68..43d938e 100644 --- a/lynglib/build.gradle.kts +++ b/lynglib/build.gradle.kts @@ -21,7 +21,7 @@ import org.jetbrains.kotlin.gradle.ExperimentalWasmDsl import org.jetbrains.kotlin.gradle.dsl.JvmTarget group = "net.sergeych" -version = "1.0.6-SNAPSHOT" +version = "1.0.7-SNAPSHOT" // Removed legacy buildscript classpath declarations; plugins are applied via the plugins DSL below diff --git a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt index 0bec81c..def70a6 100644 --- a/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt +++ b/lynglib/src/commonMain/kotlin/net/sergeych/lyng/obj/ObjRef.kt @@ -1351,15 +1351,21 @@ class FastLocalVarRef( if (owner.frameId != cachedOwnerFrameId) return false // Ensure owner is an ancestor (or same) of current var s: Scope? = current + var guard = 0 while (s != null) { if (s === owner) return true - s = s.parent + val next = s.parent + // Defensive: break on self-parent or pathological cycles + if (next === s) return false + s = next + if (++guard > 4096) return false } return false } private fun resolveSlotInAncestry(scope: Scope): Int { var s: Scope? = scope + var guard = 0 while (s != null) { val idx = s.getSlotIndexOf(name) if (idx != null) { @@ -1368,7 +1374,10 @@ class FastLocalVarRef( cachedSlot = idx return idx } - s = s.parent + val next = s.parent + if (next === s) return -1 + s = next + if (++guard > 4096) return -1 } return -1 } @@ -1387,16 +1396,26 @@ class FastLocalVarRef( // Try per-frame local binding maps in the ancestry first (locals declared in frames) run { var s: Scope? = scope + var guard = 0 while (s != null) { s.localBindings[name]?.let { return it } - s = s.parent + val next = s.parent + if (next === s) break + s = next + if (++guard > 4096) break } } // Try to find a direct local binding in the current ancestry (without invoking name resolution that may prefer fields) - var s: Scope? = scope - while (s != null) { - s.objects[name]?.let { return it } - s = s.parent + run { + var s: Scope? = scope + var guard = 0 + while (s != null) { + s.objects[name]?.let { return it } + val next = s.parent + if (next === s) break + s = next + if (++guard > 4096) break + } } // Fallback to standard name lookup (locals or closure chain) if the slot owner changed across suspension scope[name]?.let { return it } @@ -1413,16 +1432,26 @@ class FastLocalVarRef( // Try per-frame local binding maps in the ancestry first run { var s: Scope? = scope + var guard = 0 while (s != null) { s.localBindings[name]?.let { return it.value } - s = s.parent + val next = s.parent + if (next === s) break + s = next + if (++guard > 4096) break } } // Try to find a direct local binding in the current ancestry first - var s: Scope? = scope - while (s != null) { - s.objects[name]?.let { return it.value } - s = s.parent + run { + var s: Scope? = scope + var guard = 0 + while (s != null) { + s.objects[name]?.let { return it.value } + val next = s.parent + if (next === s) break + s = next + if (++guard > 4096) break + } } // Fallback to standard name lookup (locals or closure chain) scope[name]?.let { return it.value } @@ -1443,6 +1472,7 @@ class FastLocalVarRef( // Try per-frame local binding maps in the ancestry first run { var s: Scope? = scope + var guard = 0 while (s != null) { val rec = s.localBindings[name] if (rec != null) { @@ -1450,7 +1480,10 @@ class FastLocalVarRef( rec.value = newValue return } - s = s.parent + val next = s.parent + if (next === s) break + s = next + if (++guard > 4096) break } } // Fallback to standard name lookup diff --git a/lynglib/src/commonTest/kotlin/ScriptTest.kt b/lynglib/src/commonTest/kotlin/ScriptTest.kt index c761d6f..72745e5 100644 --- a/lynglib/src/commonTest/kotlin/ScriptTest.kt +++ b/lynglib/src/commonTest/kotlin/ScriptTest.kt @@ -4056,4 +4056,18 @@ class ScriptTest { """.trimIndent() ) } + + @Test + fun testHangOnPrintlnInMethods() = runTest { + eval(""" + class T(someList) { + fun f() { + val x = [...someList] + println(x) + } + } + T([1,2]).f() + """) + } + }