fix(codemode): linearize reference walks (#37179)
This commit is contained in:
parent
0cfa7b39db
commit
fd19fa89c4
1 changed files with 57 additions and 29 deletions
|
|
@ -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 => {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue