mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
25f0f9934d
A write below a slot that stores a value read from the same slot, such as `s.c = s.c.p; s.c.i = 1` on a parameter, revisits the binding with one more segment after the stored key on every turn. The cycle closure only recognized segments added at the front of the path (`node = node[key]`), so the work-list never terminated and exhausted the owned worker heap (CodeMirror 5 `mode/stylus/stylus.js`). Recognize a contiguous segment inserted after a stable base, check that repeating it after that base can never be pruned, and escape through it once, as the self-alias rule already does. Front insertions keep their existing order and result. Fixes #7130 Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.1 KiB
TypeScript
62 lines
2.1 KiB
TypeScript
import { writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { expect } from "vitest";
|
|
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
// CodeMirror 5 modes keep a parser context stack on a state parameter. Popping
|
|
// it stores a value read from the same slot, so each call can select one more
|
|
// `prev` below the slot's original value. An unclosed member-path cycle there
|
|
// exhausted the owned worker heap on `mode/stylus/stylus.js`.
|
|
const contextStack = `
|
|
function Context(type, prev) {
|
|
this.type = type;
|
|
this.prev = prev;
|
|
this.line = { firstWord: "", indent: 0 };
|
|
}
|
|
function pushContext(state, type) {
|
|
state.context = new Context(type, state.context);
|
|
return type;
|
|
}
|
|
function popContext(state, currentIndent) {
|
|
var contextIndent = state.context.indent - 2;
|
|
state.context = state.context.prev;
|
|
if (currentIndent) state.context.indent = contextIndent;
|
|
return state.context.type;
|
|
}
|
|
function tokenBase(stream, state) {
|
|
state.context.line.firstWord = stream.current();
|
|
state.context.line.indent = stream.indentation();
|
|
}
|
|
module.exports = { pushContext, popContext, tokenBase };
|
|
`;
|
|
|
|
cliTest(
|
|
"analyzes a parser context stack that pops through its own slot",
|
|
async ({ cli }) => {
|
|
const root = await createTestTempDirectory("rea-js-self-store-");
|
|
await writeFile(join(root, "mode.js"), contextStack);
|
|
const output = await cli.run({
|
|
arguments: [
|
|
"analyze-javascript-application",
|
|
root,
|
|
"--max-heap-mb",
|
|
"256",
|
|
"--json",
|
|
],
|
|
});
|
|
expect(output.exitCode).toBe(0);
|
|
const result = javascriptApplicationAnalysisResultSchema.parse(
|
|
parseEvidence(output.json).normalized_result,
|
|
);
|
|
expect(result.statistics).toMatchObject({
|
|
parsed_javascript_files: 1,
|
|
skipped_javascript_files: 0,
|
|
parse_failures: 0,
|
|
});
|
|
},
|
|
);
|