Files
ghostty/pkg/oniguruma/regex.zig
Mitchell Hashimoto 3dde6e2559 terminal: bound link regex search work with Oniguruma retry limits
Fixes #11177

Use per-search Oniguruma match params (retry_limit_in_search) in
StringMap-backed link detection to avoid pathological backtracking hangs
on very long lines.

The units are ticks in the internal loop so its kind of opaque but
this seems to still match some very long URLs. The test case in question
was a 169K character line (which is now rejected).
2026-03-04 21:02:55 -08:00

153 lines
4.0 KiB
Zig

const std = @import("std");
const c = @import("c.zig").c;
const types = @import("types.zig");
const errors = @import("errors.zig");
const testEnsureInit = @import("testing.zig").ensureInit;
const MatchParam = @import("match_param.zig").MatchParam;
const Region = @import("region.zig").Region;
const Error = errors.Error;
const ErrorInfo = errors.ErrorInfo;
const Encoding = types.Encoding;
const Option = types.Option;
const Syntax = types.Syntax;
pub const Regex = struct {
value: c.OnigRegex,
pub fn init(
pattern: []const u8,
options: Option,
enc: *Encoding,
syntax: *Syntax,
err: ?*ErrorInfo,
) !Regex {
var self: Regex = undefined;
_ = try errors.convertError(c.onig_new(
&self.value,
pattern.ptr,
pattern.ptr + pattern.len,
options.int(),
@ptrCast(@alignCast(enc)),
@ptrCast(@alignCast(syntax)),
@ptrCast(err),
));
return self;
}
pub fn deinit(self: *Regex) void {
c.onig_free(self.value);
}
/// Search an entire string for matches. This always returns a region
/// which may heap allocate (C allocator).
pub fn search(
self: *Regex,
str: []const u8,
options: Option,
) !Region {
return self.searchWithParam(str, options, null);
}
/// Search an entire string for matches. This always returns a region
/// which may heap allocate (C allocator).
pub fn searchWithParam(
self: *Regex,
str: []const u8,
options: Option,
match_param: ?*MatchParam,
) !Region {
var region: Region = .{};
// As part of the searchAdvanced API call below, onig may allocate
// memory even if we fail to match. We need to deinit if there are
// any errors to free that memory.
errdefer region.deinit();
_ = try self.searchAdvancedWithParam(
str,
0,
str.len,
&region,
options,
match_param,
);
return region;
}
/// onig_search directly
pub fn searchAdvanced(
self: *Regex,
str: []const u8,
start: usize,
end: usize,
region: *Region,
options: Option,
) !usize {
return self.searchAdvancedWithParam(
str,
start,
end,
region,
options,
null,
);
}
/// onig_search_with_param directly
pub fn searchAdvancedWithParam(
self: *Regex,
str: []const u8,
start: usize,
end: usize,
region: *Region,
options: Option,
match_param: ?*MatchParam,
) !usize {
const pos = try errors.convertError(if (match_param) |param|
c.onig_search_with_param(
self.value,
str.ptr,
str.ptr + str.len,
str.ptr + start,
str.ptr + end,
@ptrCast(region),
options.int(),
param.value,
)
else
c.onig_search(
self.value,
str.ptr,
str.ptr + str.len,
str.ptr + start,
str.ptr + end,
@ptrCast(region),
options.int(),
));
return @intCast(pos);
}
};
test {
const testing = std.testing;
try testEnsureInit();
var re = try Regex.init("foo", .{}, Encoding.utf8, Syntax.default, null);
defer re.deinit();
var reg = try re.search("hello foo bar", .{});
defer reg.deinit();
try testing.expectEqual(@as(usize, 1), reg.count());
try testing.expectError(Error.Mismatch, re.search("hello", .{}));
var match_param = try MatchParam.init();
defer match_param.deinit();
try match_param.setRetryLimitInSearch(1000);
var reg_param = try re.searchWithParam("hello foo bar", .{}, &match_param);
defer reg_param.deinit();
try testing.expectEqual(@as(usize, 1), reg_param.count());
}