fix(codemode): linearize reference walks (#37179)

This commit is contained in:
Aiden Cline 2026-07-15 17:24:36 -05:00 committed by GitHub
commit fd19fa89c4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -34,30 +34,51 @@ export const isRuntimeReference = (value: unknown): boolean =>
value instanceof ErrorConstructorReference || value instanceof ErrorConstructorReference ||
isCodeModeValue(value) isCodeModeValue(value)
export const containsRuntimeReference = (value: unknown, seen = new Set<object>()): boolean => { function* childValues(value: object): Generator<unknown> {
if (isRuntimeReference(value)) return true if (Array.isArray(value)) {
if (value === null || typeof value !== "object") return false const length = value.length
if (seen.has(value)) return false for (let index = 0; index < length; index++) yield value[index]
seen.add(value) return
const contains = Array.isArray(value) }
? value.some((item) => containsRuntimeReference(item, seen)) yield* Object.values(value)
: Object.values(value).some((item) => containsRuntimeReference(item, seen)) }
seen.delete(value)
return contains export const containsRuntimeReference = (value: unknown): boolean => {
const pending: Array<Iterator<unknown>> = [[value].values()]
const seen = new Set<object>()
while (pending.length > 0) {
const next = pending.at(-1)!.next()
if (next.done) {
pending.pop()
continue
}
const current = next.value
if (isRuntimeReference(current)) return true
if (current === null || typeof current !== "object" || seen.has(current)) continue
seen.add(current)
pending.push(childValues(current))
}
return false
} }
// CodeMode values are data here, not opaque interpreter references. // CodeMode values are data here, not opaque interpreter references.
export const containsOpaqueReference = (value: unknown, seen = new Set<object>()): boolean => { export const containsOpaqueReference = (value: unknown): boolean => {
if (isCodeModeValue(value)) return false const pending: Array<Iterator<unknown>> = [[value].values()]
if (isRuntimeReference(value)) return true const seen = new Set<object>()
if (value === null || typeof value !== "object") return false while (pending.length > 0) {
if (seen.has(value)) return false const next = pending.at(-1)!.next()
seen.add(value) if (next.done) {
const contains = Array.isArray(value) pending.pop()
? value.some((item) => containsOpaqueReference(item, seen)) continue
: Object.values(value).some((item) => containsOpaqueReference(item, seen)) }
seen.delete(value) const current = next.value
return contains if (isCodeModeValue(current)) continue
if (isRuntimeReference(current)) return true
if (current === null || typeof current !== "object" || seen.has(current)) continue
seen.add(current)
pending.push(childValues(current))
}
return false
} }
// Reject cycles before mutation so later boundary walks remain safe. // Reject cycles before mutation so later boundary walks remain safe.
@ -66,15 +87,22 @@ export const rejectCircularInsertion = (
value: unknown, value: unknown,
label: string, label: string,
node: AstNode, node: AstNode,
seen = new Set<object>(),
): void => { ): void => {
if (value === container) const pending: Array<Iterator<unknown>> = [[value].values()]
throw new InterpreterRuntimeError(`${label} contains a circular value.`, node, "InvalidDataValue") const seen = new Set<object>()
if (value === null || typeof value !== "object" || isRuntimeReference(value) || seen.has(value)) return while (pending.length > 0) {
seen.add(value) const next = pending.at(-1)!.next()
const items = Array.isArray(value) ? value : Object.values(value) if (next.done) {
for (const item of items) rejectCircularInsertion(container, item, label, node, seen) pending.pop()
seen.delete(value) continue
}
const current = next.value
if (current === container)
throw new InterpreterRuntimeError(`${label} contains a circular value.`, node, "InvalidDataValue")
if (current === null || typeof current !== "object" || isRuntimeReference(current) || seen.has(current)) continue
seen.add(current)
pending.push(Array.isArray(current) ? current[Symbol.iterator]() : childValues(current))
}
} }
export const typeofValue = (value: unknown): string => { export const typeofValue = (value: unknown): string => {