mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
fix(javascript): converge cyclic aliases with explicit projection widening
Replace initializer-history and full-path enumeration with a finite projection join at each source read. Retain conservative local returned-reference effects and disclose precision loss through static, semantic and captured-web results. Close full-binding callback cycles while preserving narrowed read semantics. Fixes #4348 Fixes #5138 Refs #4349 Refs #4364 Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: Fuxuan-CN <3072252442@qq.com>
This commit is contained in:
1 parent
9af0b6113c
commit
e4c3d72396
19 files changed
+791
-166
No files matched your search
@@ -0,0 +1,207 @@
|
||||
import * as t from "@babel/types";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { indexJavaScriptBindingsSteps } from "./javascriptSemanticAnalysis.js";
|
||||
import { parseJavaScriptSource } from "./javascriptSourceParser.js";
|
||||
import { createSemanticReturnedReferences } from "./javascriptSemanticReturnedReferences.js";
|
||||
import { completeSemanticSteps } from "./javascriptSemanticTraversal.js";
|
||||
|
||||
const indexed = (source: string) => {
|
||||
const parsed = parseJavaScriptSource(
|
||||
source,
|
||||
"returned-reference-projection.js",
|
||||
);
|
||||
if (parsed === null) throw new Error("Expected valid fixture");
|
||||
const state = completeSemanticSteps(
|
||||
indexJavaScriptBindingsSteps(parsed.program),
|
||||
);
|
||||
return { parsed, state };
|
||||
};
|
||||
|
||||
const callReads = (program: t.Program, name: string): t.Identifier[] => {
|
||||
const reads: t.Identifier[] = [];
|
||||
t.traverseFast(program, (node) => {
|
||||
if (t.isCallExpression(node) && t.isIdentifier(node.callee, { name }))
|
||||
reads.push(node.callee);
|
||||
});
|
||||
return reads;
|
||||
};
|
||||
|
||||
const callCallees = (program: t.Program, sourceName: string): t.Node[] => {
|
||||
const callees: t.Node[] = [];
|
||||
t.traverseFast(program, (node) => {
|
||||
if (!t.isCallExpression(node)) return;
|
||||
let root = node.callee;
|
||||
while (t.isMemberExpression(root)) root = root.object;
|
||||
if (t.isIdentifier(root, { name: sourceName })) callees.push(node.callee);
|
||||
});
|
||||
return callees;
|
||||
};
|
||||
|
||||
const returnedExpression = (program: t.Program, name: string): t.Node => {
|
||||
let found: t.Node | undefined;
|
||||
t.traverseFast(program, (node) => {
|
||||
if (
|
||||
found === undefined &&
|
||||
t.isFunctionDeclaration(node) &&
|
||||
node.id?.name === name
|
||||
)
|
||||
t.traverseFast(node.body, (child) => {
|
||||
if (
|
||||
found === undefined &&
|
||||
t.isReturnStatement(child) &&
|
||||
child.argument !== null
|
||||
)
|
||||
found = child.argument;
|
||||
});
|
||||
});
|
||||
if (found === undefined)
|
||||
throw new Error(`Missing return expression in ${name}`);
|
||||
return found;
|
||||
};
|
||||
|
||||
const resultIdentifier = (program: t.Program, name: string): t.Identifier => {
|
||||
let found: t.Identifier | undefined;
|
||||
t.traverseFast(program, (node) => {
|
||||
if (
|
||||
found === undefined &&
|
||||
((t.isReturnStatement(node) && node.argument !== null) ||
|
||||
(t.isYieldExpression(node) && node.argument !== null))
|
||||
)
|
||||
t.traverseFast(node.argument, (child) => {
|
||||
if (found === undefined && t.isIdentifier(child, { name }))
|
||||
found = child;
|
||||
});
|
||||
});
|
||||
if (found === undefined) throw new Error(`Missing result expression ${name}`);
|
||||
return found;
|
||||
};
|
||||
|
||||
it("keeps returned-reference targets at the source read position", () => {
|
||||
const { parsed, state } = indexed(`
|
||||
function beforeTarget() { return beforeResult; }
|
||||
function afterTarget() { return afterResult; }
|
||||
function choose() {
|
||||
let callback = beforeTarget;
|
||||
callback();
|
||||
callback = afterTarget;
|
||||
callback();
|
||||
}
|
||||
`);
|
||||
const [beforeRead, afterRead] = callReads(parsed.program, "callback");
|
||||
if (beforeRead === undefined || afterRead === undefined)
|
||||
throw new Error("Missing callback reads");
|
||||
const returnedReferences = createSemanticReturnedReferences(state);
|
||||
|
||||
const before = returnedReferences(beforeRead);
|
||||
const after = returnedReferences(afterRead);
|
||||
|
||||
expect(before.precision).toBe("exact");
|
||||
expect(before.references.map(({ node }) => node)).toEqual([
|
||||
returnedExpression(parsed.program, "beforeTarget"),
|
||||
]);
|
||||
expect(after.precision).toBe("exact");
|
||||
expect(after.references.map(({ node }) => node)).toEqual([
|
||||
returnedExpression(parsed.program, "afterTarget"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("resolves a finite object and array projection exactly", () => {
|
||||
const { parsed, state } = indexed(`
|
||||
function target() { return targetResult; }
|
||||
const box = { nested: [target] };
|
||||
const callback = box.nested[0];
|
||||
callback();
|
||||
`);
|
||||
const [read] = callReads(parsed.program, "callback");
|
||||
if (read === undefined) throw new Error("Missing callback read");
|
||||
|
||||
const result = createSemanticReturnedReferences(state)(read);
|
||||
|
||||
expect(result.precision).toBe("exact");
|
||||
expect(result.references.map(({ node }) => node)).toEqual([
|
||||
returnedExpression(parsed.program, "target"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("reports conservative widening for a shrinking projection cycle", () => {
|
||||
const { parsed, state } = indexed(`
|
||||
function target() { return targetResult; }
|
||||
let cursor = { target };
|
||||
while (flag) cursor = { next: cursor, target };
|
||||
cursor.next.next.target();
|
||||
`);
|
||||
const [callee] = callCallees(parsed.program, "cursor");
|
||||
if (callee === undefined) throw new Error("Missing projected call");
|
||||
|
||||
const result = createSemanticReturnedReferences(state)(callee);
|
||||
|
||||
expect(result.precision).toBe("widened");
|
||||
expect(result.references.map(({ node }) => node)).toEqual([
|
||||
returnedExpression(parsed.program, "target"),
|
||||
]);
|
||||
});
|
||||
|
||||
it("widens a growing projection while retaining zero-depth and nested targets", () => {
|
||||
const { parsed, state } = indexed(`
|
||||
function zeroDepthTarget() { return zeroDepthResult; }
|
||||
function objectTarget() { return objectResult; }
|
||||
function arrayTarget() { return arrayResult; }
|
||||
function* generatorTarget() { yield* generatorResult; }
|
||||
const shape = {
|
||||
zero: zeroDepthTarget,
|
||||
nested: {
|
||||
object: objectTarget,
|
||||
array: [arrayTarget],
|
||||
class: class NestedTarget { method() { return classResult; } },
|
||||
generator: generatorTarget,
|
||||
},
|
||||
};
|
||||
let cursor = shape;
|
||||
function walk(flag) {
|
||||
while (flag) cursor = cursor.next;
|
||||
return cursor();
|
||||
}
|
||||
walk(true);
|
||||
`);
|
||||
const [read] = callReads(parsed.program, "cursor");
|
||||
if (read === undefined) throw new Error("Missing cursor read");
|
||||
|
||||
const result = createSemanticReturnedReferences(state)(read);
|
||||
const expressions = result.references.map(({ node }) => node);
|
||||
|
||||
expect(result.precision).toBe("widened");
|
||||
for (const name of ["zeroDepthTarget", "objectTarget", "arrayTarget"])
|
||||
expect(expressions).toContain(returnedExpression(parsed.program, name));
|
||||
expect(expressions).toContain(
|
||||
resultIdentifier(parsed.program, "classResult"),
|
||||
);
|
||||
const generatorResult = resultIdentifier(parsed.program, "generatorResult");
|
||||
expect(result.references).toContainEqual({
|
||||
node: generatorResult,
|
||||
projection: [null],
|
||||
});
|
||||
});
|
||||
|
||||
it("widens a cycle that rewrites the path head while increasing its depth", () => {
|
||||
const { parsed, state } = indexed(`
|
||||
function target() { return targetResult; }
|
||||
let value = {};
|
||||
while (flag)
|
||||
value = {
|
||||
a: value.x.y,
|
||||
x: value.a.z,
|
||||
target,
|
||||
};
|
||||
value.a.b();
|
||||
`);
|
||||
const [callee] = callCallees(parsed.program, "value");
|
||||
if (callee === undefined) throw new Error("Missing projected call");
|
||||
|
||||
const result = createSemanticReturnedReferences(state)(callee);
|
||||
|
||||
expect(result.precision).toBe("widened");
|
||||
expect(result.references.map(({ node }) => node)).toContain(
|
||||
returnedExpression(parsed.program, "target"),
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user