Files
browser/src/crash_handler.zig
Karl Seguin 8e42d63c1c zig: Zig 0.16
Built against https://github.com/lightpanda-io/zig-v8-fork/tree/zig-0.16 but
it doesn't require a new v8 build.

Built against https://github.com/lightpanda-io/boringssl-zig/tree/zig-0.16
since the current fork we point to isn't updated.

A global std.Io instance, lp.io. Way easier this way and requires 0 changes to
our libcurl integration / event loop.

Network code uses a new layer that does what Zig 0.15's posix package used to
do. Again, quicker migration that way. But, as long as we have the global IO,
and given the half-baked nature of networking in std.Io 0.16, this just makes
sense. Things can be migrated as needed.

The std.time.* -> std.Io.Timestamp/Clock/Duration resulted in _a lot_ of
changes. ArrayList = .{} -> ArrayList -> .empty also resulted in a lot of
changes, but that's obviously superficial. As is the trimLeft/trimRight ->
trimStart/trimEnd rename.

Locking adopt the `Uncancelable` variants, e.g. mutex.lockUncancelable() to
preserve the error-free signature (and, because cancellation would be something
we'd have to put more thought into).

std.json.ObjectMap is now unmanaged, so the allocator had to be passed along.
However, there's still a deprecated managed variant of MemoryPool, so I switched
to it (we can do a small follow up PR to move to the unmanaged after).

I tried use_llvm = false, but it locks my computer, consuming RAM until MacOS
gives me a popup I've never seen before, begging me to start killing processes.

Agent and the networking stuff saw the most significant changes.
2026-07-22 13:26:03 +08:00

161 lines
5.4 KiB
Zig

const std = @import("std");
const lp = @import("lightpanda");
const builtin = @import("builtin");
const IS_DEBUG = builtin.mode == .Debug;
const abort = std.process.abort;
// tracks how deep within a panic we're panicling
var panic_level: usize = 0;
// Locked to avoid interleaving panic messages from multiple threads.
var panic_mutex: std.Io.Mutex = .init;
// overwrite's Zig default panic handler
pub fn panic(msg: []const u8, _: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn {
@branchHint(.cold);
crash(msg, .{ .source = "global" }, begin_addr orelse @returnAddress());
}
pub noinline fn crash(
reason: []const u8,
args: anytype,
begin_addr: usize,
) noreturn {
@branchHint(.cold);
nosuspend switch (panic_level) {
0 => {
panic_level = panic_level + 1;
{
panic_mutex.lockUncancelable(lp.io);
defer panic_mutex.unlock(lp.io);
var writer_w = std.Io.File.stderr().writerStreaming(lp.io, &.{});
const writer = &writer_w.interface;
writer.writeAll(
\\
\\Lightpanda has crashed. Please report the issue:
\\https://github.com/lightpanda-io/browser/issues
\\or let us know on discord: https://discord.gg/g24PtgD6
\\
) catch abort();
writer.print("\nreason: {s}\n", .{reason}) catch abort();
writer.print("OS: {s}\n", .{@tagName(builtin.os.tag)}) catch abort();
writer.print("mode: {s}\n", .{@tagName(builtin.mode)}) catch abort();
writer.print("version: {s}\n", .{lp.build_config.version}) catch abort();
inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |f| {
writer.writeAll(f.name ++ ": ") catch break;
lp.log.writeValue(.pretty, @field(args, f.name), writer) catch abort();
writer.writeByte('\n') catch abort();
}
std.debug.writeCurrentStackTrace(.{ .first_address = begin_addr }, .{ .writer = writer, .mode = .no_color }) catch abort();
}
report(reason, begin_addr, args) catch {};
},
1 => {
panic_level = 2;
var stderr_w = std.Io.File.stderr().writerStreaming(lp.io, &.{});
const stderr = &stderr_w.interface;
stderr.writeAll("panicked during a panic. Aborting.\n") catch abort();
},
else => {},
};
abort();
}
fn report(reason: []const u8, begin_addr: usize, args: anytype) !void {
if (comptime IS_DEBUG) {
return;
}
if (@import("telemetry/telemetry.zig").isDisabled()) {
return;
}
var curl_path: [2048]u8 = undefined;
const curl_path_len = curlPath(&curl_path) orelse return;
var url_buffer: [4096]u8 = undefined;
const url = blk: {
var writer: std.Io.Writer = .fixed(&url_buffer);
try writer.print("https://crash.lightpanda.io/c?v={s}&r=", .{lp.build_config.version_encoded});
for (reason) |b| {
switch (b) {
'A'...'Z', 'a'...'z', '0'...'9', '-', '.', '_' => try writer.writeByte(b),
' ' => try writer.writeByte('+'),
else => try writer.writeByte('!'), // some weird character, that we shouldn't have, but that'll we'll replace with a weird (bur url-safe) character
}
}
try writer.writeByte(0);
break :blk writer.buffered();
};
var body_buffer: [8192]u8 = undefined;
const body = blk: {
var writer: std.Io.Writer = .fixed(body_buffer[0..8191]); // reserve 1 space
inline for (@typeInfo(@TypeOf(args)).@"struct".fields) |f| {
writer.writeAll(f.name ++ ": ") catch break;
lp.log.writeValue(.pretty, @field(args, f.name), &writer) catch {};
writer.writeByte('\n') catch {};
}
std.debug.writeCurrentStackTrace(.{ .first_address = begin_addr }, .{ .writer = &writer, .mode = .no_color }) catch {};
const written = writer.buffered();
if (written.len == 0) {
break :blk "???";
}
// Overwrite the last character with our null terminator
// body_buffer always has to be > written
body_buffer[written.len] = 0;
break :blk body_buffer[0 .. written.len + 1];
};
var argv = [_:null]?[*:0]const u8{
curl_path[0..curl_path_len :0],
"-fsSL",
"-H",
"Content-Type: application/octet-stream",
"--data-binary",
body[0 .. body.len - 1 :0],
url[0 .. url.len - 1 :0],
};
const result = std.c.fork();
switch (result) {
0 => {
_ = std.c.close(0);
_ = std.c.close(1);
_ = std.c.close(2);
_ = std.c.execve(argv[0].?, &argv, std.c.environ);
std.c.exit(0);
},
else => return,
}
}
fn curlPath(buf: []u8) ?usize {
const path_z = std.c.getenv("PATH") orelse return null;
var it = std.mem.tokenizeScalar(u8, std.mem.span(path_z), std.fs.path.delimiter);
var fba = std.heap.FixedBufferAllocator.init(buf);
const allocator = fba.allocator();
const cwd = std.Io.Dir.cwd();
while (it.next()) |p| {
defer fba.reset();
const full_path = std.fs.path.joinZ(allocator, &.{ p, "curl" }) catch continue;
cwd.access(lp.io, full_path, .{}) catch continue;
return full_path.len;
}
return null;
}