diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 12:39:43 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:14 -0300 |
| commit | c8a3ecc3bcb36f0259f4d8ad2e0cdfcdc3183c9f (patch) | |
| tree | 281c5f96b398ea8c19cc65841088bc89075d1778 /build.zig | |
| parent | c5c1c200ad66a16b73428de2c369fc15aacac8b4 (diff) | |
| download | pardes-c8a3ecc3bcb36f0259f4d8ad2e0cdfcdc3183c9f.tar.gz pardes-c8a3ecc3bcb36f0259f4d8ad2e0cdfcdc3183c9f.zip | |
A regular expression search is bounded by a step budget patched into mvzr, not windows and a repeat cap
The windows returned wrong matches: a candidate reaching a window's edge was
left to the next window, half a window on, which could answer a match starting
mid-token rather than the leftmost, and addr then pointed data's next write at
the wrong bytes. The repeat cap missed mvzr's own worst case, a chain of a?,
and alternation under a repeat, each exponential inside one mvzr call the
deadline could not interrupt; and it refused ordinary s/S patterns. build.zig
now patches the fetched mvzr at build time with a step counter on its
backtracking recursion (matchPattern), so a fresh fetch keeps it and a moved
anchor stops the build; regexp.zig gives each compiled pattern a budget, about
300 ms here, and a search that spends it fails as taking too long. Windows,
the cap and their special cases are gone, and matches are exact again.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'build.zig')
| -rw-r--r-- | build.zig | 32 |
1 files changed, 31 insertions, 1 deletions
@@ -418,7 +418,37 @@ pub fn build(b: *std.Build) void { .root_source_file = b.path("src/web/zstbi.zig"), }); - const mvzr_mod = b.dependency("mvzr", .{ .target = target, .optimize = optimize }).module("mvzr"); + // mvzr backtracks with no bound on its work (its own test "do not make + // this test any longer": `a?` n times is 2^n steps), and pardes searches + // with it holding the editor. Its matcher gets a step budget, patched into + // the fetched source here at build time so that a fresh fetch keeps it: + // each entry to matchPattern, which its backtracking recurses through, + // spends a step, and with none left the match fails and says so + // (src/regexp.zig). A bump that moves the anchor stops the build. + const mvzr_mod = mvzr: { + const dep = b.dependency("mvzr", .{ .target = target, .optimize = optimize }); + const src = std.Io.Dir.cwd().readFileAlloc(io, dep.path("src/mvzr.zig").getPath(b), b.allocator, .limited(1 << 20)) catch @panic("read mvzr"); + const anchor = "fn matchPattern(patt: []const RegOp, sets: []const CharSet, haystack: []const u8, i_in: usize) ?OpMatch {\n"; + const at = (std.mem.indexOf(u8, src, anchor) orelse @panic("mvzr's matchPattern changed: redo its step budget in build.zig")) + anchor.len; + const spend = + \\ if (steps_left == 0) { + \\ exhausted = true; + \\ return null; + \\ } + \\ steps_left -= 1; + \\ + ; + const budget = + \\ + \\/// pardes's patch (its build.zig): matchPattern entries left before a + \\/// match gives up, and whether one did. Set by the caller per search. + \\pub threadlocal var steps_left: u64 = std.math.maxInt(u64); + \\pub threadlocal var exhausted: bool = false; + \\ + ; + const patched = b.fmt("{s}{s}{s}{s}", .{ src[0..at], spend, src[at..], budget }); + break :mvzr b.createModule(.{ .root_source_file = b.addWriteFiles().add("mvzr.zig", patched), .target = target, .optimize = optimize }); + }; // The C heap every C library with an allocator hook is handed: the core's // tree-sitter and fonts, and pdf.zig's MuPDF, which is a module of its own. |
