fix: prevent stack overflow on recursive toString for List and Map
Add `String.safeToString_` static method that tracks objects currently being converted to string per fiber, returning "..." when recursion is detected. Wrap List.toString and Map.toString with this guard to handle self-referential and mutually recursive structures without crashing.
This commit is contained in:
+39
-9
@@ -127,6 +127,34 @@ class WhereSequence is Sequence {
|
||||
}
|
||||
|
||||
class String is Sequence {
|
||||
// Avoids recursively calling [toString] on [object] and overflowing the
|
||||
// stack.
|
||||
//
|
||||
// If we are already within a call to [safeToString_] on [object] in this
|
||||
// fiber, then this returns "...". Otherwise, it returns the result of
|
||||
// calling [ifUnseen].
|
||||
static safeToString_(object, ifUnseen) {
|
||||
if (__seenByFiber == null) __seenByFiber = new Map
|
||||
var seen = __seenByFiber[Fiber.current]
|
||||
if (seen == null) {
|
||||
__seenByFiber[Fiber.current] = seen = new List
|
||||
}
|
||||
|
||||
// See if we are recursing on it.
|
||||
for (outer in seen) {
|
||||
if (Object.same(outer, object)) return "..."
|
||||
}
|
||||
|
||||
seen.add(object)
|
||||
|
||||
var result = ifUnseen.call()
|
||||
|
||||
seen.removeAt(-1)
|
||||
if (seen.count == 0) __seenByFiber.remove(Fiber.current)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
bytes { new StringByteSequence(this) }
|
||||
}
|
||||
|
||||
@@ -148,7 +176,7 @@ class List is Sequence {
|
||||
return other
|
||||
}
|
||||
|
||||
toString { "[" + join(", ") + "]" }
|
||||
toString { String.safeToString_(this) { "[" + join(", ") + "]" } }
|
||||
|
||||
+(other) {
|
||||
var result = this[0..-1]
|
||||
@@ -164,16 +192,18 @@ class Map {
|
||||
values { new MapValueSequence(this) }
|
||||
|
||||
toString {
|
||||
var first = true
|
||||
var result = "{"
|
||||
return String.safeToString_(this) {
|
||||
var first = true
|
||||
var result = "{"
|
||||
|
||||
for (key in keys) {
|
||||
if (!first) result = result + ", "
|
||||
first = false
|
||||
result = result + key.toString + ": " + this[key].toString
|
||||
for (key in keys) {
|
||||
if (!first) result = result + ", "
|
||||
first = false
|
||||
result = result + key.toString + ": " + this[key].toString
|
||||
}
|
||||
|
||||
return result + "}"
|
||||
}
|
||||
|
||||
return result + "}"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user