aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-01-03 22:17:34 -0800
committerGravatar Jarred Sumner <jarred@jarredsumner.com> 2022-01-03 22:17:34 -0800
commit0960f3d6d1b6460e1a7a4dcec4921d2cf664df72 (patch)
tree6928d162d1c2a9fac43fd7fdbc6c9cebb8cfa63c
parent64b49ddd951e4e94978497302cd73e8ce8114010 (diff)
downloadbun-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--.gitignore2
-rw-r--r--Makefile28
-rw-r--r--build.zig24
-rw-r--r--src/allocators/mimalloc.zig2
-rw-r--r--src/analytics/analytics_thread.zig35
-rw-r--r--src/cli.zig4
-rw-r--r--src/deps/PLCrashReport.bindings.h10
-rw-r--r--src/deps/PLCrashReport.m75
-rw-r--r--src/deps/PLCrashReport.zig42
-rw-r--r--src/global.zig2
-rw-r--r--src/install/install.zig99
-rw-r--r--src/main.zig57
-rw-r--r--src/panic_handler.zig22
-rw-r--r--src/report.zig168
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/
diff --git a/Makefile b/Makefile
index a80aece6d..5cca96c1d 100644
--- a/Makefile
+++ b/Makefile
@@ -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 ""); \
diff --git a/build.zig b/build.zig
index d4818549f..7bb6a7b2f 100644
--- a/build.zig
+++ b/build.zig
@@ -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,
+ &current_rss,
+ &peak_rss,
+ &current_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();
+ }
+}