core: prevent scope parent-chain cycles when reusing pooled frames

- Scope.resetForReuse: fully detach before re-parenting (clear state, parent=null, new frameId), then validate with ensureNoCycle and assign parent/args/pos/thisObj
- ScopePool.borrow (JVM/Android/JS/Native/Wasm): defensive fallback to fresh Scope allocation if resetForReuse detects a cycle
- docs: add docs/fix-scope-parent-cycle.md describing the change and expectations
- test: add ScopeCycleRegressionTest to ensure instance method call pattern does not crash and returns "ok"
This commit is contained in:
sergeych committed 2025-12-11 00:50:46 +01:00
1 parent b953282251
commit d15dfb6087
8 files changed
+134 -28

No files matched your search

@@ -29,12 +29,18 @@ actual object ScopePool {
actual fun borrow(parent: Scope, args: Arguments, pos: Pos, thisObj: Obj): Scope {
val s = if (pool.isNotEmpty()) pool.removeLast() else Scope(parent, args, pos, thisObj)
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
s.resetForReuse(parent, args, pos, thisObj)
} else {
s.frameId = nextFrameId()
return try {
if (s.parent !== parent || s.args !== args || s.pos !== pos || s.thisObj !== thisObj) {
s.resetForReuse(parent, args, pos, thisObj)
} else {
s.frameId = nextFrameId()
}
s
} catch (e: IllegalStateException) {
if (e.message?.contains("cycle") == true && e.message?.contains("scope parent chain") == true) {
Scope(parent, args, pos, thisObj)
} else throw e
}
return s
}
actual fun release(scope: Scope) {