diff options
author | 2022-01-03 22:17:34 -0800 | |
---|---|---|
committer | 2022-01-03 22:17:34 -0800 | |
commit | 0960f3d6d1b6460e1a7a4dcec4921d2cf664df72 (patch) | |
tree | 6928d162d1c2a9fac43fd7fdbc6c9cebb8cfa63c | |
parent | 64b49ddd951e4e94978497302cd73e8ce8114010 (diff) | |
download | bun-0960f3d6d1b6460e1a7a4dcec4921d2cf664df72.tar.gz bun-0960f3d6d1b6460e1a7a4dcec4921d2cf664df72.tar.zst bun-0960f3d6d1b6460e1a7a4dcec4921d2cf664df72.zip |
Implement a crash reporter and improve some error handling in `bun install`
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | Makefile | 28 | ||||
-rw-r--r-- | build.zig | 24 | ||||
-rw-r--r-- | src/allocators/mimalloc.zig | 2 | ||||
-rw-r--r-- | src/analytics/analytics_thread.zig | 35 | ||||
-rw-r--r-- | src/cli.zig | 4 | ||||
-rw-r--r-- | src/deps/PLCrashReport.bindings.h | 10 | ||||
-rw-r--r-- | src/deps/PLCrashReport.m | 75 | ||||
-rw-r--r-- | src/deps/PLCrashReport.zig | 42 | ||||
-rw-r--r-- | src/global.zig | 2 | ||||
-rw-r--r-- | src/install/install.zig | 99 | ||||
-rw-r--r-- | src/main.zig | 57 | ||||
-rw-r--r-- | src/panic_handler.zig | 22 | ||||
-rw-r--r-- | src/report.zig | 168 |
14 files changed, 491 insertions, 79 deletions
diff --git a/.gitignore b/.gitignore index 66c10a3fb..3d442872c 100644 --- a/.gitignore +++ b/.gitignore @@ -83,3 +83,5 @@ src/deps/s2n-tls .npm.gz bun-binary + +src/deps/PLCrashReporter/ @@ -226,6 +226,10 @@ ARCHIVE_FILES_WITHOUT_LIBCRYPTO = $(BUN_DEPS_OUT_DIR)/$(MIMALLOC_FILE) \ ARCHIVE_FILES = $(ARCHIVE_FILES_WITHOUT_LIBCRYPTO) $(BUN_DEPS_OUT_DIR)/libcrypto.boring.a +ifeq ($(OS_NAME), darwin) +ARCHIVE_FILES += $(BUN_DEPS_OUT_DIR)/libCrashReporter.a $(BUN_DEPS_OUT_DIR)/libCrashReporter.bindings.a +endif + PLATFORM_LINKER_FLAGS = STATIC_MUSL_FLAG ?= @@ -257,10 +261,10 @@ BUN_LLD_FLAGS = $(OBJ_FILES) \ $(PLATFORM_LINKER_FLAGS) -bun: vendor identifier-cache build-obj bun-link-lld-release bun-codesign-release-local +bun: vendor identifier-cache build-obj bun-link-lld-release bun-codesign-release-local -vendor-without-check: api analytics node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp zlib boringssl libarchive +vendor-without-check: api analytics node-fallbacks runtime_js fallback_decoder bun_error mimalloc picohttp zlib boringssl libarchive pl-crash-report boringssl-build: cd $(BUN_DEPS_DIR)/boringssl && mkdir -p build && cd build && cmake $(CMAKE_FLAGS) -GNinja .. && ninja @@ -271,6 +275,26 @@ boringssl-copy: boringssl: boringssl-build boringssl-copy +download-pl-crash-report: + rm -rf /tmp/PLCrashReporter.zip /tmp/PLCrashReporter + curl -L https://github.com/microsoft/plcrashreporter/releases/download/1.10.1/PLCrashReporter-Static-1.10.1.zip > /tmp/PLCrashReporter.zip + unzip /tmp/PLCrashReporter.zip -d /tmp + cp /tmp/PLCrashReporter/libCrashReporter-MacOSX-Static.a $(BUN_DEPS_OUT_DIR)/libCrashReporter.a + mkdir -p $(BUN_DEPS_DIR)/PLCrashReporter/include/PLCrashReporter + cp -r /tmp/PLCrashReporter/include/*.h $(BUN_DEPS_DIR)/PLCrashReporter/include/PLCrashReporter + +pl-crash-report: + +ifeq ($(OS_NAME), darwin) +pl-crash-report: pl-crash-report-mac +endif + +pl-crash-report-mac: download-pl-crash-report pl-crash-report-mac-compile + +pl-crash-report-mac-compile: + $(CC) $(MACOS_MIN_FLAG) -O3 -ObjC -I$(BUN_DEPS_DIR)/PLCrashReporter/include -I$(BUN_DEPS_DIR)/PLCrashReporter -c $(BUN_DEPS_DIR)/PLCrashReport.m \ + -g -o $(BUN_DEPS_OUT_DIR)/libCrashReporter.bindings.a + libarchive: cd $(BUN_DEPS_DIR)/libarchive; \ (make clean || echo ""); \ @@ -51,6 +51,16 @@ fn addInternalPackages(step: *std.build.LibExeObjStep, _: std.mem.Allocator, tar .path = pkgPath("src/thread_pool.zig"), }; + var crash_reporter_mac: std.build.Pkg = .{ + .name = "crash_reporter", + .path = pkgPath("src/deps/PLCrashReport.zig"), + }; + + var crash_reporter_linux: std.build.Pkg = .{ + .name = "crash_reporter", + .path = pkgPath("src/deps/crash_reporter_linux.zig"), + }; + var picohttp: std.build.Pkg = .{ .name = "picohttp", .path = pkgPath("src/deps/picohttp.zig"), @@ -70,6 +80,11 @@ fn addInternalPackages(step: *std.build.LibExeObjStep, _: std.mem.Allocator, tar else io_linux; + var crash_reporter = if (target.isDarwin()) + crash_reporter_mac + else + crash_reporter_linux; + var strings: std.build.Pkg = .{ .name = "strings", .path = pkgPath("src/string_immutable.zig"), @@ -120,6 +135,7 @@ fn addInternalPackages(step: *std.build.LibExeObjStep, _: std.mem.Allocator, tar step.addPackage(http); step.addPackage(boringssl); step.addPackage(javascript_core); + step.addPackage(crash_reporter); } var output_dir: []const u8 = ""; fn panicIfNotFound(comptime filepath: []const u8) []const u8 { @@ -273,6 +289,7 @@ pub fn build(b: *std.build.Builder) !void { if (target.getOsTag() == .linux) { // obj.want_lto = tar; obj.link_emit_relocs = true; + obj.link_eh_frame_hdr = true; obj.link_function_sections = true; } var log_step = b.addLog("Destination: {s}/{s}\n", .{ output_dir, bun_executable_name }); @@ -377,7 +394,7 @@ pub fn linkObjectFiles(b: *std.build.Builder, obj: *std.build.LibExeObjStep, tar var dirs_to_search = std.BoundedArray([]const u8, 32).init(0) catch unreachable; const arm_brew_prefix: []const u8 = "/opt/homebrew"; const x86_brew_prefix: []const u8 = "/usr/local"; - try dirs_to_search.append(b.env_map.get("BUN_DEPS_DIR") orelse @as([]const u8, b.pathFromRoot("src/deps"))); + try dirs_to_search.append(b.env_map.get("BUN_DEPS_OUT_DIR") orelse b.env_map.get("BUN_DEPS_DIR") orelse @as([]const u8, b.pathFromRoot("src/deps"))); if (target.getOsTag() == .macos) { if (target.getCpuArch().isAARCH64()) { try dirs_to_search.append(comptime arm_brew_prefix ++ "/opt/icu4c/lib/"); @@ -405,6 +422,8 @@ pub fn linkObjectFiles(b: *std.build.Builder, obj: *std.build.LibExeObjStep, tar .{ "libJavaScriptCore.a", "libJavaScriptCore.a" }, .{ "libWTF.a", "libWTF.a" }, .{ "libbmalloc.a", "libbmalloc.a" }, + .{ "libCrashReporter.a", "libCrashReporter.a" }, + .{ "libCrashReporter.bindings.a", "libCrashReporter.bindings.a" }, }); for (dirs_to_search.slice()) |deps_path| { @@ -431,6 +450,7 @@ pub fn configureObjectStep(obj: *std.build.LibExeObjStep, target: anytype, main_ try addInternalPackages(obj, std.heap.page_allocator, target); addPicoHTTP(obj, false); + obj.strip = false; obj.setOutputDir(output_dir); obj.setBuildMode(mode); obj.linkLibC(); @@ -439,8 +459,8 @@ pub fn configureObjectStep(obj: *std.build.LibExeObjStep, target: anytype, main_ if (target.getOsTag() == .linux) { // obj.want_lto = tar; - obj.link_eh_frame_hdr = true; obj.link_emit_relocs = true; + obj.link_eh_frame_hdr = true; obj.link_function_sections = true; } } diff --git a/src/allocators/mimalloc.zig b/src/allocators/mimalloc.zig index a6123a696..068552917 100644 --- a/src/allocators/mimalloc.zig +++ b/src/allocators/mimalloc.zig @@ -44,7 +44,7 @@ pub extern fn mi_process_init() void; pub extern fn mi_thread_init() void; pub extern fn mi_thread_done() void; pub extern fn mi_thread_stats_print_out(out: ?mi_output_fun, arg: ?*anyopaque) void; -pub extern fn mi_process_info(elapsed_msecs: [*c]usize, user_msecs: [*c]usize, system_msecs: [*c]usize, current_rss: [*c]usize, peak_rss: [*c]usize, current_commit: [*c]usize, peak_commit: [*c]usize, page_faults: [*c]usize) void; +pub extern fn mi_process_info(elapsed_msecs: *usize, user_msecs: *usize, system_msecs: *usize, current_rss: *usize, peak_rss: *usize, current_commit: *usize, peak_commit: *usize, page_faults: *usize) void; pub extern fn mi_malloc_aligned(size: usize, alignment: usize) ?*anyopaque; pub extern fn mi_malloc_aligned_at(size: usize, alignment: usize, offset: usize) ?*anyopaque; pub extern fn mi_zalloc_aligned(size: usize, alignment: usize) ?*anyopaque; diff --git a/src/analytics/analytics_thread.zig b/src/analytics/analytics_thread.zig index d453e6992..026947445 100644 --- a/src/analytics/analytics_thread.zig +++ b/src/analytics/analytics_thread.zig @@ -51,6 +51,41 @@ pub const Features = struct { pub var external = false; pub var fetch = false; + pub fn formatter() Formatter { + return Formatter{}; + } + pub const Formatter = struct { + pub fn format(_: Formatter, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void { + const fields = comptime .{ + "single_page_app_routing", + "tsconfig_paths", + "fast_refresh", + "hot_module_reloading", + "jsx", + "always_bundle", + "tsconfig", + "bun_bun", + "filesystem_router", + "framework", + "bunjs", + "macros", + "public_folder", + "dotenv", + "define", + "loaders", + "origin", + "external", + "fetch", + }; + inline for (fields) |field| { + if (@field(Features, field)) { + try writer.writeAll(field); + try writer.writeAll(" "); + } + } + } + }; + const Bitset = std.bit_set.IntegerBitSet(32); pub const Serializer = struct { diff --git a/src/cli.zig b/src/cli.zig index fe03de014..b17dc8396 100644 --- a/src/cli.zig +++ b/src/cli.zig @@ -76,6 +76,8 @@ pub const Cli = struct { } }; } + + pub var cmd: ?Command.Tag = null; }; const LoaderMatcher = strings.ExactSizeMatcher(4); @@ -583,6 +585,8 @@ pub const Command = struct { debug: DebugOptions = DebugOptions{}, pub fn create(allocator: std.mem.Allocator, log: *logger.Log, comptime command: Command.Tag) anyerror!Context { + Cli.cmd = command; + var ctx = Command.Context{ .args = std.mem.zeroes(Api.TransformOptions), .log = log, diff --git a/src/deps/PLCrashReport.bindings.h b/src/deps/PLCrashReport.bindings.h new file mode 100644 index 000000000..2fbfc9bac --- /dev/null +++ b/src/deps/PLCrashReport.bindings.h @@ -0,0 +1,10 @@ +#include <stdbool.h> +#include <stdint.h> + +extern bool PLCrashReportStart(const char *version, const char *basePath); +extern void PLCrashReportHandler(void *context); + +extern void PLCrashReportGenerate(); +extern void *PLCrashReportLoadPending(); + +extern uint16_t copyCrashReportPath(char *buf[1024]); diff --git a/src/deps/PLCrashReport.m b/src/deps/PLCrashReport.m new file mode 100644 index 000000000..7d0aff6d0 --- /dev/null +++ b/src/deps/PLCrashReport.m @@ -0,0 +1,75 @@ +#include "PLCrashReport.bindings.h" + +#include <PLCrashReporter/PLCrashReporter.h> + +NSString *crash_folder; + +@interface PLCrashReporter (PrivateMethods) + +- (id)initWithApplicationIdentifier:(NSString *)applicationIdentifier + appVersion:(NSString *)applicationVersion + appMarketingVersion:(NSString *)applicationMarketingVersion + configuration:(PLCrashReporterConfig *)configuration; + +@end + +void pl_crash_reporter_post_crash_callback(siginfo_t *info, ucontext_t *uap, + void *context) { + PLCrashReportHandler(context); +} + +static PLCrashReporter *reporter; + +NSString *v; +NSString *basePath_; +static void *handler; +bool PLCrashReportStart(const char *version, const char *basePath) { + PLCrashReporterConfig *config; + basePath_ = [NSString stringWithUTF8String:basePath]; + + handler = &pl_crash_reporter_post_crash_callback; + PLCrashReporterCallbacks callbacks = { + .version = 0, .context = NULL, .handleSignal = handler}; + config = [[PLCrashReporterConfig alloc] + initWithSignalHandlerType:PLCrashReporterSignalHandlerTypeBSD + symbolicationStrategy: + PLCrashReporterSymbolicationStrategyNone + shouldRegisterUncaughtExceptionHandler:YES + basePath:basePath_; + + v = [[NSString alloc] initWithBytesNoCopy:version + length:strlen(version) + encoding:NSUTF8StringEncoding + freeWhenDone:NO]; + reporter = [[PLCrashReporter alloc] initWithApplicationIdentifier:@"bun" + appVersion:v + appMarketingVersion:v + configuration:config]; + + crash_folder = basePath_; + [reporter setValue:crash_folder forKey:@"_crashReportDirectory"]; + [reporter setCrashCallbacks:&callbacks]; + + return [reporter enableCrashReporter]; +} + +void PLCrashReportGenerate() { [reporter generateLiveReport]; } +void *PLCrashReportLoadPending() { + return [reporter loadPendingCrashReportData]; +} + +uint16_t copyCrashReportPath(char *buf[1024]) { + NSString *crashReportPath = [reporter crashReportPath]; + [crashReportPath getBytes:buf + maxLength:(1024 - 1) + usedLength:NULL + encoding:NSUTF8StringEncoding + options:0 + range:NSMakeRange(0, [crashReportPath length]) + remainingRange:NULL]; + size_t len = [crashReportPath length]; + if (len > 1024) { + len = 0; + } + return (uint16_t)len; +} diff --git a/src/deps/PLCrashReport.zig b/src/deps/PLCrashReport.zig new file mode 100644 index 000000000..9625cf441 --- /dev/null +++ b/src/deps/PLCrashReport.zig @@ -0,0 +1,42 @@ +const root = @import("root"); +const std = @import("std"); + +extern fn PLCrashReportStart(version: [*:0]const u8, base_path: [*:0]const u8) bool; +extern fn PLCrashReportGenerate() void; +extern fn PLCrashReportLoadPending() ?*anyopaque; +extern fn copyCrashReportPath(buf: *[1024]u8) u16; + +pub export fn PLCrashReportHandler(_: ?*anyopaque) void { + root.PLCrashReportHandler(); +} + +pub fn start( + comptime version: [*:0]const u8, +) bool { + has_started = true; + var base_path_buf: [1024]u8 = undefined; + var base_path: [:0]const u8 = ""; + const crash_path = "/crash/" ++ version ++ "/"; + if (std.os.getenvZ("BUN_INSTALL")) |bun_install| { + @memcpy(&base_path_buf, bun_install.ptr, bun_install.len); + std.mem.copy(u8, base_path_buf[bun_install.len..], crash_path); + base_path_buf[bun_install.len + crash_path.len] = 0; + base_path = base_path_buf[0 .. bun_install.len + crash_path.len :0]; + } else { + base_path = "/tmp/bun" ++ crash_path; + base_path_buf["/tmp/bun".len + crash_path.len] = 0; + } + return PLCrashReportStart(version, base_path.ptr); +} + +pub fn generate() void { + return PLCrashReportGenerate(); +} +var has_started = false; + +pub fn crashReportPath(buf: *[1024]u8) []const u8 { + if (!has_started) return ""; + + const len = copyCrashReportPath(buf); + return buf[0..len]; +} diff --git a/src/global.zig b/src/global.zig index d0f7da345..af915d874 100644 --- a/src/global.zig +++ b/src/global.zig @@ -1,7 +1,7 @@ const std = @import("std"); pub const Environment = @import("env.zig"); -const use_mimalloc = !Environment.isTest; +pub const use_mimalloc = !Environment.isTest; pub const default_allocator: std.mem.Allocator = if (!use_mimalloc) std.heap.c_allocator diff --git a/src/install/install.zig b/src/install/install.zig index d9694ada1..b59349e7f 100644 --- a/src/install/install.zig +++ b/src/install/install.zig @@ -402,26 +402,6 @@ pub const Lockfile = struct { }; }; - const SignalHandler = struct { - pub export fn lockfile_corrupt_signal_handler(_: i32, _: *const std.os.siginfo_t, _: ?*const anyopaque) callconv(.C) void { - var stdout = std.io.getStdOut(); - var stderr = std.io.getStdErr(); - var source = Output.Source.init(stdout, stderr); - Output.Source.set(&source); - - if (Output.isEmojiEnabled()) { - Output.prettyErrorln("<r><red>bun.lockb is corrupt and it crashed bun<r> ðŸ˜ðŸ˜ðŸ˜\n", .{}); - Output.flush(); - } else { - stderr.writeAll("bun.lockb is corrupt, causing bun to crash :'(\n") catch {}; - } - std.mem.doNotOptimizeAway(source); - - std.os.exit(6); - } - }; - var sigaction: std.os.Sigaction = undefined; - pub fn loadFromDisk(this: *Lockfile, allocator: std.mem.Allocator, log: *logger.Log, filename: stringZ) LoadFromDiskResult { std.debug.assert(FileSystem.instance_loaded); var file = std.fs.cwd().openFileZ(filename, .{ .read = true }) catch |err| { @@ -436,13 +416,6 @@ pub const Lockfile = struct { }; var stream = Stream{ .buffer = buf, .pos = 0 }; - sigaction = std.mem.zeroes(std.os.Sigaction); - sigaction.handler = .{ .sigaction = SignalHandler.lockfile_corrupt_signal_handler }; - - std.os.sigaction(std.os.SIG.SEGV | std.os.SIG.BUS, null, null); - std.os.sigaction(std.os.SIG.SEGV | std.os.SIG.BUS, &sigaction, null); - defer std.os.sigaction(std.os.SIG.SEGV | std.os.SIG.BUS, null, null); - // defer std.os.sigaction(sig: u6, act: ?*const Sigaction, oact: ?*Sigaction) Lockfile.Serializer.load(this, &stream, allocator, log) catch |err| { return LoadFromDiskResult{ .err = .{ .step = .parse_file, .value = err } }; }; @@ -1075,13 +1048,13 @@ pub const Lockfile = struct { switch (load_from_disk) { .err => |cause| { switch (cause.step) { - .open_file => Output.prettyErrorln("<r><red>error opening lockfile:<r> {s}.", .{ + .open_file => Output.prettyErrorln("<r><red>error<r> opening lockfile:<r> {s}.", .{ @errorName(cause.value), }), - .parse_file => Output.prettyErrorln("<r><red>error parsing lockfile:<r> {s}", .{ + .parse_file => Output.prettyErrorln("<r><red>error<r> parsing lockfile:<r> {s}", .{ @errorName(cause.value), }), - .read_file => Output.prettyErrorln("<r><red>error reading lockfile:<r> {s}", .{ + .read_file => Output.prettyErrorln("<r><red>error<r> reading lockfile:<r> {s}", .{ @errorName(cause.value), }), } @@ -2291,6 +2264,9 @@ pub const Lockfile = struct { ) !void { initializeStore(); + // A valid package.json always has "{}" characters + if (source.contents.len < 2) return error.InvalidPackageJSON; + var json = json_parser.ParseJSON(&source, log, allocator) catch |err| { if (Output.enable_ansi_colors) { log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true) catch {}; @@ -2630,9 +2606,12 @@ pub const Lockfile = struct { .capacity = 0, }; } + const end = stream.getEndPos() catch 0; + if ((byte_len - 1 + stream.pos) > end) return error.CorruptLockfileNearPackageList; // Count of items in the list const list_len = try reader.readIntLittle(u64); + if (list_len > std.math.maxInt(u32) - 1) return error.CorruptLockfileNearPackageList; var list = Lockfile.Package.List{}; try list.ensureTotalCapacity(allocator, list_len); @@ -4806,7 +4785,6 @@ pub const PackageManager = struct { remote_package_features: Features = Features{ .peer_dependencies = false }, local_package_features: Features = Features{ .peer_dependencies = false, .dev_dependencies = true }, allowed_install_scripts: []const PackageNameHash = &default_allowed_install_scripts, - // The idea here is: // 1. package has a platform-specific binary to install // 2. To prevent downloading & installing incompatible versions, they stick the "real" one in optionalDependencies @@ -5055,6 +5033,7 @@ pub const PackageManager = struct { if (cli.production) { this.local_package_features.dev_dependencies = false; + this.enable.fail_early = true; } if (cli.force) { @@ -5078,6 +5057,7 @@ pub const PackageManager = struct { manifest_cache: bool = true, manifest_cache_control: bool = true, cache: bool = true, + fail_early: bool = false, /// Disabled because it doesn't actually reduce the number of packages we end up installing /// Probably need to be a little smarter @@ -6115,13 +6095,13 @@ pub const PackageManager = struct { return; }; - switch (manager.options.log_level) { - .default => try installWithManager(ctx, manager, package_json_contents, .default), - .verbose => try installWithManager(ctx, manager, package_json_contents, .verbose), - .silent => try installWithManager(ctx, manager, package_json_contents, .silent), - .default_no_progress => try installWithManager(ctx, manager, package_json_contents, .default_no_progress), - .verbose_no_progress => try installWithManager(ctx, manager, package_json_contents, .verbose_no_progress), - } + try switch (manager.options.log_level) { + .default => installWithManager(ctx, manager, package_json_contents, .default), + .verbose => installWithManager(ctx, manager, package_json_contents, .verbose), + .silent => installWithManager(ctx, manager, package_json_contents, .silent), + .default_no_progress => installWithManager(ctx, manager, package_json_contents, .default_no_progress), + .verbose_no_progress => installWithManager(ctx, manager, package_json_contents, .verbose_no_progress), + }; } const PackageInstaller = struct { @@ -6646,25 +6626,36 @@ pub const PackageManager = struct { switch (load_lockfile_result) { .err => |cause| { - switch (cause.step) { - .open_file => Output.prettyErrorln("<r><red>error opening lockfile:<r> {s}. <b>Ignoring lockfile<r>.", .{ - @errorName(cause.value), - }), - .parse_file => Output.prettyErrorln("<r><red>error parsing lockfile:<r> {s}. <b>Ignoring lockfile<r>.", .{ - @errorName(cause.value), - }), - .read_file => Output.prettyErrorln("<r><red>error reading lockfile:<r> {s}. <b>Ignoring lockfile<r>.", .{ - @errorName(cause.value), - }), - } - if (ctx.log.errors > 0) { - if (Output.enable_ansi_colors) { - try manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true); + if (log_level != .silent) { + switch (cause.step) { + .open_file => Output.prettyError("<r><red>error<r> opening lockfile:<r> {s}\n<r>", .{ + @errorName(cause.value), + }), + .parse_file => Output.prettyError("<r><red>error<r> parsing lockfile:<r> {s}\n<r>", .{ + @errorName(cause.value), + }), + .read_file => Output.prettyError("<r><red>error<r> reading lockfile:<r> {s}\n<r>", .{ + @errorName(cause.value), + }), + } + + if (manager.options.enable.fail_early) { + Output.prettyError("<b>Failed to load lockfile<r>\n", .{}); } else { - try manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false); + Output.prettyError("<b>Ignoring lockfile<r>\n", .{}); } + + if (ctx.log.errors > 0) { + if (Output.enable_ansi_colors) { + try manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), true); + } else { + try manager.log.printForLogLevelWithEnableAnsiColors(Output.errorWriter(), false); + } + } + Output.flush(); } - Output.flush(); + + if (manager.options.enable.fail_early) std.os.exit(1); }, .ok => { differ: { diff --git a/src/main.zig b/src/main.zig index 00c25c9f4..79a4755c0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -24,14 +24,22 @@ const cli = @import("cli.zig"); pub const MainPanicHandler = panicky.NewPanicHandler(std.builtin.default_panic); const js = @import("javascript/jsc/bindings/bindings.zig"); const JavaScript = @import("javascript/jsc/javascript.zig"); - pub const io_mode = .blocking; - +const Report = @import("./report.zig"); pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace) noreturn { MainPanicHandler.handle_panic(msg, error_return_trace); } + +const CrashReporter = @import("crash_reporter"); + +pub fn PLCrashReportHandler() void { + Report.fatal(null, null); +} + pub var start_time: i128 = 0; pub fn main() anyerror!void { + std.debug.assert(CrashReporter.start(Global.package_json_version)); + start_time = std.time.nanoTimestamp(); // The memory allocator makes a massive difference. @@ -58,7 +66,46 @@ pub fn main() anyerror!void { Output.flush(); std.os.exit(1); }, - else => return err, + error.FileNotFound => { + Output.prettyError( + "\n<r><red>error<r><d>:<r> <b>FileNotFound<r>\nbun could not find a file, and the code that produces this error is missing a better error.\n", + .{}, + ); + Output.flush(); + + Report.printMetadata(); + + Output.flush(); + + print_stacktrace: { + var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; + var trace = @errorReturnTrace() orelse break :print_stacktrace; + Output.disableBuffering(); + std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig()) catch break :print_stacktrace; + } + + std.os.exit(1); + }, + error.MissingPackageJSON => { + Output.prettyError( + "\n<r><red>error<r><d>:<r> <b>MissingPackageJSON<r>\nbun could not find a package.json file.\n", + .{}, + ); + Output.flush(); + std.os.exit(1); + }, + else => { + Report.fatal(err, null); + + print_stacktrace: { + var debug_info = std.debug.getSelfDebugInfo() catch break :print_stacktrace; + var trace = @errorReturnTrace() orelse break :print_stacktrace; + Output.disableBuffering(); + std.debug.writeStackTrace(trace.*, Output.errorWriter(), default_allocator, debug_info, std.debug.detectTTYConfig()) catch break :print_stacktrace; + } + + std.os.exit(1); + }, } }; @@ -76,3 +123,7 @@ test "" { std.mem.doNotOptimizeAway(JavaScriptVirtualMachine.init); std.mem.doNotOptimizeAway(JavaScriptVirtualMachine.resolve); } + +test "panic" { + panic("woah", null); +} diff --git a/src/panic_handler.zig b/src/panic_handler.zig index 8f3038fa1..d4a65c4a6 100644 --- a/src/panic_handler.zig +++ b/src/panic_handler.zig @@ -11,6 +11,10 @@ const MutableString = _global.MutableString; const stringZ = _global.stringZ; const default_allocator = _global.default_allocator; const C = _global.C; +const CLI = @import("./cli.zig").Cli; +const Features = @import("./analytics/analytics_thread.zig").Features; +const HTTP = @import("http").AsyncHTTP; +const Report = @import("./report.zig"); pub fn NewPanicHandler(comptime panic_func: fn handle_panic(msg: []const u8, error_return_type: ?*std.builtin.StackTrace) noreturn) type { return struct { @@ -30,22 +34,8 @@ pub fn NewPanicHandler(comptime panic_func: fn handle_panic(msg: []const u8, err // This exists to ensure we flush all buffered output before panicking. Output.flush(); - if (msg.len > 0) { - if (Output.isEmojiEnabled()) { - Output.prettyErrorln("<r><red>bun crashed ðŸ˜ðŸ˜ðŸ˜<r><d>: <r><b>{s}<r>\n", .{msg}); - } else { - Output.prettyErrorln("<r><red>Crash<r><d>:<r> <b>{s}<r>", .{msg}); - } - Output.flush(); - } else { - if (Output.isEmojiEnabled()) { - Output.prettyErrorln("<r><red>bun will crash now<r> ðŸ˜ðŸ˜ðŸ˜<r>\n", .{}); - Output.flush(); - } else { - Output.printError("bun has crashed :'(\n", .{}); - } - Output.flush(); - } + Report.fatal(null, msg); + Output.disableBuffering(); // // We want to always inline the panic handler so it doesn't show up in the stacktrace. diff --git a/src/report.zig b/src/report.zig new file mode 100644 index 000000000..7032fc9aa --- /dev/null +++ b/src/report.zig @@ -0,0 +1,168 @@ +const std = @import("std"); +const logger = @import("logger.zig"); +const root = @import("root"); +const _global = @import("global.zig"); +const string = _global.string; +const Output = _global.Output; +const Global = _global.Global; +const Environment = _global.Environment; +const strings = _global.strings; +const MutableString = _global.MutableString; +const stringZ = _global.stringZ; +const default_allocator = _global.default_allocator; +const C = _global.C; +const CLI = @import("./cli.zig").Cli; +const Features = @import("./analytics/analytics_thread.zig").Features; +const HTTP = @import("http").AsyncHTTP; +const CrashReporter = @import("crash_reporter"); + +var crash_reporter_path: [1024]u8 = undefined; +pub fn printMetadata() void { + @setCold(true); + const cmd_label: string = if (CLI.cmd) |tag| @tagName(tag) else "Unknown"; + + const platform = if (Environment.isMac) "macOS" else "Linux"; + const arch = if (Environment.isAarch64) + if (Environment.isMac) "Silicon" else "arm64" + else + "x64"; + + Output.prettyError( + \\ + \\<r>–––– bun meta –––– + ++ "\nBun v" ++ Global.package_json_version ++ " " ++ platform ++ " " ++ arch ++ "\n" ++ + \\{s}: {} + \\ + , .{ + cmd_label, + Features.formatter(), + }); + + const http_count = HTTP.active_requests_count.loadUnchecked(); + if (http_count > 0) + Output.prettyError( + \\HTTP: {d} + \\ + , .{http_count}); + + if (comptime _global.use_mimalloc) { + var elapsed_msecs: usize = 0; + var user_msecs: usize = 0; + var system_msecs: usize = 0; + var current_rss: usize = 0; + var peak_rss: usize = 0; + var current_commit: usize = 0; + var peak_commit: usize = 0; + var page_faults: usize = 0; + const mimalloc = @import("allocators/mimalloc.zig"); + mimalloc.mi_process_info( + &elapsed_msecs, + &user_msecs, + &system_msecs, + ¤t_rss, + &peak_rss, + ¤t_commit, + &peak_commit, + &page_faults, + ); + Output.prettyError("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\nRSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{ + elapsed_msecs, + user_msecs, + system_msecs, + std.fmt.fmtIntSizeDec(current_rss), + std.fmt.fmtIntSizeDec(peak_rss), + std.fmt.fmtIntSizeDec(current_commit), + page_faults, + }); + } + + Output.prettyError("–––– bun meta ––––\n", .{}); +} +var has_printed_fatal = false; +var has_printed_crash = false; +pub fn fatal(err_: ?anyerror, msg_: ?string) void { + const had_printed_fatal = has_printed_fatal; + if (!has_printed_fatal) { + has_printed_fatal = true; + + if (err_) |err| { + if (Output.isEmojiEnabled()) { + Output.prettyError( + "\n<r><red>error<r><d>:<r> <b>{s}<r>\n", + .{@errorName(err)}, + ); + } else { + Output.prettyError( + "\n<r>error: {s}\n\n", + .{@errorName(err)}, + ); + } + } + + if (msg_) |msg| { + const msg_ptr = @ptrToInt(msg.ptr); + if (msg_ptr > 0) { + const len = @maximum(@minimum(msg.len, 1024), 0); + + if (len > 0) { + if (Output.isEmojiEnabled()) { + Output.prettyError( + "\n<r><red>uh-oh<r><d>:<r> <b>{s}<r>\n", + .{msg[0..len]}, + ); + } else { + Output.prettyError( + "\n<r>an uh-oh: {s}\n\n", + .{msg[0..len]}, + ); + } + } + } + } + + if (err_ == null) { + if (Output.isEmojiEnabled()) { + if (msg_ == null and err_ == null) { + Output.prettyError("<r><red>", .{}); + } else { + Output.prettyError("<r>", .{}); + } + Output.prettyErrorln("bun will crash now<r> ðŸ˜ðŸ˜ðŸ˜\n", .{}); + } else { + Output.printError("bun has crashed :'(\n", .{}); + } + } + Output.flush(); + + printMetadata(); + + Output.flush(); + } + + // It only is a real crash report if it's not coming from Zig + if (err_ == null and msg_ == null and !has_printed_crash) { + var path = CrashReporter.crashReportPath(&crash_reporter_path); + + if (path.len > 0) { + has_printed_crash = true; + + if (std.os.getenvZ("HOME")) |home| { + if (strings.hasPrefix(path, home) and home.len > 1) { + crash_reporter_path[home.len - 1] = '~'; + crash_reporter_path[home.len] = '/'; + path = path[home.len - 1 ..]; + } + } + Output.prettyErrorln("Crash report saved to:\n {s}\n", .{path}); + if (!had_printed_fatal) Output.prettyError("Ask for #help in https://bun.sh/discord or go to https://bun.sh/issues. Please include the crash report. \n\n", .{}); + Output.flush(); + + std.os.exit(1); + } + } + + if (!had_printed_fatal) { + Output.prettyError("\nAsk for #help in https://bun.sh/discord or go to https://bun.sh/issues\n\n", .{}); + Output.flush(); + } +} |