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 988 lines 31 kB view raw
1#include "errors.h" 2#include "internal.h" 3#include "descriptors.h" 4#include "silver/engine.h" 5#include "modules/io.h" 6#include "highlight.h" 7 8#include <stdlib.h> 9#include <string.h> 10#include <stdarg.h> 11#include <limits.h> 12#include <crprintf.h> 13 14#ifdef _WIN32 15#define WIN32_LEAN_AND_MEAN 16#include <windows.h> 17#else 18#include <unistd.h> 19#include <sys/ioctl.h> 20#endif 21 22typedef struct { char *buf; size_t size; } errbuf_t; 23 24static void print_error_value(ant_t *js, ant_value_t value, ant_value_t fallback_stack, const char *prefix) { 25 ant_value_t obj = is_err(value) ? js_as_obj(value) : value; 26 const char *stack = NULL; bool no_stack = false; 27 28 if (vtype(obj) == T_OBJ) { 29 ant_value_t err_type = js_get_slot(obj, SLOT_ERR_TYPE); 30 no_stack = vtype(err_type) == T_NUM && ((int)js_getnum(err_type) & JS_ERR_NO_STACK); 31 if (!no_stack) stack = get_str_prop(js, obj, "stack", 5, NULL); 32 } 33 34 if (!no_stack && !stack && vtype(fallback_stack) == T_STR) { 35 ant_offset_t slen; 36 ant_offset_t soff = vstr(js, fallback_stack, &slen); 37 stack = (const char *)(uintptr_t)(soff); 38 } 39 40 if (prefix) fputs(prefix, stderr); 41 42 if (stack) { 43 fputs(stack, stderr); 44 size_t n = strlen(stack); 45 if (n == 0 || stack[n - 1] != '\n') fputc('\n', stderr); 46 } else if (vtype(obj) == T_OBJ) { 47 const char *name = get_str_prop(js, obj, "name", 4, NULL); 48 const char *msg = get_str_prop(js, obj, "message", 7, NULL); 49 50 if (name && msg) fprintf(stderr, "%s%s%s: %s%s%s\n", C_RED, name, C_RESET, C_BOLD, msg, C_RESET); 51 else if (name) fprintf(stderr, "%s%s%s\n", C_RED, name, C_RESET); 52 else fprintf(stderr, "[object Error]\n"); 53 } 54 55 else fprintf(stderr, "%s\n", js_str(js, value)); 56} 57 58bool print_uncaught_throw(ant_t *js) { 59 if (!js->thrown_exists) return false; 60 print_error_value(js, js->thrown_value, js->thrown_stack, NULL); 61 62 js->thrown_exists = false; 63 js->thrown_value = js_mkundef(); 64 js->thrown_stack = js_mkundef(); 65 66 return true; 67} 68 69bool print_unhandled_promise_rejection(ant_t *js, ant_value_t value) { 70 print_error_value(js, value, js_mkundef(), "Uncaught (in promise) "); 71 return true; 72} 73 74bool js_mark_errorlike_no_stack(ant_t *js, ant_value_t value) { 75 if (vtype(value) != T_OBJ) return false; 76 if (js_get_slot(value, SLOT_ERROR_BRAND) == js_true) return false; 77 78 const char *name = get_str_prop(js, value, "name", 4, NULL); 79 const char *message = get_str_prop(js, value, "message", 7, NULL); 80 if ((!name || !*name) && (!message || !*message)) return false; 81 82 ant_value_t err_type = js_get_slot(value, SLOT_ERR_TYPE); 83 int base_type = vtype(err_type) == T_NUM ? (int)js_getnum(err_type) : JS_ERR_GENERIC; 84 85 js_set(js, value, "stack", js_mkundef()); 86 js_set_slot(value, SLOT_ERR_TYPE, js_mknum((double)(base_type | JS_ERR_NO_STACK))); 87 88 return true; 89} 90 91static const char *get_error_type_name(js_err_type_t err_type) { 92 static const char *names[] = { 93 [JS_ERR_GENERIC] = "Error", 94 [JS_ERR_TYPE] = "TypeError", 95 [JS_ERR_SYNTAX] = "SyntaxError", 96 [JS_ERR_REFERENCE] = "ReferenceError", 97 [JS_ERR_RANGE] = "RangeError", 98 [JS_ERR_EVAL] = "EvalError", 99 [JS_ERR_URI] = "URIError", 100 [JS_ERR_INTERNAL] = "InternalError", 101 [JS_ERR_AGGREGATE] = "AggregateError", 102 }; 103 104 return names[err_type] ?: "Error"; 105} 106 107static bool ensure_errbuf_capacity(errbuf_t *eb, size_t needed) { 108 if (needed <= eb->size) return true; 109 110 size_t new_size = eb->size; 111 while (new_size < needed) { 112 size_t next = new_size * 2; 113 if (next < new_size) return false; 114 new_size = next; 115 } 116 117 char *next_buf = (char *)realloc(eb->buf, new_size); 118 if (!next_buf) return false; 119 eb->buf = next_buf; 120 eb->size = new_size; 121 return true; 122} 123 124__attribute__((format(printf, 3, 4))) 125static size_t append_errbuf_fmt(errbuf_t *eb, size_t used, const char *fmt, ...) { 126 int max_attempts = 3; 127 int attempt = 0; 128 129 for (;;) { 130 if (!ensure_errbuf_capacity(eb, used + 1)) { 131 return eb->size ? eb->size - 1 : used; 132 } 133 134 size_t remaining = eb->size - used; 135 va_list ap; 136 va_start(ap, fmt); 137 int written = vsnprintf(eb->buf + used, remaining, fmt, ap); 138 va_end(ap); 139 140 if (written < 0) return used; 141 if ((size_t)written < remaining) return used + (size_t)written; 142 143 if (!ensure_errbuf_capacity(eb, used + (size_t)written + 1)) { 144 if (++attempt >= max_attempts) return eb->size ? eb->size - 1 : used; 145 } 146 } 147} 148 149static inline size_t remaining_capacity(size_t used, size_t total) { 150 return used >= total ? 0 : total - used; 151} 152 153static size_t append_error_header(errbuf_t *eb, ant_t *js, size_t used, int line, int col) { 154 const char *file = (js->errsite.valid && js->errsite.filename) ? js->errsite.filename : js->filename; 155 if (file) return append_errbuf_fmt(eb, used, "%s:%d:%d\n", file, line, col); 156 return append_errbuf_fmt(eb, used, "<eval>:%d:%d\n", line, col); 157} 158 159static void get_line_col(const char *code, ant_offset_t code_len, ant_offset_t pos, int *out_line, int *out_col) { 160 if (!out_line || !out_col) return; 161 162 *out_line = 1; 163 *out_col = 1; 164 165 if (!code || code_len <= 0 || pos <= 0) return; 166 if (pos > code_len) pos = code_len; 167 168 for (ant_offset_t i = 0; i < pos; i++) { 169 char ch = code[i]; 170 if (ch == '\0') break; 171 if (ch == '\n') { 172 *out_line += 1; 173 *out_col = 1; 174 } else *out_col += 1; 175 } 176} 177 178static void get_error_line( 179 const char *code, ant_offset_t clen, ant_offset_t pos, char *buf, size_t bufsize, 180 int *line_start_col, ant_offset_t *out_line_start, ant_offset_t *out_line_end 181) { 182 if (!code || bufsize == 0) { 183 if (bufsize > 0) buf[0] = '\0'; 184 if (line_start_col) *line_start_col = 1; 185 if (out_line_start) *out_line_start = 0; 186 if (out_line_end) *out_line_end = 0; 187 return; 188 } 189 190 if (pos > clen) pos = clen; 191 192 if (clen == 0) { 193 buf[0] = '\0'; 194 if (line_start_col) *line_start_col = 1; 195 if (out_line_start) *out_line_start = 0; 196 if (out_line_end) *out_line_end = 0; 197 return; 198 } 199 200 ant_offset_t line_start = pos; 201 while (line_start > 0 && code[line_start - 1] != '\n') { 202 line_start--; 203 } 204 205 ant_offset_t line_end = pos; 206 while (line_end < clen && code[line_end] != '\n' && code[line_end] != '\0') { 207 line_end++; 208 } 209 210 ant_offset_t line_len = line_end - line_start; 211 if (line_len >= bufsize) line_len = (ant_offset_t)(bufsize - 1); 212 213 memcpy(buf, &code[line_start], line_len); 214 buf[line_len] = '\0'; 215 216 if (line_start_col) *line_start_col = (int)(pos - line_start) + 1; 217 if (out_line_start) *out_line_start = line_start; 218 if (out_line_end) *out_line_end = line_end; 219} 220 221static size_t append_error_value(errbuf_t *eb, ant_t *js, size_t used, ant_value_t value) { 222 const char *name = "Error"; 223 const char *msg = NULL; 224 225 ant_offset_t name_len = 5; 226 ant_offset_t msg_len = 0; 227 228 static const void *type_dispatch[] = { 229 [T_STR] = &&l_type_str, 230 [T_OBJ] = &&l_type_obj, 231 [T_FUNC] = &&l_type_default, 232 [T_ARR] = &&l_type_default, 233 [T_PROMISE] = &&l_type_default, 234 [T_GENERATOR] = &&l_type_default, 235 [T_BIGINT] = &&l_type_default, 236 [T_NUM] = &&l_type_default, 237 [T_BOOL] = &&l_type_default, 238 [T_SYMBOL] = &&l_type_default, 239 [T_CFUNC] = &&l_type_default, 240 [T_TYPEDARRAY] = &&l_type_default, 241 [T_ERR] = &&l_type_default, 242 [T_UNDEF] = &&l_type_default, 243 [T_NULL] = &&l_type_default, 244 }; 245 246 uint8_t t = vtype(value); 247 if (t < sizeof(type_dispatch) / sizeof(type_dispatch[0]) && type_dispatch[t]) { 248 goto *type_dispatch[t]; 249 } 250 goto l_type_default; 251 252 l_type_str: 253 msg = (const char *)(uintptr_t)(vstr(js, value, &msg_len)); 254 goto l_type_done; 255 256 l_type_obj: 257 name = get_str_prop(js, value, "name", 4, &name_len); 258 if (!name) { 259 name = "Error"; 260 name_len = 5; 261 } 262 msg = get_str_prop(js, value, "message", 7, &msg_len); 263 goto l_type_done; 264 265 l_type_default: 266 msg = js_str(js, value); 267 msg_len = msg ? (ant_offset_t)strlen(msg) : 0; 268 goto l_type_done; 269 270 l_type_done: 271 272 static const void *dispatch[] = { &&l_with_msg, &&l_name_only }; 273 int key = msg ? 0 : 1; 274 goto *dispatch[key]; 275 276 l_with_msg: 277 return append_errbuf_fmt(eb, used, 278 ERR_FMT, 279 (int)name_len, name, (int)msg_len, msg 280 ); 281 282 l_name_only: 283 return append_errbuf_fmt(eb, used, 284 ERR_NAME_ONLY, 285 (int)name_len, name 286 ); 287} 288 289static int count_digits_int(int v) { 290 int n = 1; 291 while (v >= 10) { v /= 10; n++; } 292 return n; 293} 294 295static void append_error_caret(errbuf_t *eb, size_t *n, int error_col, int span_cols) { 296 if (span_cols < 1) span_cols = 1; 297 if (!ensure_errbuf_capacity(eb, *n + (size_t)error_col + (size_t)span_cols + 2)) return; 298 if (*n >= eb->size - 1) return; 299 300 size_t remaining = eb->size - *n; 301 for (int i = 1; i < error_col && remaining > 1; i++) { 302 eb->buf[(*n)++] = ' '; 303 remaining--; 304 } 305 306 for (int i = 0; i < span_cols && remaining > 1; i++) { 307 eb->buf[(*n)++] = '^'; 308 remaining--; 309 } 310 311 eb->buf[*n] = '\0'; 312} 313 314static int error_span_cols_for_line(ant_offset_t src_pos, ant_offset_t span_len, ant_offset_t line_start, ant_offset_t line_end) { 315 if (line_end < line_start) return 1; 316 if (src_pos < line_start) src_pos = line_start; 317 if (src_pos > line_end) src_pos = line_end; 318 if (span_len <= 0) return 1; 319 320 ant_offset_t span_end = src_pos + span_len; 321 if (span_end < src_pos) span_end = src_pos; 322 if (span_end > line_end) span_end = line_end; 323 324 ant_offset_t width = span_end - src_pos; 325 if (width <= 0) width = 1; 326 if (width > INT_MAX) width = INT_MAX; 327 return (int)width; 328} 329 330static int error_terminal_columns(void) { 331#ifdef _WIN32 332 HANDLE h = GetStdHandle(STD_ERROR_HANDLE); 333 if (h == NULL || h == INVALID_HANDLE_VALUE) h = GetStdHandle(STD_OUTPUT_HANDLE); 334 if (h && h != INVALID_HANDLE_VALUE) { 335 CONSOLE_SCREEN_BUFFER_INFO csbi; 336 if (GetConsoleScreenBufferInfo(h, &csbi)) { 337 int cols = (int)(csbi.srWindow.Right - csbi.srWindow.Left + 1); 338 if (cols > 0) return cols; 339 } 340 } 341#else 342 struct winsize ws; 343 if (ioctl(STDERR_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 344 return (int)ws.ws_col; 345 } 346 if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { 347 return (int)ws.ws_col; 348 } 349#endif 350 351 const char *cols_env = getenv("COLUMNS"); 352 if (cols_env && *cols_env) { 353 char *end = NULL; 354 long cols = strtol(cols_env, &end, 10); 355 if (end != cols_env && cols > 0 && cols <= INT_MAX) return (int)cols; 356 } 357 return 120; 358} 359 360static int error_context_src_cols_limit(int gutter_w) { 361 int cols = error_terminal_columns(); 362 int half = cols / 2; 363 if (half < 40) half = 40; 364 int budget = half - (gutter_w + 3); 365 if (budget < 20) budget = 20; 366 return budget; 367} 368 369static const char *error_frame_name(ant_t *js, sv_frame_t *frame, sv_func_t *func) { 370 if (func && func->name && func->name[0]) return func->name; 371 if (js && frame && vtype(frame->callee) == T_FUNC) { 372 ant_offset_t name_len = 0; 373 const char *name = get_str_prop(js, js_func_obj(frame->callee), "name", 4, &name_len); 374 if (name && name_len > 0) return name; 375 } 376 return "<anonymous>"; 377} 378 379typedef struct { 380 const char *name; 381 const char *file; 382 int line; 383 int col; 384 int index; 385 int depth; 386} js_vm_frame_view_t; 387 388typedef bool 389 (*js_vm_frame_visitor_fn) 390 (ant_t *js, const js_vm_frame_view_t *view, void *ctx); 391 392static bool error_skip_bottom_wrapper_frame(const js_vm_frame_view_t *view) { 393 return 394 view && 395 view->index == 0 && 396 view->depth > 0 && 397 strcmp(view->name, "<anonymous>") == 0 && 398 view->line == 1 && 399 view->col == 1; 400} 401 402static bool error_fill_vm_frame_view( 403 ant_t *js, sv_vm_t *vm, int depth, int i, 404 const char *fallback_file, js_vm_frame_view_t *out 405) { 406 if (!js || !vm || i < 0 || i > depth || !out) return false; 407 408 sv_frame_t *frame = &vm->frames[i]; 409 sv_func_t *func = frame->func; 410 411 out->name = error_frame_name(js, frame, func); 412 out->file = (func && func->filename) ? func->filename : fallback_file; 413 out->line = (func && func->source_line > 0) ? func->source_line : 1; 414 out->col = 1; 415 out->index = i; 416 out->depth = depth; 417 418 if (func && func->srcpos && frame->ip) { 419 uint32_t l, c; 420 if (sv_lookup_srcpos(func, (int)(frame->ip - func->code), &l, &c)) { 421 out->line = (int)l; out->col = (int)c; 422 } 423 } 424 425 return true; 426} 427 428static void error_visit_vm_stack_frames( 429 ant_t *js, const char *fallback_file, js_vm_frame_visitor_fn visitor, void *ctx 430) { 431 if (!js || !visitor) return; 432 sv_vm_t *vm = sv_vm_get_active(js); 433 if (!vm) return; 434 435 int depth = vm->fp; 436 for (int i = depth; i >= 0; i--) { 437 js_vm_frame_view_t view; 438 if (!error_fill_vm_frame_view(js, vm, depth, i, fallback_file, &view)) continue; 439 if (error_skip_bottom_wrapper_frame(&view)) continue; 440 if (!visitor(js, &view, ctx)) break; 441 } 442} 443 444typedef struct { 445 errbuf_t *eb; 446 size_t *n; 447 size_t remaining; 448 const char *dim; 449 const char *reset; 450} error_frame_errbuf_ctx_t; 451 452static bool error_visit_frame_append_errbuf(ant_t *js, const js_vm_frame_view_t *view, void *ctx) { 453 error_frame_errbuf_ctx_t *c = (error_frame_errbuf_ctx_t *)ctx; 454 *c->n = append_errbuf_fmt( 455 c->eb, *c->n, 456 "\n at %s %s(%s:%d:%d)%s", 457 view->name, c->dim, view->file, view->line, view->col, c->reset 458 ); 459 c->remaining = remaining_capacity(*c->n, c->eb->size); 460 return c->remaining > 20; 461} 462 463ant_value_t js_capture_raw_stack(ant_t *js) { 464 errbuf_t eb = { malloc(4096), 4096 }; 465 if (!eb.buf) return js_mkundef(); 466 eb.buf[0] = '\0'; 467 468 size_t n = 0; 469 const char *file = (js->errsite.valid && js->errsite.filename) 470 ? js->errsite.filename 471 : (js->filename ? js->filename : "<eval>"); 472 473 sv_vm_t *vm = sv_vm_get_active(js); 474 if (vm && vm->fp >= 0) { 475 error_frame_errbuf_ctx_t ctx = { &eb, &n, remaining_capacity(n, eb.size), "", "" }; 476 error_visit_vm_stack_frames(js, file, error_visit_frame_append_errbuf, &ctx); 477 } 478 479 ant_value_t stack_str = js_mkstr(js, eb.buf, n); 480 free(eb.buf); 481 return stack_str; 482} 483 484static bool error_visit_frame_print_file(ant_t *js, const js_vm_frame_view_t *view, void *ctx) { 485 FILE *out = (FILE *)ctx; 486 fprintf( 487 out, " at %s (%s%s:%d:%d%s)\n", 488 view->name, C_GRAY, view->file, view->line, view->col, C_RESET 489 ); 490 return true; 491} 492 493static bool append_error_context( 494 errbuf_t *eb, size_t *n, 495 const char *src, ant_offset_t src_len, 496 ant_offset_t src_pos, 497 int error_line_no, int error_col, int error_span_cols 498) { 499 if (!src || src_len <= 0 || !n) return false; 500 if (src_pos < 0) src_pos = 0; 501 if (src_pos > src_len) src_pos = src_len; 502 503 ant_offset_t err_line_start = src_pos; 504 while (err_line_start > 0 && src[err_line_start - 1] != '\n') err_line_start--; 505 506 ant_offset_t err_line_end = src_pos; 507 while (err_line_end < src_len && src[err_line_end] != '\n' && src[err_line_end] != '\0') err_line_end++; 508 509 ant_offset_t ctx_start = err_line_start; 510 int first_line_no = error_line_no; 511 for (int i = 0; i < 5 && ctx_start > 0; i++) { 512 ant_offset_t prev = ctx_start - 1; 513 if (src[prev] == '\n') prev--; 514 while (prev >= 0 && src[prev] != '\n') prev--; 515 516 ctx_start = prev + 1; 517 first_line_no--; 518 if (first_line_no < 1) { first_line_no = 1; break; } 519 } 520 521 int gutter_w = count_digits_int(error_line_no); 522 int src_cols_limit = error_context_src_cols_limit(gutter_w); 523 524 char tagged[4096]; char rendered[8192]; 525 highlight_state hl_state = HL_STATE_INIT; 526 527 ant_offset_t cur = ctx_start; 528 int line_no = first_line_no; 529 530 while (cur <= err_line_end && cur < src_len) { 531 ant_offset_t ls = cur, le = cur; 532 while (le < src_len && src[le] != '\n' && src[le] != '\0') le++; 533 534 int line_len = (int)(le - ls); 535 bool was_clipped = (src_cols_limit > 0 && line_len > src_cols_limit); 536 537 if (!io_no_color) { 538 highlight_js_line_clipped( 539 src + ls, (size_t)line_len, (size_t)src_cols_limit, 540 tagged, sizeof(tagged), &hl_state 541 ); 542 crsprintf_stateful(rendered, sizeof(rendered), NULL, tagged); 543 *n = append_errbuf_fmt(eb, *n, "\n%*d | %s", gutter_w, line_no, rendered); 544 } else { 545 int shown = was_clipped ? src_cols_limit : line_len; 546 *n = append_errbuf_fmt(eb, *n, "\n%*d | %.*s", gutter_w, line_no, shown, src + ls); 547 } 548 549 if (was_clipped) *n = append_errbuf_fmt(eb, *n, "..."); 550 if (ls == err_line_start) { 551 *n = append_errbuf_fmt(eb, *n, "\n%*s ", gutter_w, ""); 552 int caret_col = error_col; 553 int caret_span = error_span_cols; 554 if (was_clipped) { 555 int max_col = src_cols_limit > 0 ? src_cols_limit : 1; 556 if (caret_col > max_col) caret_col = max_col; 557 if (caret_span > max_col - caret_col + 1) caret_span = max_col - caret_col + 1; 558 if (caret_span < 1) caret_span = 1; 559 } 560 append_error_caret(eb, n, caret_col, caret_span); 561 } 562 563 if (le >= src_len || src[le] == '\0') break; 564 cur = le + 1; line_no++; 565 } 566 567 return true; 568} 569 570static void format_error_stack(errbuf_t *eb, ant_t *js, size_t *n, int line, int col, bool include_source_line, const char *error_line, int error_col, int error_span_cols) { 571 if (!ensure_errbuf_capacity(eb, *n + 1)) return; 572 573 const char *dim = C_GRAY; 574 const char *reset = C_RESET; 575 576 if (include_source_line && error_line && error_line[0] && *n < eb->size) { 577 *n = append_errbuf_fmt(eb, *n, "\n%s\n", error_line); 578 append_error_caret(eb, n, error_col, error_span_cols); 579 } 580 581 size_t remaining = remaining_capacity(*n, eb->size); 582 if (remaining > 20) { 583 const char *file = (js->errsite.valid && js->errsite.filename) 584 ? js->errsite.filename 585 : (js->filename ? js->filename : "<eval>"); 586 587 sv_vm_t *vm = sv_vm_get_active(js); 588 int depth = vm ? vm->fp : -1; 589 590 if (depth >= 0) { 591 error_frame_errbuf_ctx_t ctx = { eb, n, remaining, dim, reset }; 592 error_visit_vm_stack_frames(js, file, error_visit_frame_append_errbuf, &ctx); 593 remaining = ctx.remaining; 594 } 595 596 if (depth <= 0 && remaining > 20) { 597 *n = append_errbuf_fmt(eb, *n, 598 "\n at %s%s:%d:%d%s", 599 dim, file, line, col, reset 600 ); 601 remaining = remaining_capacity(*n, eb->size); 602 } 603 604 if (remaining > 60 && js->filename && strcmp(js->filename, "[eval]") != 0) { 605 *n = append_errbuf_fmt(eb, *n, 606 "\n at silver.sv_execute_frame %s(ant:internal/silver/engine:323:5)%s", 607 dim, reset 608 ); 609 remaining = remaining_capacity(*n, eb->size); 610 } 611 612 if (remaining > 40 && js->filename && strcmp(js->filename, "[eval]") != 0) { 613 *n = append_errbuf_fmt(eb, *n, "\n at %sant:internal/call:13635:14%s", dim, reset); 614 } 615 } 616 617 eb->buf[eb->size - 1] = '\0'; 618} 619 620void js_set_error_site(ant_t *js, const char *src, ant_offset_t src_len, const char *filename, ant_offset_t off, ant_offset_t span_len) { 621 if (!js) return; 622 623 js->errsite.src = src; 624 js->errsite.src_len = src_len; 625 js->errsite.filename = filename; 626 js->errsite.off = off < 0 ? 0 : off; 627 js->errsite.span_len = span_len < 0 ? 0 : span_len; 628 js->errsite.valid = (src != NULL && src_len >= 0); 629} 630 631void js_get_call_location(ant_t *js, const char **out_filename, int *out_line, int *out_col) { 632 if (!js) return; 633 if (!js->errsite.valid) js_set_error_site_from_vm_top(js); 634 635 if (out_filename) *out_filename = (js->errsite.valid && js->errsite.filename) ? js->errsite.filename : js->filename; 636 if (out_line) *out_line = 1; 637 if (out_col) *out_col = 1; 638 639 if (js->errsite.valid && js->errsite.src) get_line_col( 640 js->errsite.src, js->errsite.src_len, 641 js->errsite.off, out_line, out_col 642 ); 643} 644 645void js_clear_error_site(ant_t *js) { 646 if (!js) return; 647 memset(&js->errsite, 0, sizeof(js->errsite)); 648} 649 650static void resolve_error_site( 651 ant_t *js, const char **out_src, ant_offset_t *out_src_len, 652 ant_offset_t *out_src_pos, ant_offset_t *out_span_len 653) { 654 if (js && !js->errsite.valid) js_set_error_site_from_vm_top(js); 655 656 const char *src = NULL; 657 ant_offset_t src_len = 0; 658 ant_offset_t src_pos = 0; 659 ant_offset_t span_len = 0; 660 661 if (js->errsite.valid) { 662 src = js->errsite.src; 663 src_len = js->errsite.src_len; 664 src_pos = js->errsite.off; 665 span_len = js->errsite.span_len; 666 } 667 668 if (out_src) *out_src = src; 669 if (out_src_len) *out_src_len = src_len; 670 if (out_src_pos) *out_src_pos = src_pos; 671 if (out_span_len) *out_span_len = span_len; 672} 673 674typedef struct { 675 const char *src; 676 ant_offset_t src_len; 677 ant_offset_t src_pos; 678 int error_col; 679 int error_span_cols; 680 int line; 681 int col; 682 char error_line[256]; 683} js_error_render_site_t; 684 685static void js_prepare_error_render_site(ant_t *js, js_error_render_site_t *site) { 686 if (!site) return; 687 memset(site, 0, sizeof(*site)); 688 site->line = 1; 689 site->col = 1; 690 site->error_col = 1; 691 site->error_span_cols = 1; 692 693 ant_offset_t src_span_len = 0; 694 ant_offset_t line_start = 0, line_end = 0; 695 resolve_error_site(js, &site->src, &site->src_len, &site->src_pos, &src_span_len); 696 697 get_line_col(site->src, site->src_len, site->src_pos, &site->line, &site->col); 698 get_error_line( 699 site->src, site->src_len, site->src_pos, 700 site->error_line, sizeof(site->error_line), 701 &site->error_col, &line_start, &line_end 702 ); 703 site->error_span_cols = error_span_cols_for_line(site->src_pos, src_span_len, line_start, line_end); 704} 705 706typedef enum { 707 JS_STACK_TEXT_FROM_ERROR_OBJECT = 0, 708 JS_STACK_TEXT_FROM_THROW_VALUE = 1, 709} js_stack_text_kind_t; 710 711static ant_value_t js_build_stack_text(ant_t *js, js_stack_text_kind_t kind, ant_value_t value) { 712 js_error_render_site_t site; 713 js_prepare_error_render_site(js, &site); 714 715 errbuf_t eb = { malloc(4096), 4096 }; 716 if (!eb.buf) return js_mkundef(); 717 eb.buf[0] = '\0'; 718 719 size_t n = 0; 720 n = append_error_header(&eb, js, 0, site.line, site.col); 721 722 if (n > 0 && eb.buf[n - 1] == '\n') { 723 n--; 724 eb.buf[n] = '\0'; 725 } 726 727 bool rendered_context = append_error_context( 728 &eb, &n, site.src, site.src_len, site.src_pos, 729 site.line, site.error_col, site.error_span_cols 730 ); 731 732 if (!rendered_context && site.error_line[0]) { 733 n = append_errbuf_fmt(&eb, n, "\n%s\n", site.error_line); 734 append_error_caret(&eb, &n, site.error_col, site.error_span_cols); 735 } 736 737 n = append_errbuf_fmt(&eb, n, "\n"); 738 739 if (kind == JS_STACK_TEXT_FROM_ERROR_OBJECT) { 740 const char *err_name = "Error"; 741 const char *err_msg = NULL; 742 743 ant_offset_t name_len = 5, msg_len = 0; 744 const char *n_str = get_str_prop(js, value, "name", 4, &name_len); 745 746 if (n_str) err_name = n_str; 747 err_msg = get_str_prop(js, value, "message", 7, &msg_len); 748 749 if (err_msg) { 750 n = append_errbuf_fmt(&eb, n, 751 "%s%.*s%s: %s%.*s%s", 752 C_RED, (int)name_len, err_name, C_RESET, 753 C_BOLD, (int)msg_len, err_msg, C_RESET); 754 } else { 755 n = append_errbuf_fmt(&eb, n, 756 "%s%.*s%s", 757 C_RED, (int)name_len, err_name, C_RESET); 758 } 759 } else n = append_error_value(&eb, js, n, value); 760 761 format_error_stack( 762 &eb, js, &n, site.line, site.col, false, 763 site.error_line, site.error_col, site.error_span_cols 764 ); 765 766 ant_value_t stack_str = js_mkstr(js, eb.buf, n); 767 free(eb.buf); 768 return stack_str; 769} 770 771void js_capture_stack(ant_t *js, ant_value_t err_obj) { 772 ant_value_t stack_str = js_build_stack_text(js, JS_STACK_TEXT_FROM_ERROR_OBJECT, err_obj); 773 if (vtype(stack_str) != T_STR) return; 774 775 js_set(js, err_obj, "stack", stack_str); 776 js_set_descriptor(js, js_as_obj(err_obj), "stack", 5, JS_DESC_W | JS_DESC_C); 777 js_clear_error_site(js); 778} 779 780js_err_type_t get_error_type(ant_t *js) { 781 if (!js->thrown_exists) return JS_ERR_GENERIC; 782 ant_value_t err_type = js_get_slot(js->thrown_value, SLOT_ERR_TYPE); 783 if (vtype(err_type) != T_NUM) return JS_ERR_GENERIC; 784 return (js_err_type_t)((int)js_getnum(err_type) & ~JS_ERR_NO_STACK); 785} 786 787__attribute__((format(printf, 4, 5))) 788ant_value_t js_create_error(ant_t *js, js_err_type_t err_type, ant_value_t props, const char *xx, ...) { 789 va_list ap; 790 char error_msg[256] = {0}; 791 792 bool no_stack = (err_type & JS_ERR_NO_STACK) != 0; 793 js_err_type_t base_type = (js_err_type_t)(err_type & ~JS_ERR_NO_STACK); 794 795 va_start(ap, xx); 796 vsnprintf(error_msg, sizeof(error_msg), xx, ap); 797 va_end(ap); 798 799 const char *err_name = get_error_type_name(base_type); 800 size_t err_name_len = strlen(err_name); 801 size_t msg_len = strlen(error_msg); 802 803 ant_value_t err_obj = js_mkobj(js); 804 js_set(js, err_obj, "name", js_mkstr(js, err_name, err_name_len)); 805 js_set(js, err_obj, "message", js_mkstr(js, error_msg, msg_len)); 806 js_set_slot(err_obj, SLOT_ERR_TYPE, js_mknum((double)err_type)); 807 808 int props_type = vtype(props); 809 if ((JS_TPFLG(props_type) & T_SPECIAL_OBJECT_MASK) != 0) { 810 js_merge_obj(js, err_obj, props); 811 } 812 ant_value_t proto = js_get_ctor_proto(js, err_name, err_name_len); 813 int proto_type = vtype(proto); 814 if ((JS_TPFLG(proto_type) & T_SPECIAL_OBJECT_MASK) != 0) { 815 js_set_proto_init(err_obj, proto); 816 } 817 818 js->thrown_exists = true; 819 js->thrown_value = err_obj; 820 821 if (!no_stack) { 822 js_capture_stack(js, err_obj); 823 } 824 825 js_clear_error_site(js); 826 return mkval(T_ERR, vdata(err_obj)); 827} 828 829ant_value_t js_make_error_silent(ant_t *js, js_err_type_t err_type, const char *message) { 830 bool had_throw = js->thrown_exists; 831 832 ant_value_t saved_value = had_throw ? js->thrown_value : js_mkundef(); 833 ant_value_t saved_stack = had_throw ? js->thrown_stack : js_mkundef(); 834 835 js_create_error(js, err_type, js_mkundef(), "%s", message); 836 ant_value_t err = js->thrown_value; 837 838 js->thrown_exists = had_throw; 839 js->thrown_value = saved_value; 840 js->thrown_stack = saved_stack; 841 842 return err; 843} 844 845ant_value_t js_throw(ant_t *js, ant_value_t value) { 846 if (vtype(value) == T_OBJ) { 847 ant_value_t existing = js_get(js, value, "stack"); 848 if (vtype(existing) == T_STR) { 849 js->thrown_exists = true; 850 js->thrown_value = value; 851 js_clear_error_site(js); 852 return mkval(T_ERR, vdata(value)); 853 } 854 ant_value_t slot = js_get_slot(value, SLOT_ERR_TYPE); 855 if (vtype(slot) == T_NUM && ((int)js_getnum(slot) & JS_ERR_NO_STACK)) { 856 js->thrown_exists = true; 857 js->thrown_value = value; 858 js_clear_error_site(js); 859 return mkval(T_ERR, vdata(value)); 860 } 861 } 862 863 ant_value_t stack_str = js_build_stack_text(js, JS_STACK_TEXT_FROM_THROW_VALUE, value); 864 if (vtype(stack_str) != T_STR) { 865 js->thrown_exists = true; 866 js->thrown_value = value; 867 js_clear_error_site(js); 868 return mkval(T_ERR, 0); 869 } 870 871 if (vtype(value) == T_OBJ) { 872 js_set(js, value, "stack", stack_str); 873 js_set_descriptor(js, js_as_obj(value), "stack", 5, JS_DESC_W | JS_DESC_C); 874 } 875 876 js->thrown_exists = true; 877 js->thrown_value = value; 878 js->thrown_stack = stack_str; 879 js_clear_error_site(js); 880 881 return mkval(T_ERR, 0); 882} 883 884enum { 885 CS_FILE = 0, 886 CS_LINE, 887 CS_COL, 888 CS_NAME 889}; 890 891static ant_value_t callsite_field(ant_t *js, int field) { 892 ant_value_t data = js_get_slot(js->this_val, SLOT_DATA); 893 if (vtype(data) != T_ARR) return js_mkundef(); 894 return js_arr_get(js, data, field); 895} 896 897static ant_value_t callsite_getFileName(ant_t *js, ant_value_t *args, int nargs) { return callsite_field(js, CS_FILE); } 898static ant_value_t callsite_getLineNumber(ant_t *js, ant_value_t *args, int nargs) { return callsite_field(js, CS_LINE); } 899static ant_value_t callsite_getColumnNumber(ant_t *js, ant_value_t *args, int nargs) { return callsite_field(js, CS_COL); } 900static ant_value_t callsite_getFunctionName(ant_t *js, ant_value_t *args, int nargs) { return callsite_field(js, CS_NAME); } 901 902static ant_value_t callsite_getTypeName(ant_t *js, ant_value_t *args, int nargs) { return js_mknull(); } 903static ant_value_t callsite_getMethodName(ant_t *js, ant_value_t *args, int nargs) { return callsite_field(js, CS_NAME); } 904 905static ant_value_t callsite_isNative(ant_t *js, ant_value_t *args, int nargs) { return js_false; } 906static ant_value_t callsite_isToplevel(ant_t *js, ant_value_t *args, int nargs) { return js_false; } 907static ant_value_t callsite_isEval(ant_t *js, ant_value_t *args, int nargs) { return js_false; } 908static ant_value_t callsite_isConstructor(ant_t *js, ant_value_t *args, int nargs) { return js_false; } 909static ant_value_t callsite_getEvalOrigin(ant_t *js, ant_value_t *args, int nargs) { return js_mkundef(); } 910static ant_value_t callsite_getThis(ant_t *js, ant_value_t *args, int nargs) { return js_mkundef(); } 911 912static ant_value_t callsite_toString(ant_t *js, ant_value_t *args, int nargs) { 913 ant_value_t name = callsite_field(js, CS_NAME); 914 ant_value_t file = callsite_field(js, CS_FILE); 915 ant_value_t line = callsite_field(js, CS_LINE); 916 ant_value_t col = callsite_field(js, CS_COL); 917 918 const char *n = js_str(js, name); 919 const char *f = js_str(js, file); 920 int l = vtype(line) == T_NUM ? (int)js_getnum(line) : 0; 921 int c = vtype(col) == T_NUM ? (int)js_getnum(col) : 0; 922 923 char buf[512]; 924 int len = snprintf(buf, sizeof(buf), "%s (%s:%d:%d)", n, f, l, c); 925 if (len < 0) len = 0; 926 return js_mkstr(js, buf, (size_t)len); 927} 928 929typedef struct { 930 ant_t *js; 931 ant_value_t arr; 932 ant_value_t proto; 933} callsite_build_ctx_t; 934 935static bool callsite_visit_frame(ant_t *js, const js_vm_frame_view_t *view, void *ctx) { 936 callsite_build_ctx_t *c = (callsite_build_ctx_t *)ctx; 937 938 ant_value_t data = js_mkarr(js); 939 js_arr_push(js, data, js_mkstr(js, view->file, strlen(view->file))); 940 js_arr_push(js, data, js_mknum((double)view->line)); 941 js_arr_push(js, data, js_mknum((double)view->col)); 942 js_arr_push(js, data, js_mkstr(js, view->name, strlen(view->name))); 943 944 ant_value_t site = js_mkobj(js); 945 js_set_proto_init(site, c->proto); 946 js_set_slot(site, SLOT_DATA, data); 947 948 js_arr_push(js, c->arr, site); 949 return true; 950} 951 952ant_value_t js_build_callsite_array(ant_t *js) { 953 ant_value_t proto = js_mkobj(js); 954 955 js_set(js, proto, "getFileName", js_mkfun(callsite_getFileName)); 956 js_set(js, proto, "getLineNumber", js_mkfun(callsite_getLineNumber)); 957 js_set(js, proto, "getColumnNumber", js_mkfun(callsite_getColumnNumber)); 958 js_set(js, proto, "getFunctionName", js_mkfun(callsite_getFunctionName)); 959 js_set(js, proto, "getTypeName", js_mkfun(callsite_getTypeName)); 960 js_set(js, proto, "getMethodName", js_mkfun(callsite_getMethodName)); 961 js_set(js, proto, "isNative", js_mkfun(callsite_isNative)); 962 js_set(js, proto, "isToplevel", js_mkfun(callsite_isToplevel)); 963 js_set(js, proto, "isEval", js_mkfun(callsite_isEval)); 964 js_set(js, proto, "isConstructor", js_mkfun(callsite_isConstructor)); 965 js_set(js, proto, "getEvalOrigin", js_mkfun(callsite_getEvalOrigin)); 966 js_set(js, proto, "getThis", js_mkfun(callsite_getThis)); 967 js_set(js, proto, "toString", js_mkfun(callsite_toString)); 968 969 ant_value_t arr = js_mkarr(js); 970 callsite_build_ctx_t ctx = { js, arr, proto }; 971 972 const char *file = (js->errsite.valid && js->errsite.filename) 973 ? js->errsite.filename 974 : (js->filename ? js->filename : "<eval>"); 975 976 error_visit_vm_stack_frames(js, file, callsite_visit_frame, &ctx); 977 return arr; 978} 979 980void js_print_stack_trace_vm(ant_t *js, FILE *stream) { 981 const char *fallback_file = js->filename ? js->filename : "<unknown>"; 982 if (!stream) return; 983 984 error_visit_vm_stack_frames( 985 js, fallback_file, 986 error_visit_frame_print_file, stream 987 ); 988}