summaryrefslogtreecommitdiff
path: root/build.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-28 12:39:43 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:14 -0300
commitc8a3ecc3bcb36f0259f4d8ad2e0cdfcdc3183c9f (patch)
tree281c5f96b398ea8c19cc65841088bc89075d1778 /build.zig
parentc5c1c200ad66a16b73428de2c369fc15aacac8b4 (diff)
downloadpardes-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.zig32
1 files changed, 31 insertions, 1 deletions
diff --git a/build.zig b/build.zig
index f0675a70..666e792d 100644
--- a/build.zig
+++ b/build.zig
@@ -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.