MIRROR: javascript for 馃悳's, a tiny runtime with big ambitions
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}