MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1#include <compat.h> // IWYU pragma: keep
2
3#include <stdbool.h>
4#include <stdint.h>
5#include <signal.h>
6#include <stdio.h>
7#include <stdlib.h>
8#include <string.h>
9#include <time.h>
10#include <crprintf.h>
11
12#include "ant.h"
13#include "crash.h"
14#include "internal.h"
15#include "reactor.h"
16#include "cli/version.h"
17
18#include "silver/engine.h"
19#include "modules/assert.h"
20#include "modules/fetch.h"
21#include "modules/json.h"
22
23#define ANT_CRASH_FRAME_MAX 24
24#define ANT_CRASH_ALT_STACK_SIZE 65536
25#define ANT_CRASH_ARGV_MAX 1024
26#define ANT_CRASH_EXE_PATH_MAX 1024
27#define ANT_CRASH_PAYLOAD_MAX 32768
28
29static char crash_argv[ANT_CRASH_ARGV_MAX] = "";
30static char crash_exe_path[ANT_CRASH_EXE_PATH_MAX] = "";
31
32static bool crash_print_trace = false;
33static bool crash_reporting_enabled = true;
34static bool crash_report_status_printed = false;
35static bool crash_report_status_inline = false;
36
37static uint64_t crash_start_ms = 0;
38static volatile sig_atomic_t crash_reporting_suppressed = 0;
39
40static bool should_upload_report(void) {
41 return
42 crash_reporting_enabled &&
43 crash_reporting_suppressed == 0;
44}
45
46bool ant_crash_is_internal_report(int argc, char **argv) {
47 return
48 argc >= 2 && argv &&
49 argv[1] && strcmp(argv[1], "__internal-crash-report") == 0;
50}
51
52#ifdef _WIN32
53#include <dbghelp.h>
54#include <io.h>
55#else
56#include <limits.h>
57#include <dlfcn.h>
58#include <fcntl.h>
59#include <unistd.h>
60#include <sys/resource.h>
61#include <sys/wait.h>
62
63#ifdef __APPLE__
64#include <mach-o/dyld.h>
65#include <sys/sysctl.h>
66#elif defined(__linux__)
67#include <sys/utsname.h>
68#endif
69
70#if defined(__APPLE__) || defined(__GLIBC__)
71#define ANT_CRASH_HAVE_EXECINFO 1
72#include <execinfo.h>
73#endif
74
75#if !defined(ANT_CRASH_HAVE_EXECINFO) && defined(__has_include)
76#if __has_include(<unwind.h>)
77#define ANT_CRASH_HAVE_UNWIND_BACKTRACE 1
78#include <unwind.h>
79#endif
80#endif
81#endif
82
83#ifdef ANT_CRASH_HAVE_UNWIND_BACKTRACE
84typedef struct {
85 uintptr_t *frames;
86 int frame_count;
87 int skip;
88} crash_unwind_state_t;
89
90static _Unwind_Reason_Code collect_unwind_frame(struct _Unwind_Context *ctx, void *arg) {
91 crash_unwind_state_t *state = arg;
92 uintptr_t ip = (uintptr_t)_Unwind_GetIP(ctx);
93 if (!ip) return _URC_NO_REASON;
94 if (state->skip > 0) {
95 state->skip--;
96 return _URC_NO_REASON;
97 }
98 if (state->frame_count >= ANT_CRASH_FRAME_MAX) return _URC_END_OF_STACK;
99 state->frames[state->frame_count++] = ip;
100 return _URC_NO_REASON;
101}
102#endif
103
104typedef struct {
105 char *buf;
106 size_t cap;
107 size_t len;
108} crash_buf_t;
109
110static void cb_putc(crash_buf_t *b, char c) {
111 if (b->len + 1 >= b->cap) return;
112 b->buf[b->len++] = c;
113 b->buf[b->len] = '\0';
114}
115
116static void cb_puts(crash_buf_t *b, const char *s) {
117 if (!s) return;
118 while (*s && b->len + 1 < b->cap) b->buf[b->len++] = *s++;
119 if (b->cap) b->buf[b->len] = '\0';
120}
121
122static void cb_put_uint(crash_buf_t *b, unsigned long long v) {
123 char tmp[32];
124 int i = (int)sizeof(tmp);
125 tmp[--i] = '\0';
126 if (v == 0) tmp[--i] = '0';
127 while (v && i > 0) {
128 tmp[--i] = (char)('0' + (v % 10));
129 v /= 10;
130 }
131 cb_puts(b, &tmp[i]);
132}
133
134static void cb_put_hex(crash_buf_t *b, uint64_t v) {
135 char tmp[32];
136 int i = (int)sizeof(tmp);
137 tmp[--i] = '\0';
138 if (v == 0) tmp[--i] = '0';
139 while (v && i > 0) {
140 unsigned d = (unsigned)(v & 0xf);
141 tmp[--i] = (char)(d < 10 ? '0' + d : 'a' + d - 10);
142 v >>= 4;
143 }
144 cb_puts(b, "0x");
145 cb_puts(b, &tmp[i]);
146}
147
148static void cb_put_json_string(crash_buf_t *b, const char *s) {
149 static const char hexdigits[] = "0123456789abcdef";
150 cb_putc(b, '"');
151
152 if (s) for (const unsigned char *p = (const unsigned char *)s; *p; p++) {
153 switch (*p) {
154 case '"': cb_puts(b, "\\\""); break;
155 case '\\': cb_puts(b, "\\\\"); break;
156 case '\b': cb_puts(b, "\\b"); break;
157 case '\f': cb_puts(b, "\\f"); break;
158 case '\n': cb_puts(b, "\\n"); break;
159 case '\r': cb_puts(b, "\\r"); break;
160 case '\t': cb_puts(b, "\\t"); break;
161 default: if (*p < 0x20) {
162 cb_puts(b, "\\u00");
163 cb_putc(b, hexdigits[*p >> 4]);
164 cb_putc(b, hexdigits[*p & 0xf]);
165 } else cb_putc(b, (char)*p);
166 }}
167
168 cb_putc(b, '"');
169}
170
171static bool env_bool(const char *value, bool default_value) {
172 if (!value || !*value) return default_value;
173 if (
174 strcmp(value, "0") == 0 ||
175 strcmp(value, "false") == 0 ||
176 strcmp(value, "FALSE") == 0 ||
177 strcmp(value, "off") == 0 ||
178 strcmp(value, "OFF") == 0 ||
179 strcmp(value, "no") == 0 ||
180 strcmp(value, "NO") == 0
181 ) return false;
182 return true;
183}
184
185static const char *path_basename_const(const char *path) {
186 if (!path) return "";
187 const char *base = path;
188 for (const char *p = path; *p; p++) {
189 if (*p == '/' || *p == '\\') base = p + 1;
190 }
191 return base;
192}
193
194static void init_argv_strings(int argc, char **argv) {
195 crash_buf_t payload = { crash_argv, sizeof(crash_argv), 0 };
196 int limit = argc < 8 ? argc : 8;
197 for (int i = 0; i < limit; i++) {
198 if (i) cb_putc(&payload, ',');
199 cb_put_json_string(&payload, argv[i] ? path_basename_const(argv[i]) : "");
200 }
201 if (argc > limit) {
202 if (limit > 0) cb_putc(&payload, ',');
203 cb_put_json_string(&payload, "...");
204 }
205}
206
207static void init_report_controls() {
208 crash_print_trace = env_bool(getenv("ANT_CRASH_TRACE"), false);
209 const char *enabled = getenv("ANT_ENABLE_CRASH_REPORTING");
210 if (enabled) crash_reporting_enabled = env_bool(enabled, true);
211 else crash_reporting_enabled = !env_bool(getenv("DO_NOT_TRACK"), false);
212 if (getenv("ANT_CRASH_REPORT_URL") && !enabled) crash_reporting_enabled = true;
213}
214
215static void init_exe_path(int argc, char **argv) {
216#ifdef _WIN32
217 DWORD len = GetModuleFileNameA(NULL, crash_exe_path, (DWORD)sizeof(crash_exe_path));
218 if (len > 0 && len < sizeof(crash_exe_path)) return;
219#else
220#ifdef __APPLE__
221 uint32_t size = (uint32_t)sizeof(crash_exe_path);
222 char tmp[ANT_CRASH_EXE_PATH_MAX];
223 if (_NSGetExecutablePath(tmp, &size) == 0) {
224 char *resolved = realpath(tmp, crash_exe_path);
225 if (resolved) return;
226 strncpy(crash_exe_path, tmp, sizeof(crash_exe_path) - 1);
227 crash_exe_path[sizeof(crash_exe_path) - 1] = '\0';
228 return;
229 }
230#elif defined(__linux__)
231 ssize_t len = readlink("/proc/self/exe", crash_exe_path, sizeof(crash_exe_path) - 1);
232 if (len > 0) {
233 crash_exe_path[len] = '\0';
234 return;
235 }
236#endif
237#endif
238 if (argc > 0 && argv && argv[0]) {
239 strncpy(crash_exe_path, argv[0], sizeof(crash_exe_path) - 1);
240 crash_exe_path[sizeof(crash_exe_path) - 1] = '\0';
241 }
242}
243
244static const char *os_name(void) {
245#ifdef _WIN32
246 return "Windows";
247#elif defined(__APPLE__)
248 return "macOS";
249#elif defined(__linux__)
250 return "Linux";
251#else
252 return "unknown-os";
253#endif
254}
255
256static const char *os_version(void) {
257 static char version[128];
258 if (version[0]) return version;
259
260#ifdef _WIN32
261 OSVERSIONINFOEXA info;
262 memset(&info, 0, sizeof(info));
263 info.dwOSVersionInfoSize = sizeof(info);
264 if (GetVersionExA((OSVERSIONINFOA *)&info)) {
265 snprintf(
266 version, sizeof(version), "%lu.%lu.%lu",
267 (unsigned long)info.dwMajorVersion,
268 (unsigned long)info.dwMinorVersion,
269 (unsigned long)info.dwBuildNumber
270 );
271 return version;
272 }
273#elif defined(__APPLE__)
274 size_t len = sizeof(version);
275 if (sysctlbyname("kern.osproductversion", version, &len, NULL, 0) == 0 && version[0])
276 return version;
277#elif defined(__linux__)
278 struct utsname info;
279 if (uname(&info) == 0 && info.release[0]) {
280 snprintf(version, sizeof(version), "%s", info.release);
281 return version;
282 }
283#endif
284 snprintf(version, sizeof(version), "unknown");
285 return version;
286}
287
288static const char *os_display_name(void) {
289 static char display[160];
290 if (!display[0]) snprintf(display, sizeof(display), "%s v%s", os_name(), os_version());
291 return display;
292}
293
294static const char *arch_name(void) {
295#if defined(__aarch64__) || defined(_M_ARM64)
296 return "arm64";
297#elif defined(__x86_64__) || defined(_M_X64)
298 return "x64";
299#elif defined(__i386__) || defined(_M_IX86)
300 return "x86";
301#else
302 return "unknown-arch";
303#endif
304}
305
306static uint64_t now_ms(void) {
307#ifdef _WIN32
308 return GetTickCount64();
309#else
310 struct timespec ts;
311 if (clock_gettime(CLOCK_MONOTONIC, &ts) != 0) return 0;
312 return (uint64_t)ts.tv_sec * 1000ULL + (uint64_t)ts.tv_nsec / 1000000ULL;
313#endif
314}
315
316static unsigned long long peak_rss_bytes(void) {
317#ifdef _WIN32
318 return 0;
319#else
320 struct rusage ru;
321 if (getrusage(RUSAGE_SELF, &ru) != 0) return 0;
322#ifdef __APPLE__
323 return (unsigned long long)ru.ru_maxrss;
324#else
325 return (unsigned long long)ru.ru_maxrss * 1024ULL;
326#endif
327#endif
328}
329
330static unsigned long long crash_process_id(void) {
331#ifdef _WIN32
332 return (unsigned long long)GetCurrentProcessId();
333#else
334 return (unsigned long long)getpid();
335#endif
336}
337
338static bool crash_streq_len(const char *s, size_t len, const char *literal) {
339 size_t literal_len = strlen(literal);
340 return len == literal_len && strncmp(s, literal, literal_len) == 0;
341}
342
343static const char *crash_code_detail(const char *code, size_t len) {
344 if (crash_streq_len(code, len, "SIGSEGV")) return "invalid memory access";
345 if (crash_streq_len(code, len, "SIGBUS")) return "bus error";
346 if (crash_streq_len(code, len, "SIGFPE")) return "floating point exception";
347 if (crash_streq_len(code, len, "SIGILL")) return "illegal instruction";
348 if (crash_streq_len(code, len, "SIGABRT")) return "abort";
349
350 if (crash_streq_len(code, len, "EXCEPTION_ACCESS_VIOLATION")) return "invalid memory access";
351 if (crash_streq_len(code, len, "EXCEPTION_STACK_OVERFLOW")) return "stack overflow";
352 if (crash_streq_len(code, len, "EXCEPTION_ILLEGAL_INSTRUCTION")) return "illegal instruction";
353
354 return "fatal error";
355}
356
357static int crash_code_signal_number(const char *code, size_t len) {
358#ifdef SIGSEGV
359 if (crash_streq_len(code, len, "SIGSEGV")) return SIGSEGV;
360#endif
361#ifdef SIGBUS
362 if (crash_streq_len(code, len, "SIGBUS")) return SIGBUS;
363#endif
364#ifdef SIGFPE
365 if (crash_streq_len(code, len, "SIGFPE")) return SIGFPE;
366#endif
367#ifdef SIGILL
368 if (crash_streq_len(code, len, "SIGILL")) return SIGILL;
369#endif
370#ifdef SIGABRT
371 if (crash_streq_len(code, len, "SIGABRT")) return SIGABRT;
372#endif
373 return 0;
374}
375
376static const char *posix_signal_reason(int sig) {
377switch (sig) {
378 case SIGSEGV: return "Segmentation fault";
379#ifdef SIGBUS
380 case SIGBUS: return "Bus error";
381#endif
382 case SIGFPE: return "Floating point exception";
383 case SIGILL: return "Illegal instruction";
384 case SIGABRT: return "Abort";
385 default: return "Fatal signal";
386}}
387
388static const char *posix_signal_code(int sig) {
389switch (sig) {
390 case SIGSEGV: return "SIGSEGV";
391#ifdef SIGBUS
392 case SIGBUS: return "SIGBUS";
393#endif
394 case SIGFPE: return "SIGFPE";
395 case SIGILL: return "SIGILL";
396 case SIGABRT: return "SIGABRT";
397 default: return "SIGNAL";
398}}
399
400static void format_native_frame(char *out, size_t out_cap, uintptr_t addr) {
401 if (out_cap == 0) return;
402 out[0] = '\0';
403
404#ifdef _WIN32
405 HANDLE process = GetCurrentProcess();
406 DWORD64 displacement = 0;
407 DWORD64 base = SymGetModuleBase64(process, (DWORD64)addr);
408
409 char module_path[MAX_PATH] = "";
410 const char *image = "unknown";
411 if (base && GetModuleFileNameA((HMODULE)(uintptr_t)base, module_path, (DWORD)sizeof(module_path)) > 0)
412 image = path_basename_const(module_path);
413
414 union {
415 SYMBOL_INFO info;
416 char storage[sizeof(SYMBOL_INFO) + MAX_SYM_NAME];
417 } symbol_buf;
418
419 SYMBOL_INFO *symbol = &symbol_buf.info;
420 memset(&symbol_buf, 0, sizeof(symbol_buf));
421 symbol->SizeOfStruct = sizeof(SYMBOL_INFO);
422 symbol->MaxNameLen = MAX_SYM_NAME;
423
424 if (SymFromAddr(process, (DWORD64)addr, &displacement, symbol)) {
425 snprintf(
426 out, out_cap, "%s 0x%016llx %s + %llu", image,
427 (unsigned long long)addr, symbol->Name, (unsigned long long)displacement);
428 return;
429 }
430
431 snprintf(out, out_cap, "%s 0x%016llx", image, (unsigned long long)addr);
432#else
433 Dl_info info;
434 memset(&info, 0, sizeof(info));
435
436 if (dladdr((void *)addr, &info) && info.dli_fname) {
437 const char *image = path_basename_const(info.dli_fname);
438 if (info.dli_sname && info.dli_saddr) {
439 unsigned long long offset = (unsigned long long)(addr - (uintptr_t)info.dli_saddr);
440 snprintf(
441 out, out_cap, "%s 0x%016llx %s + %llu",
442 image, (unsigned long long)addr, info.dli_sname, offset);
443 return;
444 }
445 if (info.dli_fbase) {
446 unsigned long long offset = (unsigned long long)(addr - (uintptr_t)info.dli_fbase);
447 snprintf(out, out_cap, "%s 0x%016llx + %llu", image, (unsigned long long)addr, offset);
448 return;
449 }
450 snprintf(out, out_cap, "%s 0x%016llx", image, (unsigned long long)addr);
451 return;
452 }
453
454 snprintf(out, out_cap, "0x%016llx", (unsigned long long)addr);
455#endif
456}
457
458static size_t build_report_payload(
459 char *payload_buf, size_t payload_cap,
460 const char *kind, const char *code, const char *reason,
461 uint64_t fault_addr, const uintptr_t *frames, int frame_count
462) {
463 crash_buf_t p = { payload_buf, payload_cap, 0 };
464 cb_puts(&p, "{\"upload\":");
465 cb_puts(&p, should_upload_report() ? "true" : "false");
466 cb_puts(&p, ",\"trace\":");
467 cb_puts(&p, crash_print_trace ? "true" : "false");
468 cb_puts(&p, ",\"pid\":");
469 cb_put_uint(&p, crash_process_id());
470 cb_puts(&p, ",\"argv\":[");
471 cb_puts(&p, crash_argv);
472 cb_puts(&p, "],\"report\":{\"schema\":1,\"runtime\":\"ant\",\"version\":");
473 cb_put_json_string(&p, ANT_VERSION);
474 cb_puts(&p, ",\"target\":");
475 cb_put_json_string(&p, ANT_TARGET_TRIPLE);
476 cb_puts(&p, ",\"os\":");
477 cb_put_json_string(&p, os_display_name());
478 cb_puts(&p, ",\"arch\":");
479 cb_put_json_string(&p, arch_name());
480 cb_puts(&p, ",\"kind\":");
481 cb_put_json_string(&p, kind);
482 cb_puts(&p, ",\"code\":");
483 cb_put_json_string(&p, code);
484 cb_puts(&p, ",\"reason\":");
485 cb_put_json_string(&p, reason);
486 cb_puts(&p, ",\"addr\":");
487 cb_putc(&p, '"');
488 cb_put_hex(&p, fault_addr);
489 cb_putc(&p, '"');
490 cb_puts(&p, ",\"elapsedMs\":");
491 cb_put_uint(&p, now_ms() - crash_start_ms);
492 cb_puts(&p, ",\"peakRss\":");
493 cb_put_uint(&p, peak_rss_bytes());
494 cb_puts(&p, ",\"frames\":[");
495 for (int i = 0; i < frame_count && i < ANT_CRASH_FRAME_MAX; i++) {
496 if (i) cb_putc(&p, ',');
497 char frame_text[384];
498 format_native_frame(frame_text, sizeof(frame_text), frames[i]);
499 cb_put_json_string(&p, frame_text);
500 }
501 cb_puts(&p, "]}}");
502
503 return p.len;
504}
505
506static bool crash_stderr_is_tty(void) {
507#ifdef _WIN32
508 return _isatty(_fileno(stderr)) != 0;
509#else
510 return isatty(fileno(stderr));
511#endif
512}
513
514#ifndef _WIN32
515static void write_all_fd(int fd, const char *data, size_t len) {
516while (len > 0) {
517 ssize_t n = write(fd, data, len);
518 if (n <= 0) return;
519 data += n;
520 len -= (size_t)n;
521}}
522
523static void spawn_reporter(const char *payload, size_t payload_len) {
524 int stdin_pipe[2];
525 if (pipe(stdin_pipe) != 0) return;
526
527 pid_t pid = fork();
528 if (pid != 0) {
529 close(stdin_pipe[0]);
530 if (pid > 0) write_all_fd(stdin_pipe[1], payload, payload_len);
531 close(stdin_pipe[1]);
532 if (pid > 0) {
533 int status = 0;
534 (void)waitpid(pid, &status, 0);
535 }
536 return;
537 }
538
539 close(stdin_pipe[1]);
540 dup2(stdin_pipe[0], STDIN_FILENO);
541 if (stdin_pipe[0] > STDERR_FILENO) close(stdin_pipe[0]);
542
543 int null_out = open("/dev/null", O_WRONLY);
544 if (null_out >= 0) {
545 dup2(null_out, STDOUT_FILENO);
546 if (null_out > STDERR_FILENO) close(null_out);
547 }
548
549 const char *exe = crash_exe_path[0] ? crash_exe_path : "ant";
550 char *const reporter_argv[] = {
551 (char *)exe,
552 "__internal-crash-report",
553 NULL
554 };
555
556 execv(exe, reporter_argv);
557 execvp(exe, reporter_argv);
558 _exit(0);
559}
560#else
561static void spawn_reporter(const char *payload, size_t payload_len) {
562 if (!crash_exe_path[0]) return;
563
564 SECURITY_ATTRIBUTES sa;
565 memset(&sa, 0, sizeof(sa));
566 sa.nLength = sizeof(sa);
567 sa.bInheritHandle = TRUE;
568
569 HANDLE read_pipe = NULL;
570 HANDLE write_pipe = NULL;
571 if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) return;
572 SetHandleInformation(write_pipe, HANDLE_FLAG_INHERIT, 0);
573
574 HANDLE null_out = CreateFileA("NUL", GENERIC_WRITE, FILE_SHARE_WRITE, &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
575
576 char cmd[ANT_CRASH_EXE_PATH_MAX + 64] = "";
577 snprintf(cmd, sizeof(cmd), "\"%s\" __internal-crash-report", crash_exe_path);
578
579 STARTUPINFOA si;
580 PROCESS_INFORMATION pi;
581 memset(&si, 0, sizeof(si));
582 memset(&pi, 0, sizeof(pi));
583 si.cb = sizeof(si);
584 si.dwFlags = STARTF_USESTDHANDLES;
585 si.hStdInput = read_pipe;
586 si.hStdOutput = null_out != INVALID_HANDLE_VALUE ? null_out : GetStdHandle(STD_OUTPUT_HANDLE);
587 si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
588
589 if (CreateProcessA(NULL, cmd, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
590 CloseHandle(read_pipe);
591 DWORD written = 0;
592 WriteFile(write_pipe, payload, (DWORD)payload_len, &written, NULL);
593 CloseHandle(write_pipe);
594 WaitForSingleObject(pi.hProcess, INFINITE);
595 CloseHandle(pi.hThread);
596 CloseHandle(pi.hProcess);
597 } else {
598 CloseHandle(read_pipe);
599 CloseHandle(write_pipe);
600 }
601 if (null_out != INVALID_HANDLE_VALUE) CloseHandle(null_out);
602}
603#endif
604
605static void crash_report_print_upload_failed(void) {
606 if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <red>Crash report upload failed.</red>\n");
607 else crfprintf(stderr, "<red>Crash report upload failed.</red>\n");
608}
609
610static void crash_report_print_upload_error(const char *message) {
611 crash_report_print_upload_failed();
612 if (message && *message) fprintf(stderr, "%s\n", message);
613}
614
615static ant_value_t crash_report_noop(ant_t *js, ant_value_t *args, int nargs) {
616 if (crash_report_status_printed) return js_mkundef();
617 crash_report_status_printed = true;
618
619 if (args && nargs > 0) crash_report_print_upload_error(js_str(js, args[0]));
620 else crash_report_print_upload_failed();
621
622 return js_mkundef();
623}
624
625static ant_value_t crash_report_print_url(ant_t *js, ant_value_t *args, int nargs) {
626 if (nargs < 1) return js_mkundef();
627
628 size_t len = 0;
629 const char *s = vtype(args[0]) == T_STR ? js_getstr(js, args[0], &len) : NULL;
630
631 if (!s || len == 0) {
632 if (!crash_report_status_printed) {
633 crash_report_status_printed = true;
634 crash_report_print_upload_failed();
635 }
636 return js_mkundef();
637 }
638
639 crash_report_status_printed = true;
640 if (crash_report_status_inline) crfprintf(stderr, "\r\033[2K <cyan>%.*s</cyan>\n\n", (int)len, s);
641 else crfprintf(stderr, "\n <cyan>%.*s</cyan>\n\n", (int)len, s);
642
643 return args[0];
644}
645
646static ant_value_t crash_report_response_text(ant_t *js, ant_value_t *args, int nargs) {
647 if (nargs < 1) return js_mkundef();
648
649 ant_value_t text_fn = js_getprop_fallback(js, args[0], "text");
650 if (!is_callable(text_fn)) {
651 if (!crash_report_status_printed) {
652 crash_report_status_printed = true;
653 crash_report_print_upload_failed();
654 fputs("Response.text is not available.\n", stderr);
655 }
656 return js_mkundef();
657 }
658
659 ant_value_t text_promise = sv_vm_call(js->vm, js, text_fn, args[0], NULL, 0, NULL, false);
660 if (is_err(text_promise)) return text_promise;
661
662 ant_value_t print_promise = js_promise_then(
663 js, text_promise,
664 js_mkfun(crash_report_print_url),
665 js_mkfun(crash_report_noop)
666 );
667
668 promise_mark_handled(text_promise);
669 promise_mark_handled(print_promise);
670
671 return js_mkundef();
672}
673
674static void crash_report_url(char *out, size_t out_cap) {
675 const char *base = getenv("ANT_CRASH_REPORT_URL");
676 if (!base || !*base) base = "https://js.report";
677
678 size_t n = strlen(base);
679 while (n > 0 && base[n - 1] == '/') n--;
680 if (n >= out_cap) n = out_cap - 1;
681
682 memcpy(out, base, n);
683 out[n] = '\0';
684 strncat(out, "/report", out_cap - strlen(out) - 1);
685}
686
687static char *crash_read_stdin(size_t *len) {
688 size_t cap = 4096;
689 *len = 0;
690 char *buf = malloc(cap);
691 if (!buf) return NULL;
692
693 size_t n = 0;
694 while ((n = fread(buf + *len, 1, cap - *len, stdin)) > 0) {
695 *len += n;
696 if (*len == cap) {
697 cap *= 2;
698 char *next = realloc(buf, cap);
699 if (!next) {
700 free(buf);
701 return NULL;
702 }
703 buf = next;
704 }}
705
706 buf[*len] = '\0';
707 return buf;
708}
709
710static ant_value_t crash_get(ant_t *js, ant_value_t obj, const char *key) {
711 if (!is_object_type(obj)) return js_mkundef();
712 return js_get(js, obj, key);
713}
714
715static const char *crash_get_string(ant_t *js, ant_value_t obj, const char *key, size_t *len, const char *fallback) {
716 ant_value_t value = crash_get(js, obj, key);
717 if (vtype(value) == T_STR) {
718 const char *s = js_getstr(js, value, len);
719 if (s) return s;
720 }
721 *len = strlen(fallback);
722 return fallback;
723}
724
725static unsigned long long crash_get_uint(ant_t *js, ant_value_t obj, const char *key) {
726 ant_value_t value = crash_get(js, obj, key);
727 if (vtype(value) != T_NUM) return 0;
728 double n = js_getnum(value);
729 if (n <= 0) return 0;
730 return (unsigned long long)n;
731}
732
733static void crash_format_bytes(char *out, size_t out_cap, unsigned long long bytes) {
734 if (bytes == 0) {
735 snprintf(out, out_cap, "unknown");
736 return;
737 }
738
739 if (bytes >= 1024ULL * 1024ULL)
740 snprintf(out, out_cap, "%lluMB", bytes / (1024ULL * 1024ULL));
741 else if (bytes >= 1024ULL)
742 snprintf(out, out_cap, "%lluKB", bytes / 1024ULL);
743 else
744 snprintf(out, out_cap, "%lluB", bytes);
745}
746
747static void crash_print_quoted_value(ant_t *js, ant_value_t value) {
748 size_t len = 0;
749 const char *s = NULL;
750
751 if (vtype(value) == T_STR) s = js_getstr(js, value, &len);
752 if (!s) {
753 s = "<unknown>";
754 len = strlen(s);
755 }
756
757 fputc('"', stderr);
758 for (size_t i = 0; i < len; i++) {
759 char c = s[i];
760 if (c == '"' || c == '\\') fputc('\\', stderr);
761 if (c == '\n' || c == '\r') c = ' ';
762 fputc(c, stderr);
763 }
764
765 fputc('"', stderr);
766}
767
768static void crash_print_string_value(ant_t *js, ant_value_t value) {
769 size_t len = 0;
770 const char *s = NULL;
771
772 if (vtype(value) == T_STR) s = js_getstr(js, value, &len);
773 if (!s) {
774 s = "<unknown>";
775 len = strlen(s);
776 }
777
778 for (size_t i = 0; i < len; i++) {
779 char c = s[i];
780 if (c == '\n' || c == '\r') c = ' ';
781 fputc(c, stderr);
782 }
783}
784
785static void crash_print_args(ant_t *js, ant_value_t argv) {
786 crfprintf(stderr, "<dim>Args:");
787 if (vtype(argv) != T_ARR || js_arr_len(js, argv) == 0) {
788 fputs(" \"ant\"\n", stderr);
789 return;
790 }
791
792 ant_offset_t len = js_arr_len(js, argv);
793 for (ant_offset_t i = 0; i < len; i++) {
794 fputc(' ', stderr);
795 crash_print_quoted_value(js, js_arr_get(js, argv, i));
796 }
797 fputc('\n', stderr);
798}
799
800static void crash_print_frames(ant_t *js, ant_value_t report) {
801 ant_value_t frames = crash_get(js, report, "frames");
802 crfprintf(stderr, "<dim>Native backtrace:</>\n");
803 if (vtype(frames) != T_ARR || js_arr_len(js, frames) == 0) {
804 crfprintf(stderr, " <dim>(no native frames were captured)</>\n\n");
805 return;
806 }
807
808 ant_offset_t len = js_arr_len(js, frames);
809 for (ant_offset_t i = 0; i < len; i++) {
810 fputs(" ", stderr);
811 fprintf(stderr, "%-2lld ", (long long)i);
812 crash_print_string_value(js, js_arr_get(js, frames, i));
813 fputc('\n', stderr);
814 }
815 fputc('\n', stderr);
816}
817
818static void crash_print_report_summary(ant_t *js, ant_value_t report, ant_value_t argv, bool upload, bool trace, unsigned long long pid) {
819 size_t code_len = 0, addr_len = 0, reason_len = 0;
820
821 const char *code = crash_get_string(js, report, "code", &code_len, "SIGNAL");
822 const char *addr = crash_get_string(js, report, "addr", &addr_len, "0x0");
823 const char *reason = crash_get_string(js, report, "reason", &reason_len, "Fatal signal");
824
825 unsigned long long elapsed = crash_get_uint(js, report, "elapsedMs");
826 unsigned long long peak_rss = crash_get_uint(js, report, "peakRss");
827
828 const char *detail = crash_code_detail(code, code_len);
829 int signal_number = crash_code_signal_number(code, code_len);
830
831 char peak_rss_text[32];
832 crash_format_bytes(peak_rss_text, sizeof(peak_rss_text), peak_rss);
833
834 fprintf(stderr, "=== (%llu) ===================================================\n", pid);
835
836 crfprintf(stderr, "<dim>Ant v%s (%s) %s</>\n", ant_semver(), ANT_GIT_HASH, ANT_TARGET_TRIPLE);
837 crfprintf(stderr, "<dim>%s</>\n", os_display_name());
838
839 crash_print_args(js, argv);
840
841 crfprintf(stderr, "<dim>Summary: %s (%.*s) with signal %d</>\n", detail, (int)code_len, code, signal_number);
842 crfprintf(stderr, "<dim>Elapsed: %llums | RSS Peak: %s</>\n\n", elapsed, peak_rss_text);
843 crfprintf(stderr, "<red>panic</red><dim>(main thread):</> %.*s at address %.*s \n", (int)reason_len, reason, (int)addr_len, addr);
844
845 crfprintf(stderr, "oh no<dim>:</> Ant has crashed. This indicates a bug in Ant, not your code.\n\n");
846 if (trace) crash_print_frames(js, report);
847
848 if (upload) {
849 crfprintf(stderr, "To send a redacted crash report to Ant's team,\n");
850 crfprintf(stderr, "please file a GitHub issue using the link below:\n\n");
851 if (crash_report_status_inline) crfprintf(stderr, " <yellow>uploading...</>");
852 }
853 else crfprintf(stderr, "Crash reporting is disabled for this process.\n\n");
854}
855
856int ant_crash_run_internal_report(ant_t *js) {
857 if (!js) return EXIT_FAILURE;
858
859 size_t payload_len = 0;
860 char *payload = crash_read_stdin(&payload_len);
861 if (!payload) return EXIT_FAILURE;
862
863 crash_report_status_printed = false;
864
865 ant_value_t wrapper_json = js_mkstr(js, payload, payload_len);
866 ant_value_t wrapper = json_parse_value(js, wrapper_json);
867
868 if (is_err(wrapper) || !is_object_type(wrapper)) {
869 crash_report_print_upload_failed();
870 free(payload);
871 return EXIT_FAILURE;
872 }
873
874 ant_value_t report = crash_get(js, wrapper, "report");
875 if (!is_object_type(report)) {
876 crash_report_print_upload_failed();
877 free(payload);
878 return EXIT_FAILURE;
879 }
880
881 bool upload = js_truthy(js, crash_get(js, wrapper, "upload"));
882 bool trace = js_truthy(js, crash_get(js, wrapper, "trace"));
883
884 unsigned long long pid = crash_get_uint(js, wrapper, "pid");
885 ant_value_t argv = crash_get(js, wrapper, "argv");
886
887 crash_report_status_inline = upload && crash_stderr_is_tty();
888 crash_print_report_summary(js, report, argv, upload, trace, pid);
889
890 if (!upload) {
891 free(payload);
892 return EXIT_SUCCESS;
893 }
894
895 ant_value_t report_json = js_json_stringify(js, &report, 1);
896 if (vtype(report_json) != T_STR) {
897 crash_report_print_upload_failed();
898 free(payload);
899 return EXIT_FAILURE;
900 }
901
902 size_t report_payload_len = 0;
903 const char *report_payload = js_getstr(js, report_json, &report_payload_len);
904 if (!report_payload) {
905 crash_report_print_upload_failed();
906 free(payload);
907 return EXIT_FAILURE;
908 }
909
910 fflush(stderr);
911 char url[512] = "";
912 crash_report_url(url, sizeof(url));
913
914 ant_value_t headers = js_mkobj(js);
915 js_set(js, headers, "content-type", js_mkstr(js, "application/json", 16));
916
917 ant_value_t init = js_mkobj(js);
918 js_set(js, init, "headers", headers);
919 js_set(js, init, "method", js_mkstr(js, "POST", 4));
920 js_set(js, init, "body", js_mkstr(js, report_payload, report_payload_len));
921
922 ant_value_t fetch_args[2] = { js_mkstr(js, url, strlen(url)), init };
923 ant_value_t fetch_promise = ant_fetch(js, fetch_args, 2);
924
925 if (is_err(fetch_promise)) {
926 crash_report_status_printed = true;
927 crash_report_print_upload_error(js_str(js, fetch_promise));
928 free(payload);
929 return EXIT_FAILURE;
930 }
931
932 ant_value_t report_promise = js_promise_then(
933 js, fetch_promise,
934 js_mkfun(crash_report_response_text),
935 js_mkfun(crash_report_noop)
936 );
937
938 promise_mark_handled(fetch_promise);
939 if (is_err(report_promise)) {
940 crash_report_status_printed = true;
941 crash_report_print_upload_error(js_str(js, report_promise));
942 free(payload);
943 return EXIT_FAILURE;
944 }
945
946 promise_mark_handled(report_promise);
947 js_run_event_loop(js);
948 free(payload);
949
950 return EXIT_SUCCESS;
951}
952
953void ant_crash_suppress_reporting(void) {
954 crash_reporting_suppressed = 1;
955}
956
957#ifndef _WIN32
958static int install_altstack(void) {
959#ifdef SA_ONSTACK
960 static void *stack_mem;
961 if (stack_mem) return 1;
962
963 stack_mem = malloc(ANT_CRASH_ALT_STACK_SIZE);
964 if (!stack_mem) return 0;
965
966 stack_t ss;
967 memset(&ss, 0, sizeof(ss));
968 ss.ss_sp = stack_mem;
969 ss.ss_size = ANT_CRASH_ALT_STACK_SIZE;
970 ss.ss_flags = 0;
971
972 if (sigaltstack(&ss, NULL) == 0) return 1;
973
974 free(stack_mem);
975 stack_mem = NULL;
976#endif
977 return 0;
978}
979
980static void crash_handler(int sig, siginfo_t *info, void *ucontext) {
981 struct sigaction dfl;
982 memset(&dfl, 0, sizeof(dfl));
983
984 dfl.sa_handler = SIG_DFL;
985 sigemptyset(&dfl.sa_mask);
986 sigaction(sig, &dfl, NULL);
987
988 uintptr_t frames[ANT_CRASH_FRAME_MAX] = {0};
989 int frame_count = 0;
990#ifdef ANT_CRASH_HAVE_EXECINFO
991 void *raw_frames[64];
992 int n = backtrace(raw_frames, (int)(sizeof(raw_frames) / sizeof(raw_frames[0])));
993 int skip = n > 1 ? 1 : 0;
994 for (int i = skip; i < n && frame_count < ANT_CRASH_FRAME_MAX; i++)
995 frames[frame_count++] = (uintptr_t)raw_frames[i];
996#elif defined(ANT_CRASH_HAVE_UNWIND_BACKTRACE)
997 (void)ucontext;
998 crash_unwind_state_t state = { frames, 0, 1 };
999 _Unwind_Backtrace(collect_unwind_frame, &state);
1000 frame_count = state.frame_count;
1001#endif
1002
1003 uint64_t fault_addr = info ? (uint64_t)(uintptr_t)info->si_addr : 0;
1004 char payload[ANT_CRASH_PAYLOAD_MAX] = "";
1005 const char *reason = posix_signal_reason(sig);
1006 const char *code = posix_signal_code(sig);
1007
1008 size_t payload_len = build_report_payload(
1009 payload, sizeof(payload), "signal", code,
1010 reason, fault_addr, frames, frame_count
1011 );
1012
1013 spawn_reporter(payload, payload_len);
1014#ifdef SIGTRAP
1015 struct sigaction trap_dfl;
1016 memset(&trap_dfl, 0, sizeof(trap_dfl));
1017 trap_dfl.sa_handler = SIG_DFL;
1018 sigemptyset(&trap_dfl.sa_mask);
1019 sigaction(SIGTRAP, &trap_dfl, NULL);
1020 raise(SIGTRAP);
1021#else
1022 raise(sig);
1023#endif
1024}
1025
1026void ant_crash_init(int argc, char **argv) {
1027 crash_start_ms = now_ms();
1028 init_exe_path(argc, argv);
1029 init_argv_strings(argc, argv);
1030 init_report_controls();
1031
1032 struct sigaction sa;
1033 memset(&sa, 0, sizeof(sa));
1034 sa.sa_sigaction = crash_handler;
1035 sa.sa_flags = SA_SIGINFO | SA_RESETHAND;
1036#ifdef SA_ONSTACK
1037 if (install_altstack()) sa.sa_flags |= SA_ONSTACK;
1038#endif
1039 sigemptyset(&sa.sa_mask);
1040 static const int sigs[] = { SIGSEGV, SIGBUS, SIGFPE, SIGILL, SIGABRT };
1041 for (size_t i = 0; i < sizeof(sigs) / sizeof(sigs[0]); i++) sigaction(sigs[i], &sa, NULL);
1042}
1043
1044#else // _WIN32
1045#include <process.h>
1046
1047static LPTOP_LEVEL_EXCEPTION_FILTER previous_filter;
1048static volatile LONG crash_in_progress;
1049
1050static const char *exception_name(DWORD code) {
1051switch (code) {
1052 case EXCEPTION_ACCESS_VIOLATION: return "EXCEPTION_ACCESS_VIOLATION";
1053 case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED";
1054 case EXCEPTION_BREAKPOINT: return "EXCEPTION_BREAKPOINT";
1055 case EXCEPTION_DATATYPE_MISALIGNMENT: return "EXCEPTION_DATATYPE_MISALIGNMENT";
1056 case EXCEPTION_FLT_DENORMAL_OPERAND: return "EXCEPTION_FLT_DENORMAL_OPERAND";
1057 case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "EXCEPTION_FLT_DIVIDE_BY_ZERO";
1058 case EXCEPTION_FLT_INEXACT_RESULT: return "EXCEPTION_FLT_INEXACT_RESULT";
1059 case EXCEPTION_FLT_INVALID_OPERATION: return "EXCEPTION_FLT_INVALID_OPERATION";
1060 case EXCEPTION_FLT_OVERFLOW: return "EXCEPTION_FLT_OVERFLOW";
1061 case EXCEPTION_FLT_STACK_CHECK: return "EXCEPTION_FLT_STACK_CHECK";
1062 case EXCEPTION_FLT_UNDERFLOW: return "EXCEPTION_FLT_UNDERFLOW";
1063 case EXCEPTION_ILLEGAL_INSTRUCTION: return "EXCEPTION_ILLEGAL_INSTRUCTION";
1064 case EXCEPTION_IN_PAGE_ERROR: return "EXCEPTION_IN_PAGE_ERROR";
1065 case EXCEPTION_INT_DIVIDE_BY_ZERO: return "EXCEPTION_INT_DIVIDE_BY_ZERO";
1066 case EXCEPTION_INT_OVERFLOW: return "EXCEPTION_INT_OVERFLOW";
1067 case EXCEPTION_INVALID_DISPOSITION: return "EXCEPTION_INVALID_DISPOSITION";
1068 case EXCEPTION_NONCONTINUABLE_EXCEPTION: return "EXCEPTION_NONCONTINUABLE_EXCEPTION";
1069 case EXCEPTION_PRIV_INSTRUCTION: return "EXCEPTION_PRIV_INSTRUCTION";
1070 case EXCEPTION_STACK_OVERFLOW: return "EXCEPTION_STACK_OVERFLOW";
1071 default: return "fatal exception";
1072}}
1073
1074static const char *exception_reason(DWORD code) {
1075switch (code) {
1076 case EXCEPTION_ACCESS_VIOLATION: return "Segmentation fault";
1077 case EXCEPTION_ILLEGAL_INSTRUCTION: return "Illegal instruction";
1078 case EXCEPTION_STACK_OVERFLOW: return "Stack overflow";
1079 case EXCEPTION_INT_DIVIDE_BY_ZERO:
1080 case EXCEPTION_FLT_DIVIDE_BY_ZERO: return "Divide by zero";
1081 default: return "Fatal exception";
1082}}
1083
1084static DWORD64 exception_fault_address(EXCEPTION_RECORD *record) {
1085 if (!record) return 0;
1086 if ((
1087 record->ExceptionCode == EXCEPTION_ACCESS_VIOLATION ||
1088 record->ExceptionCode == EXCEPTION_IN_PAGE_ERROR)
1089 && record->NumberParameters >= 2
1090 ) return (DWORD64)record->ExceptionInformation[1];
1091 return (DWORD64)(uintptr_t)record->ExceptionAddress;
1092}
1093
1094static BOOL init_stack_frame(CONTEXT *ctx, STACKFRAME64 *frame, DWORD *machine) {
1095 memset(frame, 0, sizeof(*frame));
1096#if defined(_M_X64) || defined(__x86_64__)
1097 *machine = IMAGE_FILE_MACHINE_AMD64;
1098 frame->AddrPC.Offset = ctx->Rip;
1099 frame->AddrFrame.Offset = ctx->Rbp;
1100 frame->AddrStack.Offset = ctx->Rsp;
1101#elif defined(_M_IX86) || defined(__i386__)
1102 *machine = IMAGE_FILE_MACHINE_I386;
1103 frame->AddrPC.Offset = ctx->Eip;
1104 frame->AddrFrame.Offset = ctx->Ebp;
1105 frame->AddrStack.Offset = ctx->Esp;
1106#elif defined(_M_ARM64) || defined(__aarch64__)
1107 *machine = IMAGE_FILE_MACHINE_ARM64;
1108 frame->AddrPC.Offset = ctx->Pc;
1109 frame->AddrFrame.Offset = ctx->Fp;
1110 frame->AddrStack.Offset = ctx->Sp;
1111#else
1112 return FALSE;
1113#endif
1114 frame->AddrPC.Mode = AddrModeFlat;
1115 frame->AddrFrame.Mode = AddrModeFlat;
1116 frame->AddrStack.Mode = AddrModeFlat;
1117 return TRUE;
1118}
1119
1120static int collect_windows_frames(EXCEPTION_POINTERS *exc, uintptr_t *frames, int max_frames) {
1121 if (!exc || !exc->ContextRecord) return 0;
1122 HANDLE process = GetCurrentProcess();
1123 HANDLE thread = GetCurrentThread();
1124 CONTEXT ctx = *exc->ContextRecord;
1125 STACKFRAME64 frame;
1126 DWORD machine;
1127 if (!init_stack_frame(&ctx, &frame, &machine)) return 0;
1128
1129 int count = 0;
1130 while (count < max_frames) {
1131 DWORD64 addr = frame.AddrPC.Offset;
1132 if (addr == 0) break;
1133 frames[count++] = (uintptr_t)addr;
1134 DWORD64 prev_pc = frame.AddrPC.Offset;
1135 DWORD64 prev_sp = frame.AddrStack.Offset;
1136 BOOL ok = StackWalk64(
1137 machine, process, thread, &frame, &ctx, NULL,
1138 SymFunctionTableAccess64, SymGetModuleBase64, NULL
1139 );
1140 if (!ok || (frame.AddrPC.Offset == prev_pc && frame.AddrStack.Offset == prev_sp)) break;
1141 }
1142 return count;
1143}
1144
1145static LONG WINAPI windows_crash_handler(EXCEPTION_POINTERS *exc) {
1146 if (InterlockedExchange(&crash_in_progress, 1) != 0)
1147 return EXCEPTION_CONTINUE_SEARCH;
1148
1149 HANDLE process = GetCurrentProcess();
1150 SymSetOptions(SYMOPT_DEFERRED_LOADS | SYMOPT_LOAD_LINES | SYMOPT_UNDNAME);
1151 SymInitialize(process, NULL, TRUE);
1152
1153 EXCEPTION_RECORD *record = exc ? exc->ExceptionRecord : NULL;
1154 DWORD code = record ? record->ExceptionCode : 0;
1155 uint64_t fault_addr = (uint64_t)exception_fault_address(record);
1156
1157 uintptr_t frames[ANT_CRASH_FRAME_MAX] = {0};
1158 int frame_count = collect_windows_frames(exc, frames, ANT_CRASH_FRAME_MAX);
1159 char payload[ANT_CRASH_PAYLOAD_MAX] = "";
1160
1161 size_t payload_len = build_report_payload(
1162 payload, sizeof(payload), "exception", exception_name(code),
1163 exception_reason(code), fault_addr, frames, frame_count
1164 );
1165
1166 spawn_reporter(payload, payload_len);
1167 if (previous_filter) return previous_filter(exc);
1168 return EXCEPTION_EXECUTE_HANDLER;
1169}
1170
1171void ant_crash_init(int argc, char **argv) {
1172 crash_start_ms = now_ms();
1173 init_exe_path(argc, argv);
1174 init_argv_strings(argc, argv);
1175 init_report_controls();
1176 previous_filter = SetUnhandledExceptionFilter(windows_crash_handler);
1177}
1178
1179#endif