forked from oven-sh/bun
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathcrash_handler.zig
1782 lines (1566 loc) · 69.6 KB
/
crash_handler.zig
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//! This file contains Bun's crash handler. In debug builds, we are able to
//! print backtraces that are mapped to source code. In release builds, we do
//! not have debug symbols in the binary. Bun's solution to this is called
//! a "trace string", a url with compressed encoding of the captured
//! backtrace. Version 1 trace strings contain the following information:
//!
//! - What version and commit of Bun captured the backtrace.
//! - The platform the backtrace was captured on.
//! - The list of addresses with ASLR removed, ready to be remapped.
//! - If panicking, the message that was panicked with.
//!
//! These can be demangled using Bun's remapping API, which has cached
//! versions of all debug symbols for all versions of Bun. Hosting this keeps
//! users from having to download symbols, which can be very large.
//!
//! The remapper is open source: https://github.com/oven-sh/bun.report
//!
//! A lot of this handler is based on the Zig Standard Library implementation
//! for std.debug.panicImpl and their code for gathering backtraces.
const std = @import("std");
const bun = @import("root").bun;
const builtin = @import("builtin");
const mimalloc = @import("allocators/mimalloc.zig");
const SourceMap = @import("./sourcemap/sourcemap.zig");
const windows = std.os.windows;
const Output = bun.Output;
const Global = bun.Global;
const Features = bun.Analytics.Features;
const debug = std.debug;
/// Set this to false if you want to disable all uses of this panic handler.
/// This is useful for testing as a crash in here will not 'panicked during a panic'.
pub const enable = true;
/// Overridable with BUN_CRASH_REPORT_URL environment variable.
const default_report_base_url = "https://bun.report";
/// Only print the `Bun has crashed` message once. Once this is true, control
/// flow is not returned to the main application.
var has_printed_message = false;
/// Non-zero whenever the program triggered a panic.
/// The counter is incremented/decremented atomically.
var panicking = std.atomic.Value(u8).init(0);
// Locked to avoid interleaving panic messages from multiple threads.
var panic_mutex = std.Thread.Mutex{};
/// Counts how many times the panic handler is invoked by this thread.
/// This is used to catch and handle panics triggered by the panic handler.
threadlocal var panic_stage: usize = 0;
/// This can be set by various parts of the codebase to indicate a broader
/// action being taken. It is printed when a crash happens, which can help
/// narrow down what the bug is. Example: "Crashed while parsing /path/to/file.js"
///
/// Some of these are enabled in release builds, which may encourage users to
/// attach the affected files to crash report. Others, which may have low crash
/// rate or only crash due to assertion failures, are debug-only. See `Action`.
pub threadlocal var current_action: ?Action = null;
var before_crash_handlers: std.ArrayListUnmanaged(struct { *anyopaque, *const OnBeforeCrash }) = .{};
var before_crash_handlers_mutex: std.Thread.Mutex = .{};
const CPUFeatures = @import("./bun.js/bindings/CPUFeatures.zig").CPUFeatures;
/// This structure and formatter must be kept in sync with `bun.report`'s decoder implementation.
pub const CrashReason = union(enum) {
/// From @panic()
panic: []const u8,
/// "reached unreachable code"
@"unreachable",
segmentation_fault: usize,
illegal_instruction: usize,
/// Posix-only
bus_error: usize,
/// Posix-only
floating_point_error: usize,
/// Windows-only
datatype_misalignment,
/// Windows-only
stack_overflow,
/// Either `main` returned an error, or somewhere else in the code a trace string is printed.
zig_error: anyerror,
out_of_memory,
pub fn format(reason: CrashReason, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
switch (reason) {
.panic => |message| try writer.print("{s}", .{message}),
.@"unreachable" => try writer.writeAll("reached unreachable code"),
.segmentation_fault => |addr| try writer.print("Segmentation fault at address 0x{X}", .{addr}),
.illegal_instruction => |addr| try writer.print("Illegal instruction at address 0x{X}", .{addr}),
.bus_error => |addr| try writer.print("Bus error at address 0x{X}", .{addr}),
.floating_point_error => |addr| try writer.print("Floating point error at address 0x{X}", .{addr}),
.datatype_misalignment => try writer.writeAll("Unaligned memory access"),
.stack_overflow => try writer.writeAll("Stack overflow"),
.zig_error => |err| try writer.print("error.{s}", .{@errorName(err)}),
.out_of_memory => try writer.writeAll("Bun ran out of memory"),
}
}
};
pub const Action = union(enum) {
parse: []const u8,
visit: []const u8,
print: []const u8,
/// bun.bundle_v2.LinkerContext.generateCompileResultForJSChunk
bundle_generate_chunk: if (bun.Environment.isDebug) struct {
context: *const anyopaque, // unfortunate dependency loop workaround
chunk: *const bun.bundle_v2.Chunk,
part_range: *const bun.bundle_v2.PartRange,
pub fn linkerContext(data: *const @This()) *const bun.bundle_v2.LinkerContext {
return @ptrCast(@alignCast(data.context));
}
} else void,
resolver: if (bun.Environment.isDebug) struct {
source_dir: []const u8,
import_path: []const u8,
kind: bun.ImportKind,
} else void,
pub fn format(act: Action, comptime _: []const u8, _: std.fmt.FormatOptions, writer: anytype) !void {
switch (act) {
.parse => |path| try writer.print("parsing {s}", .{path}),
.visit => |path| try writer.print("visiting {s}", .{path}),
.print => |path| try writer.print("printing {s}", .{path}),
.bundle_generate_chunk => |data| if (bun.Environment.isDebug) {
try writer.print(
\\generating bundler chunk
\\ chunk entry point: {?s}
\\ source: {?s}
\\ part range: {d}..{d}
,
.{
if (data.part_range.source_index.isValid()) data.linkerContext().parse_graph.input_files
.items(.source)[data.chunk.entry_point.source_index]
.path.text else null,
if (data.part_range.source_index.isValid()) data.linkerContext().parse_graph.input_files
.items(.source)[data.part_range.source_index.get()]
.path.text else null,
data.part_range.part_index_begin,
data.part_range.part_index_end,
},
);
},
.resolver => |res| if (bun.Environment.isDebug) {
try writer.print("resolving {s} from {s} ({s})", .{
res.import_path,
res.source_dir,
res.kind.label(),
});
},
}
}
};
/// This function is invoked when a crash happens. A crash is classified in `CrashReason`.
pub fn crashHandler(
reason: CrashReason,
// TODO: if both of these are specified, what is supposed to happen?
error_return_trace: ?*std.builtin.StackTrace,
begin_addr: ?usize,
) noreturn {
@setCold(true);
if (bun.Environment.isDebug)
bun.Output.disableScopedDebugWriter();
var trace_str_buf = std.BoundedArray(u8, 1024){};
nosuspend switch (panic_stage) {
0 => {
bun.maybeHandlePanicDuringProcessReload();
panic_stage = 1;
_ = panicking.fetchAdd(1, .seq_cst);
if (before_crash_handlers_mutex.tryLock()) {
for (before_crash_handlers.items) |item| {
const ptr, const cb = item;
cb(ptr);
}
}
{
panic_mutex.lock();
defer panic_mutex.unlock();
// Use an raw unbuffered writer to stderr to avoid losing information on
// panic in a panic. There is also a possibility that `Output` related code
// is not configured correctly, so that would also mask the message.
//
// Output.errorWriter() is not used here because it may not be configured
// if the program crashes immediately at startup.
const writer = std.io.getStdErr().writer();
// The format of the panic trace is slightly different in debug
// builds. Mainly, we demangle the backtrace immediately instead
// of using a trace string.
//
// To make the release-mode behavior easier to demo, debug mode
// checks for this CLI flag.
const debug_trace = bun.Environment.isDebug and check_flag: {
for (bun.argv) |arg| {
if (bun.strings.eqlComptime(arg, "--debug-crash-handler-use-trace-string")) {
break :check_flag false;
}
}
// Act like release build when explicitly enabling reporting
if (isReportingEnabled()) break :check_flag false;
break :check_flag true;
};
if (!has_printed_message) {
Output.flush();
Output.Source.Stdio.restore();
writer.writeAll("=" ** 60 ++ "\n") catch std.posix.abort();
printMetadata(writer) catch std.posix.abort();
} else {
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<red>", true)) catch std.posix.abort();
}
writer.writeAll("oh no") catch std.posix.abort();
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<r><d>: multiple threads are crashing<r>\n", true)) catch std.posix.abort();
} else {
writer.writeAll(Output.prettyFmt(": multiple threads are crashing\n", true)) catch std.posix.abort();
}
}
if (reason != .out_of_memory or debug_trace) {
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<red>", true)) catch std.posix.abort();
}
writer.writeAll("panic") catch std.posix.abort();
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<r><d>", true)) catch std.posix.abort();
}
if (bun.CLI.Cli.is_main_thread) {
writer.writeAll("(main thread)") catch std.posix.abort();
} else switch (bun.Environment.os) {
.windows => {
var name: std.os.windows.PWSTR = undefined;
const result = bun.windows.GetThreadDescription(std.os.windows.kernel32.GetCurrentThread(), &name);
if (std.os.windows.HRESULT_CODE(result) == .SUCCESS and name[0] != 0) {
writer.print("({})", .{bun.fmt.utf16(bun.span(name))}) catch std.posix.abort();
} else {
writer.print("(thread {d})", .{std.os.windows.kernel32.GetCurrentThreadId()}) catch std.posix.abort();
}
},
.mac, .linux => {},
else => @compileError("TODO"),
}
writer.writeAll(": ") catch std.posix.abort();
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<r>", true)) catch std.posix.abort();
}
writer.print("{}\n", .{reason}) catch std.posix.abort();
}
if (current_action) |action| {
writer.print("Crashed while {}\n", .{action}) catch std.posix.abort();
}
var addr_buf: [10]usize = undefined;
var trace_buf: std.builtin.StackTrace = undefined;
// If a trace was not provided, compute one now
const trace = error_return_trace orelse get_backtrace: {
trace_buf = std.builtin.StackTrace{
.index = 0,
.instruction_addresses = &addr_buf,
};
std.debug.captureStackTrace(begin_addr orelse @returnAddress(), &trace_buf);
break :get_backtrace &trace_buf;
};
if (debug_trace) {
has_printed_message = true;
dumpStackTrace(trace.*);
trace_str_buf.writer().print("{}", .{TraceString{
.trace = trace,
.reason = reason,
.action = .view_trace,
}}) catch std.posix.abort();
} else {
if (!has_printed_message) {
has_printed_message = true;
writer.writeAll("oh no") catch std.posix.abort();
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<r><d>:<r> ", true)) catch std.posix.abort();
} else {
writer.writeAll(Output.prettyFmt(": ", true)) catch std.posix.abort();
}
if (reason == .out_of_memory) {
writer.writeAll(
\\Bun has ran out of memory.
\\
\\To send a redacted crash report to Bun's team,
\\please file a GitHub issue using the link below:
\\
\\
) catch std.posix.abort();
} else {
writer.writeAll(
\\Bun has crashed. This indicates a bug in Bun, not your code.
\\
\\To send a redacted crash report to Bun's team,
\\please file a GitHub issue using the link below:
\\
\\
) catch std.posix.abort();
}
}
if (Output.enable_ansi_colors) {
writer.print(Output.prettyFmt("<cyan>", true), .{}) catch std.posix.abort();
}
writer.writeAll(" ") catch std.posix.abort();
trace_str_buf.writer().print("{}", .{TraceString{
.trace = trace,
.reason = reason,
.action = .open_issue,
}}) catch std.posix.abort();
writer.writeAll(trace_str_buf.slice()) catch std.posix.abort();
writer.writeAll("\n") catch std.posix.abort();
}
if (Output.enable_ansi_colors) {
writer.writeAll(Output.prettyFmt("<r>\n", true)) catch std.posix.abort();
} else {
writer.writeAll("\n") catch std.posix.abort();
}
}
// Be aware that this function only lets one thread return from it.
// This is important so that we do not try to run the following reload logic twice.
waitForOtherThreadToFinishPanicking();
report(trace_str_buf.slice());
// At this point, the crash handler has performed it's job. Reset the segfault handler
// so that a crash will actually crash. We need this because we want the process to
// exit with a signal, and allow tools to be able to gather core dumps.
//
// This is done so late (in comparison to the Zig Standard Library's panic handler)
// because if multiple threads segfault (more often the case on Windows), we don't
// want another thread to interrupt the crashing of the first one.
resetSegfaultHandler();
if (bun.auto_reload_on_crash and
// Do not reload if the panic arose FROM the reload function.
!bun.isProcessReloadInProgressOnAnotherThread())
{
// attempt to prevent a double panic
bun.auto_reload_on_crash = false;
Output.prettyErrorln("<d>--- Bun is auto-restarting due to crash <d>[time: <b>{d}<r><d>] ---<r>", .{
@max(std.time.milliTimestamp(), 0),
});
Output.flush();
comptime bun.assert(void == @TypeOf(bun.reloadProcess(bun.default_allocator, false, true)));
bun.reloadProcess(bun.default_allocator, false, true);
}
},
inline 1, 2 => |t| {
if (t == 1) {
panic_stage = 2;
resetSegfaultHandler();
Output.flush();
}
panic_stage = 3;
// A panic happened while trying to print a previous panic message,
// we're still holding the mutex but that's fine as we're going to
// call abort()
const stderr = std.io.getStdErr().writer();
stderr.print("\npanic: {s}\n", .{reason}) catch std.posix.abort();
stderr.print("panicked during a panic. Aborting.\n", .{}) catch std.posix.abort();
},
3 => {
// Panicked while printing "Panicked during a panic."
panic_stage = 4;
},
else => {
// Panicked or otherwise looped into the panic handler while trying to exit.
std.posix.abort();
},
};
crash();
}
/// This is called when `main` returns a Zig error.
/// We don't want to treat it as a crash under certain error codes.
pub fn handleRootError(err: anyerror, error_return_trace: ?*std.builtin.StackTrace) noreturn {
var show_trace = bun.Environment.isDebug;
switch (err) {
error.OutOfMemory => bun.outOfMemory(),
error.InvalidArgument,
error.@"Invalid Bunfig",
error.InstallFailed,
=> if (!show_trace) Global.exit(1),
error.SyntaxError => {
Output.err("SyntaxError", "An error occurred while parsing code", .{});
},
error.CurrentWorkingDirectoryUnlinked => {
Output.errGeneric(
"The current working directory was deleted, so that command didn't work. Please cd into a different directory and try again.",
.{},
);
},
error.SystemFdQuotaExceeded => {
if (comptime bun.Environment.isPosix) {
const limit = if (std.posix.getrlimit(.NOFILE)) |limit| limit.cur else |_| null;
if (comptime bun.Environment.isMac) {
Output.prettyError(
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
\\
\\<d>Current limit: {d}<r>
\\
\\To fix this, try running:
\\
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
\\ <cyan>ulimit -n 2147483646<r>
\\
\\That will only work until you reboot.
\\
,
.{
bun.fmt.nullableFallback(limit, "<unknown>"),
},
);
} else {
Output.prettyError(
\\
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
\\
\\<d>Current limit: {d}<r>
\\
\\To fix this, try running:
\\
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
\\ <cyan>sudo sysctl -p<r>
\\ <cyan>ulimit -n 2147483646<r>
\\
,
.{
bun.fmt.nullableFallback(limit, "<unknown>"),
},
);
if (bun.getenvZ("USER")) |user| {
if (user.len > 0) {
Output.prettyError(
\\
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
\\
\\ <cyan>{s} soft nofile 2147483646<r>
\\ <cyan>{s} hard nofile 2147483646<r>
\\
,
.{ user, user },
);
}
}
}
} else {
Output.prettyError(
\\<r><red>error<r>: Your computer ran out of file descriptors <d>(<red>SystemFdQuotaExceeded<r><d>)<r>
,
.{},
);
}
},
error.ProcessFdQuotaExceeded => {
if (comptime bun.Environment.isPosix) {
const limit = if (std.posix.getrlimit(.NOFILE)) |limit| limit.cur else |_| null;
if (comptime bun.Environment.isMac) {
Output.prettyError(
\\
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
\\
\\<d>Current limit: {d}<r>
\\
\\To fix this, try running:
\\
\\ <cyan>ulimit -n 2147483646<r>
\\
\\You may also need to run:
\\
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
\\
,
.{
bun.fmt.nullableFallback(limit, "<unknown>"),
},
);
} else {
Output.prettyError(
\\
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
\\
\\<d>Current limit: {d}<r>
\\
\\To fix this, try running:
\\
\\ <cyan>ulimit -n 2147483646<r>
\\
\\That will only work for the current shell. To fix this for the entire system, run:
\\
\\ <cyan>sudo echo -e "\nfs.file-max=2147483646\n" >> /etc/sysctl.conf<r>
\\ <cyan>sudo sysctl -p<r>
\\
,
.{
bun.fmt.nullableFallback(limit, "<unknown>"),
},
);
if (bun.getenvZ("USER")) |user| {
if (user.len > 0) {
Output.prettyError(
\\
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
\\
\\ <cyan>{s} soft nofile 2147483646<r>
\\ <cyan>{s} hard nofile 2147483646<r>
\\
,
.{ user, user },
);
}
}
}
} else {
Output.prettyErrorln(
\\<r><red>error<r>: bun ran out of file descriptors <d>(<red>ProcessFdQuotaExceeded<r><d>)<r>
,
.{},
);
}
},
// The usage of `unreachable` in Zig's std.posix may cause the file descriptor problem to show up as other errors
error.NotOpenForReading, error.Unexpected => {
if (comptime bun.Environment.isPosix) {
const limit = std.posix.getrlimit(.NOFILE) catch std.mem.zeroes(std.posix.rlimit);
if (limit.cur > 0 and limit.cur < (8192 * 2)) {
Output.prettyError(
\\
\\<r><red>error<r>: An unknown error occurred, possibly due to low max file descriptors <d>(<red>Unexpected<r><d>)<r>
\\
\\<d>Current limit: {d}<r>
\\
\\To fix this, try running:
\\
\\ <cyan>ulimit -n 2147483646<r>
\\
,
.{
limit.cur,
},
);
if (bun.Environment.isLinux) {
if (bun.getenvZ("USER")) |user| {
if (user.len > 0) {
Output.prettyError(
\\
\\If that still doesn't work, you may need to add these lines to /etc/security/limits.conf:
\\
\\ <cyan>{s} soft nofile 2147483646<r>
\\ <cyan>{s} hard nofile 2147483646<r>
\\
,
.{
user,
user,
},
);
}
}
} else if (bun.Environment.isMac) {
Output.prettyError(
\\
\\If that still doesn't work, you may need to run:
\\
\\ <cyan>sudo launchctl limit maxfiles 2147483646<r>
\\
,
.{},
);
}
} else {
Output.errGeneric(
"An unknown error occurred <d>(<red>{s}<r><d>)<r>",
.{@errorName(err)},
);
show_trace = true;
}
} else {
Output.errGeneric(
\\An unknown error occurred <d>(<red>{s}<r><d>)<r>
,
.{@errorName(err)},
);
show_trace = true;
}
},
error.ENOENT, error.FileNotFound => {
Output.err(
"ENOENT",
"Bun could not find a file, and the code that produces this error is missing a better error.",
.{},
);
},
error.MissingPackageJSON => {
Output.errGeneric(
"Bun could not find a package.json file to install from",
.{},
);
Output.note("Run \"bun init\" to initialize a project", .{});
},
else => {
Output.errGeneric(
if (bun.Environment.isDebug)
"'main' returned <red>error.{s}<r>"
else
"An internal error occurred (<red>{s}<r>)",
.{@errorName(err)},
);
show_trace = true;
},
}
if (show_trace) {
verbose_error_trace = show_trace;
handleErrorReturnTraceExtra(err, error_return_trace, true);
}
Global.exit(1);
}
pub fn panicImpl(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn {
@setCold(true);
crashHandler(
if (bun.strings.eqlComptime(msg, "reached unreachable code"))
.{ .@"unreachable" = {} }
else
.{ .panic = msg },
error_return_trace,
begin_addr orelse @returnAddress(),
);
}
fn panicBuiltin(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, begin_addr: ?usize) noreturn {
std.debug.panicImpl(error_return_trace, begin_addr, msg);
}
pub const panic = if (enable) panicImpl else panicBuiltin;
pub fn reportBaseUrl() []const u8 {
const static = struct {
var base_url: ?[]const u8 = null;
};
return static.base_url orelse {
const computed = computed: {
if (bun.getenvZ("BUN_CRASH_REPORT_URL")) |url| {
break :computed bun.strings.withoutTrailingSlash(url);
}
break :computed default_report_base_url;
};
static.base_url = computed;
return computed;
};
}
const arch_display_string = if (bun.Environment.isAarch64)
if (bun.Environment.isMac) "Silicon" else "arm64"
else
"x64";
const metadata_version_line = std.fmt.comptimePrint(
"Bun {s}v{s} {s} {s}{s}\n",
.{
if (bun.Environment.isDebug) "Debug " else if (bun.Environment.is_canary) "Canary " else "",
Global.package_json_version_with_sha,
bun.Environment.os.displayString(),
arch_display_string,
if (bun.Environment.baseline) " (baseline)" else "",
},
);
fn handleSegfaultPosix(sig: i32, info: *const std.posix.siginfo_t, _: ?*const anyopaque) callconv(.C) noreturn {
const addr = switch (bun.Environment.os) {
.linux => @intFromPtr(info.fields.sigfault.addr),
.mac => @intFromPtr(info.addr),
else => @compileError(unreachable),
};
crashHandler(
switch (sig) {
std.posix.SIG.SEGV => .{ .segmentation_fault = addr },
std.posix.SIG.ILL => .{ .illegal_instruction = addr },
std.posix.SIG.BUS => .{ .bus_error = addr },
std.posix.SIG.FPE => .{ .floating_point_error = addr },
// we do not register this handler for other signals
else => unreachable,
},
null,
@returnAddress(),
);
}
var did_register_sigaltstack = false;
var sigaltstack: [512 * 1024]u8 = undefined;
pub fn updatePosixSegfaultHandler(act: ?*std.posix.Sigaction) !void {
if (act) |act_| {
if (!did_register_sigaltstack) {
var stack: std.c.stack_t = .{
.flags = 0,
.size = sigaltstack.len,
.sp = &sigaltstack,
};
if (std.c.sigaltstack(&stack, null) == 0) {
act_.flags |= std.posix.SA.ONSTACK;
did_register_sigaltstack = true;
}
}
}
try std.posix.sigaction(std.posix.SIG.SEGV, act, null);
try std.posix.sigaction(std.posix.SIG.ILL, act, null);
try std.posix.sigaction(std.posix.SIG.BUS, act, null);
try std.posix.sigaction(std.posix.SIG.FPE, act, null);
}
var windows_segfault_handle: ?windows.HANDLE = null;
pub fn resetOnPosix() void {
var act = std.posix.Sigaction{
.handler = .{ .sigaction = handleSegfaultPosix },
.mask = std.posix.empty_sigset,
.flags = (std.posix.SA.SIGINFO | std.posix.SA.RESTART | std.posix.SA.RESETHAND),
};
updatePosixSegfaultHandler(&act) catch {};
}
pub fn init() void {
if (!enable) return;
switch (bun.Environment.os) {
.windows => {
windows_segfault_handle = windows.kernel32.AddVectoredExceptionHandler(0, handleSegfaultWindows);
},
.mac, .linux => {
resetOnPosix();
},
else => @compileError("TODO"),
}
}
pub fn resetSegfaultHandler() void {
if (!enable) return;
if (bun.Environment.os == .windows) {
if (windows_segfault_handle) |handle| {
const rc = windows.kernel32.RemoveVectoredExceptionHandler(handle);
windows_segfault_handle = null;
bun.assert(rc != 0);
}
return;
}
var act = std.posix.Sigaction{
.handler = .{ .handler = std.posix.SIG.DFL },
.mask = std.posix.empty_sigset,
.flags = 0,
};
// To avoid a double-panic, do nothing if an error happens here.
updatePosixSegfaultHandler(&act) catch {};
}
pub fn handleSegfaultWindows(info: *windows.EXCEPTION_POINTERS) callconv(windows.WINAPI) c_long {
crashHandler(
switch (info.ExceptionRecord.ExceptionCode) {
windows.EXCEPTION_DATATYPE_MISALIGNMENT => .{ .datatype_misalignment = {} },
windows.EXCEPTION_ACCESS_VIOLATION => .{ .segmentation_fault = info.ExceptionRecord.ExceptionInformation[1] },
windows.EXCEPTION_ILLEGAL_INSTRUCTION => .{ .illegal_instruction = info.ContextRecord.getRegs().ip },
windows.EXCEPTION_STACK_OVERFLOW => .{ .stack_overflow = {} },
// exception used for thread naming
// https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception
// related commit
// https://github.com/go-delve/delve/pull/1384
bun.windows.MS_VC_EXCEPTION => return bun.windows.EXCEPTION_CONTINUE_EXECUTION,
else => return windows.EXCEPTION_CONTINUE_SEARCH,
},
null,
@intFromPtr(info.ExceptionRecord.ExceptionAddress),
);
}
extern "C" fn gnu_get_libc_version() ?[*:0]const u8;
pub fn printMetadata(writer: anytype) !void {
if (Output.enable_ansi_colors) {
try writer.writeAll(Output.prettyFmt("<r><d>", true));
}
var is_ancient_cpu = false;
try writer.writeAll(metadata_version_line);
{
const platform = bun.Analytics.GenerateHeader.GeneratePlatform.forOS();
const cpu_features = CPUFeatures.get();
if (bun.Environment.isLinux and !bun.Environment.isMusl) {
const version = gnu_get_libc_version() orelse "";
const kernel_version = bun.Analytics.GenerateHeader.GeneratePlatform.kernelVersion();
if (platform.os == .wsl) {
try writer.print("WSL Kernel v{d}.{d}.{d} | glibc v{s}\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch, bun.sliceTo(version, 0) });
} else {
try writer.print("Linux Kernel v{d}.{d}.{d} | glibc v{s}\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch, bun.sliceTo(version, 0) });
}
} else if (bun.Environment.isLinux and bun.Environment.isMusl) {
const kernel_version = bun.Analytics.GenerateHeader.GeneratePlatform.kernelVersion();
try writer.print("Linux Kernel v{d}.{d}.{d} | musl\n", .{ kernel_version.major, kernel_version.minor, kernel_version.patch });
} else if (bun.Environment.isMac) {
try writer.print("macOS v{s}\n", .{platform.version});
} else if (bun.Environment.isWindows) {
try writer.print("Windows v{s}\n", .{std.zig.system.windows.detectRuntimeVersion()});
}
if (comptime bun.Environment.isX64) {
if (!cpu_features.avx and !cpu_features.avx2 and !cpu_features.avx512) {
is_ancient_cpu = true;
}
}
if (!cpu_features.isEmpty()) {
try writer.print("CPU: {}\n", .{cpu_features});
}
try writer.print("Args: ", .{});
var arg_chars_left: usize = if (bun.Environment.isDebug) 4096 else 196;
for (bun.argv, 0..) |arg, i| {
if (i != 0) try writer.writeAll(" ");
try bun.fmt.quotedWriter(writer, arg[0..@min(arg.len, arg_chars_left)]);
arg_chars_left -|= arg.len;
if (arg_chars_left == 0) {
try writer.writeAll("...");
break;
}
}
}
try writer.print("\n{}", .{bun.Analytics.Features.formatter()});
if (bun.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;
mimalloc.mi_process_info(
&elapsed_msecs,
&user_msecs,
&system_msecs,
¤t_rss,
&peak_rss,
¤t_commit,
&peak_commit,
&page_faults,
);
try writer.print("Elapsed: {d}ms | User: {d}ms | Sys: {d}ms\n", .{
elapsed_msecs,
user_msecs,
system_msecs,
});
try writer.print("RSS: {:<3.2} | Peak: {:<3.2} | Commit: {:<3.2} | Faults: {d}\n", .{
std.fmt.fmtIntSizeDec(current_rss),
std.fmt.fmtIntSizeDec(peak_rss),
std.fmt.fmtIntSizeDec(current_commit),
page_faults,
});
}
if (Output.enable_ansi_colors) {
try writer.writeAll(Output.prettyFmt("<r>", true));
}
try writer.writeAll("\n");
if (comptime bun.Environment.isX64) {
if (is_ancient_cpu) {
try writer.writeAll("CPU lacks AVX support. Please consider upgrading to a newer CPU.\n");
}
}
}
fn waitForOtherThreadToFinishPanicking() void {
if (panicking.fetchSub(1, .seq_cst) != 1) {
// Another thread is panicking, wait for the last one to finish
// and call abort()
if (builtin.single_threaded) unreachable;
// Sleep forever without hammering the CPU
var futex = std.atomic.Value(u32).init(0);
while (true) std.Thread.Futex.wait(&futex, 0);
comptime unreachable;
}
}
/// This is to be called by any thread that is attempting to exit the process.
/// If another thread is panicking, this will sleep this thread forever, under
/// the assumption that the crash handler will terminate the program.
///
/// There have been situations in the past where a bundler thread starts
/// panicking, but the main thread ends up marking a test as passing and then
/// exiting with code zero before the crash handler can finish the crash.
pub fn sleepForeverIfAnotherThreadIsCrashing() void {
if (panicking.load(.acquire) > 0) {
// Sleep forever without hammering the CPU
var futex = std.atomic.Value(u32).init(0);
while (true) std.Thread.Futex.wait(&futex, 0);
comptime unreachable;
}
}
/// Each platform is encoded as a single character. It is placed right after the
/// slash after the version, so someone just reading the trace string can tell
/// what platform it came from. L, M, and W are for Linux, macOS, and Windows,
/// with capital letters indicating aarch64, lowercase indicating x86_64.
///
/// eg: 'https://bun.report/1.1.3/we04c...
// ^ this tells you it is windows x86_64
///
/// Baseline gets a weirder encoding of a mix of b and e.
const Platform = enum(u8) {
linux_x86_64 = 'l',
linux_x86_64_baseline = 'B',
linux_aarch64 = 'L',
mac_x86_64_baseline = 'b',
mac_x86_64 = 'm',
mac_aarch64 = 'M',
windows_x86_64 = 'w',
windows_x86_64_baseline = 'e',
const current = @field(Platform, @tagName(bun.Environment.os) ++
"_" ++ @tagName(builtin.target.cpu.arch) ++
(if (bun.Environment.baseline) "_baseline" else ""));
};
/// Note to the decoder on how to process this string. This ensures backwards
/// compatibility with older versions of the tracestring.
///
/// '1' - original. uses 7 char hash with VLQ encoded stack-frames
/// '2' - same as '1' but this build is known to be a canary build
const version_char = if (bun.Environment.is_canary)
"2"
else