83 lines
3.9 KiB
TypeScript
83 lines
3.9 KiB
TypeScript
/**
|
|||
|
|
* Regression coverage for the `LinearFifo::ordered_remove_item` wrapped-buffer
|
||
|
|
* bounds bug (issue #31563).
|
||
|
|
*
|
||
|
|
* `LinearFifo` (src/collections/linear_fifo.rs) is an internal Rust ring buffer
|
||
|
|
* with no JS-visible surface of its own. Its only in-tree caller (the bake dev
|
||
|
|
* server's source-map weak-ref store) drives it with CSPRNG keys and an async
|
||
|
|
* expiry timer, so the *wrapped* branch of `ordered_remove_item` can't be
|
||
|
|
* reached deterministically from a normal test. The `linearFifoOrderedRemoveProbe`
|
||
|
|
* helper (src/runtime/linear_fifo_testing.rs, exposed via
|
||
|
|
* `bun:internal-for-testing`) reconstructs the exact wrapped states from the
|
||
|
|
* issue and returns the resulting FIFO contents.
|
||
|
|
*
|
||
|
|
* With the buggy bounds (`count - head` / `head - count`) the wrapped branch
|
||
|
|
* panics with a `usize` subtraction overflow / out-of-range slice index; with
|
||
|
|
* the fix (`head + count - buf_len`) these assertions hold. The crate's own
|
||
|
|
* `#[cfg(test)] mod tests` exercise the same states under Miri as well.
|
||
|
|
*/
|
||
|
|
import { linearFifoOrderedRemoveProbe } from "bun:internal-for-testing";
|
||
|
|
import { expect, test } from "bun:test";
|
||
|
|
import { existsSync } from "node:fs";
|
||
|
|
import path from "node:path";
|
||
|
|
|
||
|
|
test("ordered_remove_item preserves FIFO order in the wrapped tail sub-branch (head < count)", () => {
|
||
|
|
// write 12, read 8, write 10 -> head=8, count=14, buf_len=16 (wraps).
|
||
|
|
// readable = [8,9,10,11, 100,101,102,103, 104,105,106,107,108,109]
|
||
|
|
// remove offset 6 -> index=(8+6)&15=14 >= head -> tail sub-branch, drops 102.
|
||
|
|
expect(linearFifoOrderedRemoveProbe(0)).toEqual([8, 9, 10, 11, 100, 101, 103, 104, 105, 106, 107, 108, 109]);
|
||
|
|
});
|
||
|
|
|
||
|
|
test("ordered_remove_item preserves FIFO order in the wrapped prefix sub-branch (head > count)", () => {
|
||
|
|
// write 12, read 12, write 8 -> head=12, count=8, buf_len=16 (wraps).
|
||
|
|
// readable = [200,201,202,203, 204,205,206,207]
|
||
|
|
// remove offset 5 -> index=(12+5)&15=1 < head -> wrapped-prefix sub-branch, drops 205.
|
||
|
|
expect(linearFifoOrderedRemoveProbe(1)).toEqual([200, 201, 202, 203, 204, 206, 207]);
|
||
|
|
});
|
||
|
|
|
||
|
|
// The intra-slice `ptr::copy` calls in `shift_down_one` / `realign` used to
|
||
|
|
// pass `slice.as_ptr()` as src and `slice.as_mut_ptr()` as dst in one call;
|
||
|
|
// under Stacked Borrows the second retag invalidates the first. This has no
|
||
|
|
// runtime-observable effect, so the discriminator is `cargo miri test` itself.
|
||
|
|
// `bun run rust:miri` pins Tree Borrows (which accepts the old shape); this
|
||
|
|
// test clears MIRIFLAGS to use miri's default Stacked Borrows model. Skipped
|
||
|
|
// where miri is not installed, or where the cargo workspace is not resolvable
|
||
|
|
// (test-only lanes run a prebuilt binary and lack vendor/lolhtml; see
|
||
|
|
// scripts/rust-miri.ts for the same prerequisite check).
|
||
|
|
const cargoBin = Bun.which("cargo");
|
||
|
|
const repoRoot = path.resolve(import.meta.dir, "..", "..");
|
||
|
|
const workspaceResolvable =
|
||
|
|
existsSync(path.join(repoRoot, "vendor", "lolhtml", "Cargo.toml")) &&
|
||
|
|
existsSync(path.join(repoRoot, "build", "debug", "codegen", "build_options.rs"));
|
||
|
|
const miriAvailable =
|
||
|
|
!!cargoBin &&
|
||
|
|
workspaceResolvable &&
|
||
|
|
Bun.spawnSync({
|
||
|
|
cmd: [cargoBin, "miri", "--version"],
|
||
|
|
cwd: repoRoot,
|
||
|
|
stdout: "ignore",
|
||
|
|
stderr: "ignore",
|
||
|
|
timeout: 30_000,
|
||
|
|
}).exitCode === 0;
|
||
|
|
|
||
|
|
test.skipIf(!miriAvailable)(
|
||
|
|
"linear_fifo unit tests are clean under Stacked Borrows miri",
|
||
|
|
async () => {
|
||
|
|
await using proc = Bun.spawn({
|
||
|
|
cmd: [cargoBin!, "miri", "test", "--locked", "-p", "bun_collections", "--", "linear_fifo"],
|
||
|
|
cwd: repoRoot,
|
||
|
|
env: { ...process.env, MIRIFLAGS: "" },
|
||
|
|
stdout: "pipe",
|
||
|
|
stderr: "pipe",
|
||
|
|
});
|
||
|
|
const [stdout, stderr, exitCode] = await Promise.all([proc.stdout.text(), proc.stderr.text(), proc.exited]);
|
||
|
|
if (exitCode !== 0) {
|
||
|
|
// Surface miri's diagnostic so the gate/CI log shows the actual UB.
|
||
|
|
console.error(stderr || stdout);
|
||
|
|
}
|
||
|
|
expect(stderr).not.toContain("Undefined Behavior");
|
||
|
|
expect(exitCode).toBe(0);
|
||
|
|
},
|
||
|
|
120_000,
|
||
|
|
);
|