mirror of
https://github.com/morluto/rea.git
synced 2026-11-06 14:56:32 +00:00
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>
208 lines
6.8 KiB
TypeScript
208 lines
6.8 KiB
TypeScript
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"),
|
|
);
|
|
});
|