MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at master 1179 lines 36 kB view raw
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