verbose_air: use caller-provided error buffer instead of heap allocation

Replace the heap-allocated error_msg with a fixed-size caller-provided
buffer, making error reporting infallible. All error paths now write
into the buffer via bufPrint with truncation on overflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-20 08:10:57 +00:00
parent a560752410
commit a787650d54
3 changed files with 50 additions and 30 deletions

View File

@@ -30,20 +30,27 @@ const CSemaFuncAir = extern struct {
const CompileAirResult = extern struct {
items: ?[*]CSemaFuncAir,
len: u32,
error_msg: ?[*:0]u8, // NULL on success, caller frees
};
const AirCollector = struct {
funcs: std.ArrayListUnmanaged(CSemaFuncAir) = .empty,
first_error: ?[*:0]u8 = null,
err_buf: *[err_buf_size]u8,
fn hasError(self: *const AirCollector) bool {
return self.err_buf[0] != 0;
}
fn collectFunc(ctx: *anyopaque, name: []const u8, air: *const Air) void {
const self: *AirCollector = @ptrCast(@alignCast(ctx));
self.collectFuncInner(name, air) catch {};
self.collectFuncInner(name, air) catch |err| {
if (!self.hasError()) {
setErr(self.err_buf, "collectFunc '{s}': {s}", .{ name, @errorName(err) });
}
};
}
fn collectFuncInner(self: *AirCollector, name: []const u8, air: *const Air) !void {
if (self.first_error != null) return;
if (self.hasError()) return;
const gpa = std.heap.c_allocator;
const inst_len: u32 = @intCast(air.instructions.len);
@@ -52,7 +59,7 @@ const AirCollector = struct {
const zig_tags = air.instructions.items(.tag);
const tags_copy: ?[*]u8 = if (inst_len > 0) blk: {
const src = @as([*]const u8, @ptrCast(zig_tags.ptr))[0..inst_len];
const dst = gpa.alloc(u8, inst_len) catch return;
const dst = try gpa.alloc(u8, inst_len);
@memcpy(dst, src);
break :blk dst.ptr;
} else null;
@@ -60,7 +67,7 @@ const AirCollector = struct {
// Copy datas (8 bytes per instruction)
const datas_byte_len = inst_len * 8;
const datas_copy: ?[*]u8 = if (inst_len > 0) blk: {
const dst = gpa.alloc(u8, datas_byte_len) catch return;
const dst = try gpa.alloc(u8, datas_byte_len);
const zig_datas = air.instructions.items(.data);
if (@sizeOf(Air.Inst.Data) == 8) {
const src = @as([*]const u8, @ptrCast(zig_datas.ptr))[0..datas_byte_len];
@@ -78,15 +85,15 @@ const AirCollector = struct {
// Copy extra
const extra_len: u32 = @intCast(air.extra.items.len);
const extra_copy: ?[*]u32 = if (extra_len > 0) blk: {
const dst = gpa.alloc(u32, extra_len) catch return;
const dst = try gpa.alloc(u32, extra_len);
@memcpy(dst, air.extra.items);
break :blk dst.ptr;
} else null;
// Copy name
const name_copy = gpa.dupeZ(u8, name) catch return;
const name_copy = try gpa.dupeZ(u8, name);
self.funcs.append(gpa, .{
try self.funcs.append(gpa, .{
.name = name_copy.ptr,
.air = .{
.inst_len = inst_len,
@@ -97,25 +104,31 @@ const AirCollector = struct {
.extra_cap = extra_len,
.extra = extra_copy,
},
}) catch return;
});
}
};
const err_buf_size = 256;
export fn zig_compile_air(
src_path_ptr: [*:0]const u8,
module_root_ptr: ?[*:0]const u8,
err_buf_ptr: [*]u8,
) CompileAirResult {
const err_buf: *[err_buf_size]u8 = err_buf_ptr[0..err_buf_size];
err_buf[0] = 0;
return zigCompileAirImpl(
std.mem.span(src_path_ptr),
if (module_root_ptr) |p| std.mem.span(p) else null,
err_buf,
) catch |err| {
return errResult(@errorName(err));
setErr(err_buf, "{s}", .{@errorName(err)});
return .{ .items = null, .len = 0 };
};
}
export fn zig_compile_air_free(result: *CompileAirResult) void {
const gpa = std.heap.c_allocator;
if (result.error_msg) |e| gpa.free(std.mem.span(e));
if (result.items) |items| {
for (items[0..result.len]) |*f| {
if (f.name) |n| gpa.free(std.mem.span(n));
@@ -127,15 +140,18 @@ export fn zig_compile_air_free(result: *CompileAirResult) void {
}
}
fn errResult(msg: []const u8) CompileAirResult {
const duped = std.heap.c_allocator.dupeZ(u8, msg) catch
return .{ .items = null, .len = 0, .error_msg = null };
return .{ .items = null, .len = 0, .error_msg = duped.ptr };
fn setErr(buf: *[err_buf_size]u8, comptime fmt: []const u8, args: anytype) void {
const written = std.fmt.bufPrint(buf[0 .. err_buf_size - 1], fmt, args) catch {
buf[err_buf_size - 1] = 0;
return;
};
buf[written.len] = 0;
}
fn zigCompileAirImpl(
src_path: []const u8,
module_root_opt: ?[]const u8,
err_buf: *[err_buf_size]u8,
) !CompileAirResult {
const gpa = std.heap.c_allocator;
@@ -224,7 +240,7 @@ fn zigCompileAirImpl(
gpa.destroy(thread_pool);
}
var collector: AirCollector = .{};
var collector: AirCollector = .{ .err_buf = err_buf };
var create_diag: Compilation.CreateDiagnostic = undefined;
const comp = Compilation.create(gpa, arena, &create_diag, .{
@@ -241,7 +257,8 @@ fn zigCompileAirImpl(
},
}) catch |err| switch (err) {
error.CreateFail => {
return errResult("Compilation.create failed");
setErr(err_buf, "Compilation.create failed", .{});
return .{ .items = null, .len = 0 };
},
else => return err,
};
@@ -255,11 +272,12 @@ fn zigCompileAirImpl(
var buf: std.io.Writer.Allocating = .init(gpa);
error_bundle.renderToWriter(.{ .ttyconf = .no_color }, &buf.writer) catch {};
defer buf.deinit();
return errResult(buf.written());
setErr(err_buf, "{s}", .{buf.written()});
return .{ .items = null, .len = 0 };
}
if (collector.first_error) |e| {
return .{ .items = null, .len = 0, .error_msg = e };
if (collector.hasError()) {
return .{ .items = null, .len = 0 };
}
const allocated = collector.funcs.allocatedSlice();
@@ -271,7 +289,7 @@ fn zigCompileAirImpl(
// Shrink to exact size so free works with items[0..len].
const exact = gpa.realloc(allocated, len) catch
allocated; // keep original on realloc failure
return .{ .items = exact.ptr, .len = len, .error_msg = null };
return .{ .items = exact.ptr, .len = len };
}
return .{ .items = allocated.ptr, .len = len, .error_msg = null };
return .{ .items = allocated.ptr, .len = len };
}

View File

@@ -8,10 +8,12 @@
typedef struct {
void* items; // SemaFuncAir* (from sema.h), owned by Zig allocator
uint32_t len;
char* error_msg; // NULL on success, owned by Zig allocator
} ZigCompileAirResult;
extern ZigCompileAirResult zig_compile_air(const char* src_path, const char* module_root);
#define ZIG_COMPILE_ERR_BUF_SIZE 256
extern ZigCompileAirResult zig_compile_air(const char* src_path, const char* module_root,
char err_buf[ZIG_COMPILE_ERR_BUF_SIZE]);
extern void zig_compile_air_free(ZigCompileAirResult* result);
#endif

View File

@@ -227,9 +227,8 @@ test "sema: function decl smoke test" {
const ZigCompileAirResult = extern struct {
items: ?[*]c.SemaFuncAir,
len: u32,
error_msg: ?[*:0]u8,
};
extern fn zig_compile_air([*:0]const u8, ?[*:0]const u8) ZigCompileAirResult;
extern fn zig_compile_air([*:0]const u8, ?[*:0]const u8, [*]u8) ZigCompileAirResult;
extern fn zig_compile_air_free(*ZigCompileAirResult) void;
pub fn airCompareFromSource(source: [:0]const u8, c_func_air_list: c.SemaFuncAirList) !void {
@@ -249,11 +248,12 @@ pub fn airCompare(
module_root: ?[*:0]const u8,
c_func_air_list: c.SemaFuncAirList,
) !void {
var zig_result = zig_compile_air(src_path, module_root);
var err_buf: [c.ZIG_COMPILE_ERR_BUF_SIZE]u8 = .{0} ** c.ZIG_COMPILE_ERR_BUF_SIZE;
var zig_result = zig_compile_air(src_path, module_root, &err_buf);
defer zig_compile_air_free(&zig_result);
if (zig_result.error_msg) |e| {
std.debug.print("zig_compile_air error: {s}\n", .{std.mem.span(e)});
if (err_buf[0] != 0) {
std.debug.print("zig_compile_air error: {s}\n", .{std.mem.sliceTo(&err_buf, 0)});
return error.ZigCompileError;
}