mirror of
https://github.com/morluto/rea.git
synced 2026-11-05 22:36:08 +00:00
fix: report provider admission failures as typed codes (#8066)
* fix: report provider admission failures as typed codes * fix(evm): keep limiter path on admission diagnostics * fix: assert WABT and CBOR admission codes * fix: keep admission details on returned failures * fix(evm): detect repeated CBOR keys in the decoder --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com>
This commit is contained in:
1 parent
3b647132c6
commit
317bf3fe23
63 files changed
+1021
-432
No files matched your search
@@ -0,0 +1,47 @@
|
||||
const WABT_ADMISSION_CODES = [
|
||||
"incomplete_string",
|
||||
"unsupported_escape",
|
||||
"incomplete_comment",
|
||||
"malformed_form",
|
||||
"name_missing",
|
||||
"incomplete_import",
|
||||
"incomplete_export",
|
||||
"unsupported_section_header",
|
||||
"unrecognized_section_row",
|
||||
"blank_custom_name",
|
||||
"custom_header_mismatch",
|
||||
"incomplete_section_row",
|
||||
"section_ranges",
|
||||
"custom_inventory_mismatch",
|
||||
"section_coverage",
|
||||
"incomplete_name_metadata",
|
||||
"name_subsection_order",
|
||||
"incomplete_module_name",
|
||||
"unfinished_module_name",
|
||||
"unsupported_module_prefix",
|
||||
"unexpected_string",
|
||||
"unexpected_second_module",
|
||||
"unsupported_custom_name",
|
||||
"unsupported_name_payload",
|
||||
"unbalanced_output",
|
||||
"unexpected_text",
|
||||
"incomplete_output",
|
||||
"directory_not_absolute",
|
||||
"incomplete_tool_profile",
|
||||
] as const;
|
||||
|
||||
export type WabtAdmissionCode = (typeof WABT_ADMISSION_CODES)[number];
|
||||
|
||||
/** WABT output or tool-profile admission failure. `code` is the stable identity. */
|
||||
export class WabtAdmissionError extends Error {
|
||||
readonly _tag = "WabtAdmissionError";
|
||||
|
||||
constructor(
|
||||
readonly code: WabtAdmissionCode,
|
||||
readonly detail?: Readonly<Record<string, string | number>>,
|
||||
options?: ErrorOptions,
|
||||
) {
|
||||
super(code, options);
|
||||
this.name = "WabtAdmissionError";
|
||||
}
|
||||
}
|
||||
@@ -436,7 +436,12 @@ it("returns actionable unavailable without creating a workspace when tools are a
|
||||
ok: false,
|
||||
error: {
|
||||
_tag: "AnalysisCapabilityUnavailableError",
|
||||
userMessage: expect.stringContaining("REA_WABT_BIN_DIRECTORY"),
|
||||
reason: "directory_not_absolute",
|
||||
userMessage: "WABT configuration cannot be used",
|
||||
diagnostics: {
|
||||
code: "directory_not_absolute",
|
||||
setting: "REA_WABT_BIN_DIRECTORY",
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
import { randomUUID, createHash } from "node:crypto";
|
||||
import { access, writeFile } from "node:fs/promises";
|
||||
import { constants } from "node:fs";
|
||||
@@ -221,9 +222,9 @@ export class WabtArtifactProvider implements WasmArtifactPort {
|
||||
});
|
||||
const directory = environment.REA_WABT_BIN_DIRECTORY ?? "";
|
||||
if (!isAbsolute(directory))
|
||||
throw new Error(
|
||||
"REA_WABT_BIN_DIRECTORY must be an absolute tool directory.",
|
||||
);
|
||||
throw new WabtAdmissionError("directory_not_absolute", {
|
||||
setting: "REA_WABT_BIN_DIRECTORY",
|
||||
});
|
||||
const commands: {
|
||||
tool: "wasm-validate" | "wasm-objdump" | "wasm2wat";
|
||||
path: string;
|
||||
@@ -466,7 +467,7 @@ export class WabtArtifactProvider implements WasmArtifactPort {
|
||||
objdump === undefined ||
|
||||
decode === undefined
|
||||
)
|
||||
throw new Error("Incomplete WABT tool profile.");
|
||||
throw new WabtAdmissionError("incomplete_tool_profile");
|
||||
const validation = await execute(validate, validate.arguments, true);
|
||||
if (validation.exitCode !== 0)
|
||||
throw new AnalysisInputError(
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
|
||||
/** Skip a WAT comment without interpreting its punctuation as forms or strings. */
|
||||
export const watCommentEnd = (
|
||||
text: string,
|
||||
@@ -24,5 +26,5 @@ export const watCommentEnd = (
|
||||
index++;
|
||||
}
|
||||
}
|
||||
throw new Error("Incomplete WABT WAT comment.");
|
||||
throw new WabtAdmissionError("incomplete_comment");
|
||||
};
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
RuntimeControlUnavailableError,
|
||||
runtimeControlCapabilityError,
|
||||
} from "../process/RuntimeControlUnavailableError.js";
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
import {
|
||||
DEFAULT_WASM_COMMAND_TIMEOUT_MS,
|
||||
MAX_WASM_COMMAND_TIMEOUT_MS,
|
||||
@@ -237,18 +238,31 @@ const classifyWabtFailure = (
|
||||
remediationAction: `The WABT tool allowance is ${String(WABT_LIMITS.toolBytes)} bytes. This release has no runtime override. command_timeout_ms and REA_WABT_BIN_DIRECTORY do not raise it. Select tools within the allowance. Do not repeat the unchanged call.`,
|
||||
},
|
||||
);
|
||||
if (phase === "configuration")
|
||||
if (phase === "configuration") {
|
||||
const admission = cause instanceof WabtAdmissionError ? cause : undefined;
|
||||
return new AnalysisCapabilityUnavailableError(
|
||||
WABT_PROVIDER_IDENTITY.id,
|
||||
"inspect_wasm_artifact",
|
||||
reason,
|
||||
admission?.code ?? reason,
|
||||
{
|
||||
cause,
|
||||
...artifactOptions,
|
||||
userMessage: reason,
|
||||
...(admission === undefined
|
||||
? {}
|
||||
: {
|
||||
diagnostics: {
|
||||
code: admission.code,
|
||||
...admission.detail,
|
||||
},
|
||||
}),
|
||||
userMessage:
|
||||
admission === undefined
|
||||
? reason
|
||||
: "WABT configuration cannot be used",
|
||||
remediationAction: wabtConfigurationRecoveryAction(cause),
|
||||
},
|
||||
);
|
||||
}
|
||||
if (
|
||||
phase === "artifact" &&
|
||||
cause instanceof ArtifactReaderFailure &&
|
||||
|
||||
@@ -217,11 +217,19 @@ it("rejects unfinished nested comments rather than projecting a truncated interf
|
||||
"(module (type (func (param (; unfinished ) i32))))",
|
||||
"(module) (; outer (; inner ;) still open",
|
||||
])
|
||||
expect(() => parseWabtWat(source)).toThrow("Incomplete WABT WAT comment");
|
||||
expect(() => parseWabtWat(source)).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "WabtAdmissionError",
|
||||
code: "incomplete_comment",
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("rejects a second module after a CR-terminated line comment", () => {
|
||||
expect(() => parseWabtWat("(module) ;; comment\r(module)")).toThrow(
|
||||
"Unexpected second WABT WAT module",
|
||||
expect.objectContaining({
|
||||
_tag: "WabtAdmissionError",
|
||||
code: "unexpected_second_module",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -1,3 +1,4 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
import { AnalysisResourceConstraintError } from "../domain/analysisErrorCore.js";
|
||||
import { wasmInterfaceEntryRepresentationBytes } from "../domain/wasm/wasmInterface.js";
|
||||
import { WasmInterfaceBudget } from "./WasmInterfaceBudget.js";
|
||||
@@ -47,7 +48,7 @@ const parts = (form: string): string[] => {
|
||||
if (items[0] === "func" && items.length > 1 && !atom.startsWith("$"))
|
||||
break;
|
||||
}
|
||||
if (i <= start) throw new Error("Malformed WABT form");
|
||||
if (i <= start) throw new WabtAdmissionError("malformed_form");
|
||||
budget.reserve(64 + (i - start) * 2);
|
||||
items.push(form.slice(start, i));
|
||||
}
|
||||
@@ -91,7 +92,7 @@ const functionType = (
|
||||
};
|
||||
const name = (text: string | undefined): string => {
|
||||
if (text?.startsWith('"') !== true)
|
||||
throw new Error("WABT interface name is missing");
|
||||
throw new WabtAdmissionError("name_missing");
|
||||
return new TextDecoder("utf8", { fatal: true, ignoreBOM: true }).decode(
|
||||
watString(text, 1).bytes,
|
||||
);
|
||||
@@ -252,7 +253,7 @@ const decodeWabtInterface = (
|
||||
const p = parts(form);
|
||||
if (p[0] !== "import") continue;
|
||||
const body = p[3];
|
||||
if (body === undefined) throw new Error("Incomplete WABT import");
|
||||
if (body === undefined) throw new WabtAdmissionError("incomplete_import");
|
||||
const observed = addDefinition(body);
|
||||
const entry: WasmInterfaceEntry = {
|
||||
direction: "import",
|
||||
@@ -280,7 +281,7 @@ const decodeWabtInterface = (
|
||||
const p = parts(form);
|
||||
if (p[0] !== "export") continue;
|
||||
const body = p[2];
|
||||
if (body === undefined) throw new Error("Incomplete WABT export");
|
||||
if (body === undefined) throw new WabtAdmissionError("incomplete_export");
|
||||
const descriptor = parts(body);
|
||||
const rawKind = descriptor[0] ?? "unknown";
|
||||
const kind = ["func", "table", "memory", "global", "tag"].includes(rawKind)
|
||||
|
||||
+25
-18
@@ -1,5 +1,8 @@
|
||||
import { readFile } from "node:fs/promises";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
const refused = (code: string) =>
|
||||
expect.objectContaining({ _tag: "WabtAdmissionError", code });
|
||||
import { parseWabtOutput } from "./WabtOutput.js";
|
||||
import { parseWabtWat } from "./WabtWat.js";
|
||||
import { associateWasmGlue } from "./WasmGlueAdapter.js";
|
||||
@@ -28,10 +31,10 @@ it("corroborates a blank custom name with bounded invalid UTF-8 bytes", () => {
|
||||
valid[11] = 0x61;
|
||||
valid[12] = 0x62;
|
||||
expect(() => parseWabtOutput(dump, valid.length, [], [], valid)).toThrow(
|
||||
/not corroborated/u,
|
||||
refused("blank_custom_name"),
|
||||
);
|
||||
expect(() => parseWabtOutput(dump, bytes.length, [])).toThrow(
|
||||
/not corroborated/u,
|
||||
refused("blank_custom_name"),
|
||||
);
|
||||
const mixed = Buffer.concat([bytes, Buffer.from("00050174616263", "hex")]);
|
||||
const mixedDump = dump.replace(
|
||||
@@ -57,19 +60,21 @@ it("retains real WABT section ranges, custom section identity and original heade
|
||||
bytes: 12,
|
||||
description: '"test"',
|
||||
});
|
||||
expect(() => parseWabtOutput(text, 75, ["test"])).toThrow(/ranges/u);
|
||||
expect(() => parseWabtOutput(text, 75, ["test"])).toThrow(
|
||||
refused("section_ranges"),
|
||||
);
|
||||
expect(() =>
|
||||
parseWabtOutput(text.replace("size=0x0000000c", "size=0x0000000d"), 76, [
|
||||
"test",
|
||||
]),
|
||||
).toThrow(/ranges/u);
|
||||
).toThrow(refused("section_ranges"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
text.replace("\nSection Details:\n", "unexpected\nSection Details:\n"),
|
||||
76,
|
||||
["test"],
|
||||
),
|
||||
).toThrow(/Unrecognized/u);
|
||||
).toThrow(refused("unrecognized_section_row"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
text.replace(
|
||||
@@ -79,7 +84,7 @@ it("retains real WABT section ranges, custom section identity and original heade
|
||||
76,
|
||||
["test"],
|
||||
),
|
||||
).toThrow(/Unrecognized/u);
|
||||
).toThrow(refused("unrecognized_section_row"));
|
||||
});
|
||||
const dataCountModuleBytes = 0x3d;
|
||||
const unpaddedDataCountRow =
|
||||
@@ -126,14 +131,14 @@ it("accepts an unpadded DataCount row and still rejects malformed output and bad
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/Unrecognized/u);
|
||||
).toThrow(refused("unrecognized_section_row"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
dataCountDump.replace(unpaddedDataCountRow, "Sections:"),
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/Unrecognized/u);
|
||||
).toThrow(refused("unrecognized_section_row"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
dataCountDump.replace(
|
||||
@@ -143,14 +148,14 @@ it("accepts an unpadded DataCount row and still rejects malformed output and bad
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/Unrecognized/u);
|
||||
).toThrow(refused("unrecognized_section_row"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
dataCountDump.replace("(size=0x00000001)", "(size=0x00000002)"),
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/ranges/u);
|
||||
).toThrow(refused("section_ranges"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
dataCountDump.replace(
|
||||
@@ -160,10 +165,10 @@ it("accepts an unpadded DataCount row and still rejects malformed output and bad
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/ranges/u);
|
||||
).toThrow(refused("section_ranges"));
|
||||
expect(() =>
|
||||
parseWabtOutput(dataCountDump, dataCountModuleBytes - 1, []),
|
||||
).toThrow(/ranges/u);
|
||||
).toThrow(refused("section_ranges"));
|
||||
expect(() =>
|
||||
parseWabtOutput(
|
||||
dataCountDump.replace(
|
||||
@@ -173,7 +178,7 @@ it("accepts an unpadded DataCount row and still rejects malformed output and bad
|
||||
dataCountModuleBytes,
|
||||
[],
|
||||
),
|
||||
).toThrow(/header/u);
|
||||
).toThrow(refused("unsupported_section_header"));
|
||||
});
|
||||
it("bounds section header bytes while accepting a five-byte size LEB", () => {
|
||||
const paddedCustomDump = [
|
||||
@@ -209,7 +214,7 @@ it("bounds section header bytes while accepting a five-byte size LEB", () => {
|
||||
"",
|
||||
].join("\n");
|
||||
expect(() => parseWabtOutput(malformed, start + 1, [""])).toThrow(
|
||||
/ranges/u,
|
||||
refused("section_ranges"),
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -225,7 +230,7 @@ it.each([
|
||||
`\n${displayed.map((name) => `module name: <${name}>\n`).join("")}\nSections:`,
|
||||
);
|
||||
expect(() => parseWabtOutput(text, 76, ["test"], decoded)).toThrow(
|
||||
/header format/u,
|
||||
refused("unsupported_section_header"),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -284,7 +289,7 @@ it("frames custom section names at their NUL-terminated objdump display", async
|
||||
description: '"test"',
|
||||
});
|
||||
expect(() => parseWabtOutput(text, 76, ["other\0test"])).toThrow(
|
||||
/custom-section header/u,
|
||||
refused("custom_header_mismatch"),
|
||||
);
|
||||
});
|
||||
it("preserves leading U+FEFF as custom-name data, not a debug-name marker", () => {
|
||||
@@ -327,9 +332,11 @@ it("extracts escaped WAT import/export forms while retaining multiline producer
|
||||
).exports,
|
||||
).toEqual(['(export "x" (func 0))']);
|
||||
expect(() => parseWabtWat('(module (export "unfinished"')).toThrow(
|
||||
/Incomplete/u,
|
||||
refused("incomplete_output"),
|
||||
);
|
||||
expect(() => parseWabtWat("(module) unexpected")).toThrow(
|
||||
refused("unexpected_text"),
|
||||
);
|
||||
expect(() => parseWabtWat("(module) unexpected")).toThrow(/Unexpected/u);
|
||||
});
|
||||
it("keeps exact local, ambiguous URL, computed and unparseable glue distinct", () => {
|
||||
const candidates = ["/app/module.wasm", "/other/module.wasm"];
|
||||
|
||||
+11
-14
@@ -1,3 +1,5 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
|
||||
/** WABT prints names with %.*s, so an embedded NUL ends their display. */
|
||||
const wabtDisplayedName = (name: string | undefined): string | undefined =>
|
||||
name?.split("\0", 1)[0];
|
||||
@@ -17,7 +19,7 @@ export const parseWabtOutput = (
|
||||
.join("");
|
||||
const prefix = `${preamble}\nSections:\n\n`;
|
||||
if (!text.startsWith(prefix))
|
||||
throw new Error("Unsupported WABT section header format.");
|
||||
throw new WabtAdmissionError("unsupported_section_header");
|
||||
let offset = prefix.length;
|
||||
let customIndex = 0;
|
||||
const sections = [];
|
||||
@@ -27,7 +29,8 @@ export const parseWabtOutput = (
|
||||
/^[ \t]*(\w+) start=0x([\da-f]+) end=0x([\da-f]+) \(size=0x([\da-f]+)\) /u.exec(
|
||||
text.slice(offset),
|
||||
);
|
||||
if (match === null) throw new Error("Unrecognized WABT section row.");
|
||||
if (match === null)
|
||||
throw new WabtAdmissionError("unrecognized_section_row");
|
||||
offset += match[0].length;
|
||||
let description: string;
|
||||
if (match[1] === "Custom" && !text.startsWith('"', offset)) {
|
||||
@@ -37,7 +40,7 @@ export const parseWabtOutput = (
|
||||
const end = Number.parseInt(match[3] ?? "", 16);
|
||||
const invalidEnd = invalidCustomNameEnd(artifact, start, end);
|
||||
if (invalidEnd === undefined)
|
||||
throw new Error("WABT blank custom-section name is not corroborated.");
|
||||
throw new WabtAdmissionError("blank_custom_name");
|
||||
invalidNameEnds.push(invalidEnd);
|
||||
description = "";
|
||||
// WABT does not terminate a blank-name row when another section follows.
|
||||
@@ -49,14 +52,12 @@ export const parseWabtOutput = (
|
||||
} else if (match[1] === "Custom") {
|
||||
const name = wabtDisplayedName(customSectionNames[customIndex++]);
|
||||
if (name === undefined || !text.startsWith(`"${name}"\n`, offset))
|
||||
throw new Error(
|
||||
"WABT custom-section header does not match decoded WAT.",
|
||||
);
|
||||
throw new WabtAdmissionError("custom_header_mismatch");
|
||||
description = `"${name}"`;
|
||||
offset += description.length + 1;
|
||||
} else {
|
||||
const end = text.indexOf("\n", offset);
|
||||
if (end < 0) throw new Error("Incomplete WABT section row.");
|
||||
if (end < 0) throw new WabtAdmissionError("incomplete_section_row");
|
||||
description = text.slice(offset, end).trim();
|
||||
offset = end + 1;
|
||||
}
|
||||
@@ -74,17 +75,13 @@ export const parseWabtOutput = (
|
||||
sectionHeaderBytes < 2 ||
|
||||
sectionHeaderBytes > 6
|
||||
)
|
||||
throw new Error("WABT section ranges do not match selected artifact.");
|
||||
throw new WabtAdmissionError("section_ranges");
|
||||
sections.push(section);
|
||||
}
|
||||
if (customIndex !== customSectionNames.length)
|
||||
throw new Error(
|
||||
"WABT custom-section inventory does not match decoded WAT.",
|
||||
);
|
||||
throw new WabtAdmissionError("custom_inventory_mismatch");
|
||||
if ((sections.at(-1)?.end ?? 8) !== artifactBytes)
|
||||
throw new Error(
|
||||
"WABT section output does not cover the selected artifact.",
|
||||
);
|
||||
throw new WabtAdmissionError("section_coverage");
|
||||
const separator = offset + 1;
|
||||
return {
|
||||
sections,
|
||||
|
||||
@@ -26,8 +26,18 @@ it("frames escaped quotes and trailing backslashes without consuming the next fo
|
||||
|
||||
it("rejects missing terminators, including a dangling escape or escaped last quote", () => {
|
||||
for (const source of ['"', '"payload', '"payload\\', '"payload\\"']) {
|
||||
expect(() => watStringEnd(source, 1)).toThrow("Incomplete WABT string");
|
||||
expect(() => watString(source, 1)).toThrow("Incomplete WABT string");
|
||||
expect(() => watStringEnd(source, 1)).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "WabtAdmissionError",
|
||||
code: "incomplete_string",
|
||||
}),
|
||||
);
|
||||
expect(() => watString(source, 1)).toThrow(
|
||||
expect.objectContaining({
|
||||
_tag: "WabtAdmissionError",
|
||||
code: "incomplete_string",
|
||||
}),
|
||||
);
|
||||
expect(() => parseWabtWat(`(module (import ${source}`)).toThrow();
|
||||
}
|
||||
});
|
||||
@@ -53,6 +63,9 @@ it("preserves quoted WAT punctuation, nested comments, escaped names and unknown
|
||||
},
|
||||
]);
|
||||
expect(() => parseWabtWat('(module (import "env" "\\q" (func)))')).toThrow(
|
||||
"Unsupported WABT name escape",
|
||||
expect.objectContaining({
|
||||
_tag: "WabtAdmissionError",
|
||||
code: "unsupported_escape",
|
||||
}),
|
||||
);
|
||||
});
|
||||
@@ -1,3 +1,5 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
|
||||
/** Locate a WAT string's exclusive end without decoding or copying its payload. */
|
||||
export const watStringEnd = (form: string, start: number): number => {
|
||||
for (
|
||||
@@ -9,7 +11,7 @@ export const watStringEnd = (form: string, start: number): number => {
|
||||
while (escapes > start && form[escapes - 1] === "\\") escapes--;
|
||||
if ((end - escapes) % 2 === 0) return end + 1;
|
||||
}
|
||||
throw new Error("Incomplete WABT string.");
|
||||
throw new WabtAdmissionError("incomplete_string");
|
||||
};
|
||||
|
||||
/** Decode a producer WAT string only where its bytes are interpreted. */
|
||||
@@ -45,7 +47,8 @@ export const watString = (form: string, start: number) => {
|
||||
: escape === "\\"
|
||||
? 92
|
||||
: undefined;
|
||||
if (value === undefined) throw new Error("Unsupported WABT name escape.");
|
||||
if (value === undefined)
|
||||
throw new WabtAdmissionError("unsupported_escape");
|
||||
bytes[length++] = value;
|
||||
index += 2;
|
||||
}
|
||||
|
||||
+14
-14
@@ -1,3 +1,4 @@
|
||||
import { WabtAdmissionError } from "./WabtAdmissionError.js";
|
||||
import { parseWabtInterface } from "./WabtInterface.js";
|
||||
import { watString, watStringEnd } from "./WabtString.js";
|
||||
import { watCommentEnd } from "./WabtComment.js";
|
||||
@@ -25,24 +26,24 @@ const moduleNames = (
|
||||
value += (byte & 127) * 2 ** shift;
|
||||
if (byte < 128) return value;
|
||||
}
|
||||
throw new Error("Incomplete WABT name-section metadata.");
|
||||
throw new WabtAdmissionError("incomplete_name_metadata");
|
||||
};
|
||||
try {
|
||||
let previousType = -1;
|
||||
while (offset < bytes.length) {
|
||||
const type = uint32(bytes.length);
|
||||
if (type <= previousType)
|
||||
throw new Error("Duplicate or out-of-order WABT name subsection.");
|
||||
throw new WabtAdmissionError("name_subsection_order");
|
||||
previousType = type;
|
||||
const size = uint32(bytes.length);
|
||||
const end = offset + size;
|
||||
if (end > bytes.length)
|
||||
throw new Error("Incomplete WABT name-section metadata.");
|
||||
throw new WabtAdmissionError("incomplete_name_metadata");
|
||||
if (type === 0 && size !== 0) {
|
||||
const length = uint32(end);
|
||||
const nameEnd = offset + length;
|
||||
if (nameEnd > end)
|
||||
throw new Error("Incomplete WABT module-name metadata.");
|
||||
throw new WabtAdmissionError("incomplete_module_name");
|
||||
names.push(
|
||||
new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(
|
||||
bytes.subarray(offset, nameEnd),
|
||||
@@ -50,7 +51,7 @@ const moduleNames = (
|
||||
);
|
||||
// WABT calls OnModuleName before rejecting trailing subsection bytes.
|
||||
if (nameEnd !== end)
|
||||
throw new Error("Unfinished WABT module-name subsection.");
|
||||
throw new WabtAdmissionError("unfinished_module_name");
|
||||
}
|
||||
offset = end;
|
||||
}
|
||||
@@ -68,7 +69,7 @@ const moduleNames = (
|
||||
/** Extract exact top-level WABT WAT forms; no raw WASM decoding or import/export name unescaping. */
|
||||
export const parseWabtWat = (text: string) => {
|
||||
if (!/^\(module(?:\s|\))/u.test(text))
|
||||
throw new Error("Unsupported WABT WAT module prefix.");
|
||||
throw new WabtAdmissionError("unsupported_module_prefix");
|
||||
const forms: string[] = [];
|
||||
const imports: string[] = [];
|
||||
const exports: string[] = [];
|
||||
@@ -95,13 +96,12 @@ export const parseWabtWat = (text: string) => {
|
||||
continue;
|
||||
}
|
||||
if (char === '"') {
|
||||
if (depth === 0)
|
||||
throw new Error("Unexpected string outside WABT WAT module.");
|
||||
if (depth === 0) throw new WabtAdmissionError("unexpected_string");
|
||||
index = watStringEnd(text, index + 1) - 1;
|
||||
continue;
|
||||
}
|
||||
if (char === "(") {
|
||||
if (closed) throw new Error("Unexpected second WABT WAT module.");
|
||||
if (closed) throw new WabtAdmissionError("unexpected_second_module");
|
||||
depth++;
|
||||
if (depth === 2) start = index;
|
||||
} else if (char === ")") {
|
||||
@@ -118,7 +118,7 @@ export const parseWabtWat = (text: string) => {
|
||||
else if (/^\(@custom\s/u.test(form)) {
|
||||
const prefix = /^\(@custom\s+"/u.exec(form);
|
||||
if (prefix === null)
|
||||
throw new Error("Unsupported WABT custom-section name.");
|
||||
throw new WabtAdmissionError("unsupported_custom_name");
|
||||
const nameString = watString(form, prefix[0].length);
|
||||
// A WASM name is length-delimited UTF-8; leading U+FEFF is name data.
|
||||
const name = new TextDecoder("utf-8", {
|
||||
@@ -129,7 +129,7 @@ export const parseWabtWat = (text: string) => {
|
||||
if (name === "name") {
|
||||
const payloadPrefix = /^\s+"/u.exec(form.slice(nameString.end));
|
||||
if (payloadPrefix === null)
|
||||
throw new Error("Unsupported WABT name-section payload.");
|
||||
throw new WabtAdmissionError("unsupported_name_payload");
|
||||
const payload = watString(
|
||||
form,
|
||||
nameString.end + payloadPrefix[0].length,
|
||||
@@ -148,12 +148,12 @@ export const parseWabtWat = (text: string) => {
|
||||
}
|
||||
}
|
||||
}
|
||||
if (--depth < 0) throw new Error("Unbalanced WABT WAT output.");
|
||||
if (--depth < 0) throw new WabtAdmissionError("unbalanced_output");
|
||||
if (depth === 0) closed = true;
|
||||
} else if (depth === 0 && char !== undefined && !/\s/u.test(char))
|
||||
throw new Error("Unexpected text outside WABT WAT module.");
|
||||
throw new WabtAdmissionError("unexpected_text");
|
||||
}
|
||||
if (depth !== 0) throw new Error("Incomplete WABT WAT output.");
|
||||
if (depth !== 0) throw new WabtAdmissionError("incomplete_output");
|
||||
return {
|
||||
imports,
|
||||
exports,
|
||||
|
||||
Reference in new issue
Block a user