chore(experiment): refresh main before cross-function trials

Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
This commit is contained in:
committed 2026-10-16 23:26:03 +00:00
commit c8d8319367
4 files changed
+83 -20

No files matched your search

@@ -92,3 +92,33 @@ it("escapes constructed fields when a getter query widens its suffix", () => {
expect(ir.coverage.resourceLimits).toEqual(["getter-expansion"]);
expectRetainedFacts(ir);
});
it.each(["class", "object"] as const)(
"bounds branching local aliases inside one %s getter",
(receiver) => {
const aliases = Array.from(
{ length: 10 },
(_, index) =>
`const a${index + 1} = flag ? a${index}.left : a${index}.right;`,
).join("\n");
const getter = `get state() { const a0 = this; ${aliases} return a10; }`;
const definition =
receiver === "class"
? `class Request { slot = shared; ${getter} } const request = new Request();`
: `const request = { slot: shared, ${getter} };`;
const ir = analyzeJavaScriptSemantics(`
import imported from "dep";
const shared = { value: 1 };
${definition}
consume(request.state);
const result = shared.value;
const unrelated = 42;
`);
expect(ir.coverage).toMatchObject({
status: "partial",
resourceLimits: ["getter-expansion"],
});
expect(onlyBinding(ir, "result").value.status).toBe("unknown");
expectRetainedFacts(ir);
},
);
@@ -64,6 +64,21 @@ export class SemanticGetterQuery {
constructor(readonly state: JavaScriptSemanticAnalysisState) {}
get widening(): boolean {
return this.#fallback;
}
/** Charge retained local work and getter jobs to the same precise query. */
reserveResult(): boolean {
if (this.#limited) return false;
if (!this.#fallback && this.#visits >= SEMANTIC_GETTER_EXPANSION_LIMIT) {
this.#limited = true;
return false;
}
this.#visits++;
return true;
}
retain(reference: Reference, escapes = false): void {
// Direct fields keep their write suffix; only getter jobs widen omitted paths.
this.#references.add({
@@ -85,11 +100,7 @@ export class SemanticGetterQuery {
const paths = receivers?.get(receiver);
const key = JSON.stringify([path, escapes]);
if (paths?.has(key)) return true;
if (!this.#fallback && this.#visits >= SEMANTIC_GETTER_EXPANSION_LIMIT) {
this.#limited = true;
return true;
}
this.#visits++;
if (!this.reserveResult()) return true;
const selections = paths ?? new Set<string>();
const owners = receivers ?? new WeakMap<t.Node, Set<string>>();
selections.add(key);
@@ -75,7 +75,7 @@ const resultBranches = (node: t.Node): readonly t.Node[] | undefined => {
/** Each walk owns local aliases; getter-to-getter traversal belongs to the query worklist. */
class GetterResultWalker {
readonly #pending: GetterWork[];
readonly #pending: GetterWork[] = [];
readonly #aliases = new Map<string, Set<string>>();
readonly #scope: string | undefined;
@@ -84,9 +84,10 @@ class GetterResultWalker {
readonly query: SemanticGetterQuery,
readonly resolvers: GetterResultResolvers,
) {
this.#pending = semanticCallableResultExpressions(
for (const { node } of semanticCallableResultExpressions(
job.invocation.getter,
).flatMap(({ node }) => (node === null ? [] : [{ node, path: job.path }]));
))
if (node !== null) this.#enqueue(() => ({ node, path: job.path }));
this.#scope = query.state.scopeByNode.get(job.invocation.getter)?.scopeId;
}
@@ -97,6 +98,25 @@ class GetterResultWalker {
}
}
#enqueue(create: () => GetterWork): void {
if (!this.query.reserveResult()) return;
const work = create();
// The fallback escapes receiver roots. Keeping local suffix permutations
// here would repeat the precise expansion inside a single getter.
this.#pending.push(
this.query.widening
? {
...work,
path: [],
escapes: true,
...(work.privateRead === undefined
? {}
: { privateRead: { ...work.privateRead, path: [] } }),
}
: work,
);
}
#retain(current: GetterWork, reference: Reference): void {
this.query.retain(reference, current.escapes || this.job.escapes);
}
@@ -149,12 +169,12 @@ class GetterResultWalker {
if (previous !== undefined) {
// Growing aliases can reach arbitrarily deep children of the same roots.
if (current.path.length > previous.pathLength && !current.escapes)
this.#pending.push({
this.#enqueue(() => ({
...current,
path: [],
aliases: undefined,
escapes: true,
});
}));
return true;
}
const key = JSON.stringify([
@@ -195,12 +215,12 @@ class GetterResultWalker {
(origin) => !semanticReferenceSourcesInactive(origin, state),
),
])
this.#pending.push({
this.#enqueue(() => ({
...current,
node: initializer.node,
path: [...initializer.projection, ...current.path],
aliases,
});
}));
}
#ownsThis(expression: t.ThisExpression): boolean {
@@ -305,27 +325,28 @@ class GetterResultWalker {
t.isPrivateName(expression.property) &&
t.isClass(this.job.invocation.owner)
) {
this.#pending.push({
const name = expression.property.id.name;
this.#enqueue(() => ({
...current,
node: expression.object,
path: [],
privateRead: {
node: expression,
name: expression.property.id.name,
name,
path: current.path,
},
});
}));
return;
}
if (!this.#superValue(current, expression))
this.#pending.push({
this.#enqueue(() => ({
...current,
node: expression.object,
path: [
semanticStaticPropertyKey(expression.property, expression.computed),
...current.path,
],
});
}));
}
#visit(current: GetterWork): void {
@@ -340,7 +361,8 @@ class GetterResultWalker {
else {
const branches = resultBranches(expression);
if (branches !== undefined)
for (const node of branches) this.#pending.push({ ...current, node });
for (const node of branches)
this.#enqueue(() => ({ ...current, node }));
else if (current.privateRead !== undefined) this.#privateRead(current);
else this.#retain(current, { node: expression, path: current.path });
}
@@ -7,7 +7,7 @@ import type {
export const SEMANTIC_PRIMITIVE_CANDIDATE_LIMIT = 256;
/** Maximum active expression nesting during inert semantic evaluation. */
export const SEMANTIC_EXPRESSION_DEPTH_LIMIT = 256;
/** Distinct getter, receiver and path states retained in one precise query. */
/** Getter jobs and local result work retained in one precise query. */
export const SEMANTIC_GETTER_EXPANSION_LIMIT = 512;
/** Bound normalized strings and transient keys independently of transport limits. */
export const SEMANTIC_PRIMITIVE_JSON_BYTES_LIMIT = 1_048_576;
@@ -20,7 +20,7 @@ const RESOURCE_LIMIT_REASON: Record<JavaScriptSemanticResourceLimit, string> = {
"expression-depth":
"Semantic expression depth budget exceeded (maximum 256 nested expressions).",
"getter-expansion":
"Getter expansion was conservatively bounded (maximum 512 distinct states or a growing receiver cycle).",
"Getter expansion was conservatively bounded (maximum 512 scheduled states or a growing receiver cycle).",
};
/** Check UTF-16 length against worst-case JSON escaping without flattening. */