~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs

0
0
main
58 lines · 4.3 KB Raw History
import assert from "node:assert/strict";
import { existsSync, mkdtempSync, readdirSync, readFileSync, rmSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { test } from "node:test";

import { createNodeContext } from "../server/index.js";
import { capMemory } from "./index.js";

// exports memory (min pages, no maximum, as rustc emits), alloc, and grow: memory.grow(2000), then
// {"grew":false} when that returned -1, else {"grew":true}. The same bytes are in rust/host/src/wasm.rs.
const grow = (min) =>
	Uint8Array.from([
		0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x0c, 0x02, 0x60, 0x01, 0x7f, 0x01, 0x7f, 0x60, 0x02, 0x7f, 0x7f, 0x01, 0x7e, 0x03, 0x03, 0x02, 0x00, 0x01,
		...(min === 1 ? [0x05, 0x03, 0x01, 0x00, 0x01] : [0x05, 0x04, 0x01, 0x00, 0xd0, 0x0f]),
		0x07, 0x19, 0x03, 0x06, 0x6d, 0x65, 0x6d, 0x6f, 0x72, 0x79, 0x02, 0x00, 0x05, 0x61, 0x6c, 0x6c, 0x6f, 0x63, 0x00, 0x00, 0x04, 0x67, 0x72, 0x6f, 0x77, 0x00, 0x01,
		0x0a, 0x1f, 0x02, 0x05, 0x00, 0x41, 0xc0, 0x00, 0x0b, 0x17, 0x00, 0x41, 0xd0, 0x0f, 0x40, 0x00, 0x41, 0x7f, 0x46, 0x04, 0x7e, 0x42, 0x0e, 0x05, 0x42, 0x8d, 0x80, 0x80, 0x80, 0x80, 0x02, 0x0b, 0x0b,
		0x0b, 0x23, 0x01, 0x00, 0x41, 0x00, 0x0b, 0x1d, 0x7b, 0x22, 0x67, 0x72, 0x65, 0x77, 0x22, 0x3a, 0x66, 0x61, 0x6c, 0x73, 0x65, 0x7d, 0x00, 0x00, 0x7b, 0x22, 0x67, 0x72, 0x65, 0x77, 0x22, 0x3a, 0x74, 0x72, 0x75, 0x65, 0x7d,
	]);

test("capMemory: maximum set to the cap (or kept when lower), a minimum over it refused", () => {
	const limits = (bytes) => new WebAssembly.Instance(new WebAssembly.Module(bytes)).exports.memory;
	assert.equal(new WebAssembly.Instance(new WebAssembly.Module(grow(1))).exports.grow(0, 0), (16n << 32n) | 13n, "uncapped, V8 grows past 1024 pages");
	const capped = capMemory(grow(1), 1024);
	assert.equal(capped.length, grow(1).length + 2);
	assert.equal(limits(capped).grow(1023), 1);
	assert.throws(() => limits(capped).grow(1024), RangeError);
	assert.equal(capMemory(capMemory(grow(1), 8), 1024)[31], 0x01, "flags: has max");
	assert.throws(() => limits(capMemory(capMemory(grow(1), 8), 1024)).grow(8), RangeError, "a lower maximum is kept");
	assert.throws(() => capMemory(grow(2000), 1024), { code: "WASM_TOO_LARGE" });
	const twoMemories = Uint8Array.from([...grow(1).subarray(0, 27), 0x05, 0x05, 0x02, 0x00, 0x01, 0x00, 0x01, ...grow(1).subarray(32)]);
	assert.throws(() => capMemory(twoMemories, 1024), { code: "WASM_ABI_INVALID", message: "WASM_ABI_INVALID: a module may declare at most one memory" });
	const memory64 = grow(1);
	memory64[30] = 0x04;
	assert.throws(() => capMemory(memory64, 1024), { code: "WASM_ABI_INVALID", message: "WASM_ABI_INVALID: memory64 is not supported" });
	const noMemory = Uint8Array.from([0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00]);
	assert.equal(capMemory(noMemory, 1024), noMemory);
});

test("wasm.run: memory.grow past the cap fails inside the call; rustc-built modules still load", async () => {
	const dir = mkdtempSync(join(tmpdir(), "wasm-cap-"));
	const ctx = createNodeContext({ id: "t", stateDir: dir });
	try {
		assert.deepEqual(await ctx.wasm.run({ module: grow(1), entry: "grow", input: {} }), { grew: false });
		await assert.rejects(ctx.wasm.run({ module: grow(2000), entry: "grow", input: {} }), { code: "WASM_TOO_LARGE", message: "WASM_TOO_LARGE: memory exceeds 1024 pages" });
		// input past 1 MiB: refused by default, let through when the call's ioBytes is larger (it then traps: one page of memory)
		const big = { x: "x".repeat(1 << 20) };
		await assert.rejects(ctx.wasm.run({ module: grow(1), entry: "grow", input: big }), { message: `WASM_TOO_LARGE: input exceeds ${1 << 20} bytes` });
		await assert.rejects(ctx.wasm.run({ module: grow(1), entry: "grow", input: big, ioBytes: 2 << 20 }), { code: "WASM_TRAP" });
		await assert.rejects(ctx.wasm.run({ module: grow(1), entry: "grow", input: { x: "x".repeat(17 << 20) }, ioBytes: 1 << 30 }), { message: `WASM_TOO_LARGE: input exceeds ${16 << 20} bytes` });
		const release = new URL("../../../target/wasm32-unknown-unknown/release/", import.meta.url);
		const built = existsSync(release) ? readdirSync(release).filter((f) => f.endsWith(".wasm")) : [];
		for (const file of built) await WebAssembly.compile(capMemory(readFileSync(new URL(file, release)), 1024));
	} finally {
		ctx.close();
		rmSync(dir, { recursive: true, force: true });
	}
});