fix(wasm): retain glue query and fragment across line terminators (#6802)

fix(wasm): retain multiline glue query and fragment literals

Co-authored-by: gongfpp <22683417+gongfpp@users.noreply.github.com>
Co-authored-by: gensokyo-dev <342580529+gensokyo-dev@users.noreply.github.com>
This commit is contained in:
gongfpp
2026-10-24 05:00:12 +08:00
committed by GitHub
parent 57b1f82367
commit d65c027c2d
3 changed files with 90 additions and 26 deletions
+38 -12
View File
@@ -227,8 +227,23 @@ try {
const candidate = join(candidateRoot, "module.wasm");
await writeFile(join(candidateRoot, "source.wat"), "(module (memory 2))");
await tool("wat2wasm", ["source.wat", "-o", "module.wasm"], candidateRoot);
const glueSource =
'fetch("./module.wasm"); new URL("https://example.invalid/module.wasm?version=1", import.meta.url);';
const glueValues = [
"./module.wasm",
"https://example.invalid/module.wasm?version=1",
...["\n", "\r", "\r\n", "\u2028", "\u2029"].flatMap((separator) => [
`https://example.invalid/module.wasm?query=a${separator}b`,
`https://example.invalid/module.wasm#fragment=a${separator}b`,
]),
];
const glueLiterals = glueValues.map((value) =>
JSON.stringify(value)
.replace(/\u2028/gu, "\\u2028")
.replace(/\u2029/gu, "\\u2029"),
);
const glueSource = [
`fetch(${glueLiterals[0]}); new URL(${glueLiterals[1]}, import.meta.url);`,
...glueLiterals.slice(2).map((literal) => `fetch(${literal});`),
].join("\n");
const glueSources = [
glueSource,
`\uFEFF${glueSource}`,
@@ -283,21 +298,32 @@ try {
assert.equal(observed.artifact.bytes, Buffer.byteLength(source));
assert.equal(await readFile(gluePaths[index], "utf8"), source);
assert.equal(observed.parse_status, "complete");
assert.deepEqual(
observed.references.map(({ value }) => value),
glueValues,
);
assert.deepEqual(
observed.references.map(({ candidate_paths }) => candidate_paths),
[[path], [path, candidate]],
glueValues.map((_, index) =>
index === 0 ? [path] : [path, candidate],
),
);
assert.deepEqual(
observed.references.map(({ association }) => association),
glueValues.map((_, index) =>
index === 0 ? "local-path-candidate" : "basename-candidates",
),
);
assert.deepEqual(
observed.references.map(({ line, column }) => ({ line, column })),
["./module.wasm", "https://example.invalid/module.wasm?version=1"].map(
(value) => {
const offset = source.indexOf(`"${value}"`);
return {
line: source.slice(0, offset).split("\n").length,
column: offset - source.lastIndexOf("\n", offset) - 1,
};
},
),
glueLiterals.map((literal) => {
const offset = source.indexOf(literal);
assert.notEqual(offset, -1);
return {
line: source.slice(0, offset).split("\n").length,
column: offset - source.lastIndexOf("\n", offset) - 1,
};
}),
);
}
assert.notEqual(report.candidates[0].sha256, report.candidates[1].sha256);
+48 -1
View File
@@ -421,11 +421,58 @@ it("matches a CLI-selected ./ module exactly against its glue reference", () =>
]);
});
it.each(["\n", "\r", "\r\n", "\u2028", "\u2029"])(
"retains complete .wasm query and fragment literals containing %j",
(separator) => {
const candidates = ["/artifacts/module.wasm", "/other/module.wasm"];
for (const qualifier of ["?query=", "#fragment="]) {
const value = `https://example.invalid/module.wasm${qualifier}a${separator}b`;
const result = associateWasmGlue(
`fetch(${JSON.stringify(value)});`,
"/artifacts/glue.js",
candidates,
);
expect(result).toEqual({
parse_status: "complete",
references: [
{
value,
line: 1,
column: 6,
candidate_paths: candidates,
association: "basename-candidates",
evidence_kind: "static-literal",
},
],
});
}
},
);
it.each(["\n", "\r", "\r\n", "\u2028", "\u2029"])(
"does not accept a path-ending separator without a query or fragment: %j",
(separator) => {
const value = `https://example.invalid/module.wasm${separator}`;
expect(
associateWasmGlue(`fetch(${JSON.stringify(value)});`, "/glue.js", []),
).toEqual({ parse_status: "complete", references: [] });
},
);
it("tests long multi-line glue literals for a .wasm suffix in linear time", () => {
const value = `${".wasm#".repeat(32_000)}\nend`;
const started = performance.now();
const result = associateWasmGlue(
`const s = ${JSON.stringify(value)};`,
"/glue.js",
[],
);
expect(result.parse_status).toBe("complete");
expect(result.references).toHaveLength(1);
expect(result.references[0]?.value).toBe(value);
const negative = `${".wasm!".repeat(32_000)}\nend`;
expect(
associateWasmGlue(`const s = ${JSON.stringify(value)};`, "/glue.js", []),
associateWasmGlue(`const s = ${JSON.stringify(negative)};`, "/glue.js", []),
).toEqual({ parse_status: "complete", references: [] });
expect(performance.now() - started).toBeLessThan(1_000);
expect(
+4 -13
View File
@@ -5,21 +5,12 @@ import { traverseJavaScriptAst } from "../domain/javascript/javascriptSemanticTr
import { absoluteCallerPath } from "../filesystem/callerPath.js";
/**
* `.` cannot cross a line terminator and `$` only matches at the end of the
* literal, so a `.wasm` suffix can only occur on its last line. Testing that
* line alone keeps long multi-line literals linear.
* Query/fragment content may contain line terminators. With dotAll, the first
* `.wasm?` or `.wasm#` consumes the rest without retrying overlapping suffixes;
* absent that qualifier, each candidate requires only a fixed suffix check.
*/
const endsWithWasmReference = (value: string): boolean =>
/\.wasm(?:[?#].*)?$/iu.test(
value.slice(
Math.max(
value.lastIndexOf("\n"),
value.lastIndexOf("\r"),
value.lastIndexOf("
"),
value.lastIndexOf("
"),
) + 1,
),
);
/\.wasm(?:[?#].*)?$/isu.test(value);
/** Associate visible literals with selected candidates without asserting data flow or URL identity. */
export const associateWasmGlue = (