fuzz: add OSC parser fuzzer

This commit is contained in:
Mitchell Hashimoto
2026-03-03 08:31:05 -08:00
parent 2f0039d419
commit d2175d1b56
45 changed files with 100 additions and 28 deletions

View File

@@ -0,0 +1,45 @@
const std = @import("std");
const ghostty_vt = @import("ghostty-vt");
const mem = @import("mem.zig");
const osc = ghostty_vt.osc;
/// Use a single global allocator for simplicity and to avoid heap
/// allocation overhead in the fuzzer. The allocator is backed by a fixed
/// buffer, and every fuzz input resets the bump pointer to the start.
var fuzz_alloc: mem.FuzzAllocator(8 * 1024 * 1024) = .{};
pub export fn zig_fuzz_init() callconv(.c) void {
fuzz_alloc.init();
}
pub export fn zig_fuzz_test(
buf: [*]const u8,
len: usize,
) callconv(.c) void {
// Need at least one byte for the terminator selector.
if (len == 0) return;
fuzz_alloc.reset();
const alloc = fuzz_alloc.allocator();
const input = buf[0..len];
// Use the first byte to select the terminator variant.
const selector = input[0];
const payload = input[1..];
var p = osc.Parser.init(alloc);
defer p.deinit();
for (payload) |byte| p.next(byte);
// Exercise all three terminator paths:
// 0 -> BEL (0x07)
// 1 -> ST (0x9c)
// 2 -> missing terminator (null)
const terminator: ?u8 = switch (selector % 3) {
0 => 0x07,
1 => 0x9c,
else => null,
};
_ = p.end(terminator);
}

View File

@@ -1,12 +1,13 @@
const std = @import("std");
const ghostty_vt = @import("ghostty-vt");
const mem = @import("mem.zig");
const Terminal = ghostty_vt.Terminal;
const ReadonlyStream = ghostty_vt.ReadonlyStream;
/// Use a single global allocator for simplicity and to avoid heap
/// allocation overhead in the fuzzer. The allocator is backed by a fixed
/// buffer, and every fuzz input resets the bump pointer to the start.
var fuzz_alloc: FuzzAllocator = .{};
var fuzz_alloc: mem.FuzzAllocator(64 * 1024 * 1024) = .{};
pub export fn zig_fuzz_init() callconv(.c) void {
fuzz_alloc.init();
@@ -50,29 +51,3 @@ pub export fn zig_fuzz_test(
std.debug.panic("next: {}", .{err});
}
}
/// Fixed-capacity allocator that avoids heap allocation and gives the
/// fuzzer deterministic, bounded memory behaviour. Backed by a single
/// fixed buffer; every `reset()` returns the bump pointer to the start
/// so the same memory is reused across iterations.
const FuzzAllocator = struct {
buf: [mem_size]u8 = undefined,
state: std.heap.FixedBufferAllocator = undefined,
/// 64 MiB gives the fuzzer enough headroom to exercise terminal
/// resizes, large scrollback, and other allocation-heavy paths
/// without running into out-of-memory on every other input.
const mem_size = 64 * 1024 * 1024;
fn init(self: *FuzzAllocator) void {
self.state = .init(&self.buf);
}
fn allocator(self: *FuzzAllocator) std.mem.Allocator {
return self.state.allocator();
}
fn reset(self: *FuzzAllocator) void {
self.state.reset();
}
};

View File

@@ -0,0 +1,26 @@
const std = @import("std");
/// Fixed-capacity allocator that avoids heap allocation and gives the
/// fuzzer deterministic, bounded memory behaviour. Backed by a single
/// fixed buffer; every `reset()` returns the bump pointer to the start
/// so the same memory is reused across iterations.
pub fn FuzzAllocator(comptime mem_size: usize) type {
return struct {
buf: [mem_size]u8 = undefined,
state: std.heap.FixedBufferAllocator = undefined,
const Self = @This();
pub fn init(self: *Self) void {
self.state = .init(&self.buf);
}
pub fn allocator(self: *Self) std.mem.Allocator {
return self.state.allocator();
}
pub fn reset(self: *Self) void {
self.state.reset();
}
};
}