2 * Augmenting errors at their creation site and their throw site.
4 * When errors are created, traceback data is added by built-in code
5 * and a user error handler (if defined) can process or replace the
6 * error. Similarly, when errors are thrown, a user error handler
7 * (if defined) can process or replace the error.
9 * Augmentation and other processing at error creation time is nice
10 * because an error is only created once, but it may be thrown and
11 * rethrown multiple times. User error handler registered for processing
12 * an error at its throw site must be careful to handle rethrowing in
15 * Error augmentation may throw an internal error (e.g. alloc error).
17 * Ecmascript allows throwing any values, so all values cannot be
18 * augmented. Currently, the built-in augmentation at error creation
19 * only augments error values which are Error instances (= have the
20 * built-in Error.prototype in their prototype chain) and are also
21 * extensible. User error handlers have no limitations in this respect.
24 #include "duk_internal.h"
27 * Helper for calling a user error handler.
29 * 'thr' must be the currently active thread; the error handler is called
30 * in its context. The valstack of 'thr' must have the error value on
31 * top, and will be replaced by another error value based on the return
32 * value of the error handler.
34 * The helper calls duk_handle_call() recursively in protected mode.
35 * Before that call happens, no longjmps should happen; as a consequence,
36 * we must assume that the valstack contains enough temporary space for
39 * While the error handler runs, any errors thrown will not trigger a
40 * recursive error handler call (this is implemented using a heap level
41 * flag which will "follow" through any coroutines resumed inside the
42 * error handler). If the error handler is not callable or throws an
43 * error, the resulting error replaces the original error (for Duktape
44 * internal errors, duk_error_throw.c further substitutes this error with
45 * a DoubleError which is not ideal). This would be easy to change and
46 * even signal to the caller.
48 * The user error handler is stored in 'Duktape.errCreate' or
49 * 'Duktape.errThrow' depending on whether we're augmenting the error at
50 * creation or throw time. There are several alternatives to this approach,
51 * see doc/error-objects.rst for discussion.
53 * Note: since further longjmp()s may occur while calling the error handler
54 * (for many reasons, e.g. a labeled 'break' inside the handler), the
55 * caller can make no assumptions on the thr->heap->lj state after the
56 * call (this affects especially duk_error_throw.c). This is not an issue
57 * as long as the caller writes to the lj state only after the error handler
61 #if defined(DUK_USE_ERRTHROW) || defined(DUK_USE_ERRCREATE)
62 DUK_LOCAL
void duk__err_augment_user(duk_hthread
*thr
, duk_small_uint_t stridx_cb
) {
63 duk_context
*ctx
= (duk_context
*) thr
;
65 duk_small_uint_t call_flags
;
68 DUK_ASSERT(thr
!= NULL
);
69 DUK_ASSERT(thr
->heap
!= NULL
);
70 DUK_ASSERT_DISABLE(stridx_cb
>= 0); /* unsigned */
71 DUK_ASSERT(stridx_cb
< DUK_HEAP_NUM_STRINGS
);
73 if (DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr
->heap
)) {
74 DUK_DD(DUK_DDPRINT("recursive call to error handler, ignore"));
79 * Check whether or not we have an error handler.
81 * We must be careful of not triggering an error when looking up the
82 * property. For instance, if the property is a getter, we don't want
83 * to call it, only plain values are allowed. The value, if it exists,
84 * is not checked. If the value is not a function, a TypeError happens
85 * when it is called and that error replaces the original one.
88 DUK_ASSERT_VALSTACK_SPACE(thr
, 4); /* 3 entries actually needed below */
92 if (thr
->builtins
[DUK_BIDX_DUKTAPE
] == NULL
) {
93 /* When creating built-ins, some of the built-ins may not be set
94 * and we want to tolerate that when throwing errors.
96 DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring"));
99 tv_hnd
= duk_hobject_find_existing_entry_tval_ptr(thr
->heap
,
100 thr
->builtins
[DUK_BIDX_DUKTAPE
],
101 DUK_HTHREAD_GET_STRING(thr
, stridx_cb
));
102 if (tv_hnd
== NULL
) {
103 DUK_DD(DUK_DDPRINT("error handler does not exist or is not a plain value: %!T",
104 (duk_tval
*) tv_hnd
));
107 DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T",
108 (duk_tval
*) tv_hnd
));
109 duk_push_tval(ctx
, tv_hnd
);
111 /* [ ... errval errhandler ] */
113 duk_insert(ctx
, -2); /* -> [ ... errhandler errval ] */
114 duk_push_undefined(ctx
);
115 duk_insert(ctx
, -2); /* -> [ ... errhandler undefined(= this) errval ] */
117 /* [ ... errhandler undefined errval ] */
120 * DUK_CALL_FLAG_IGNORE_RECLIMIT causes duk_handle_call() to ignore C
121 * recursion depth limit (and won't increase it either). This is
122 * dangerous, but useful because it allows the error handler to run
123 * even if the original error is caused by C recursion depth limit.
125 * The heap level DUK_HEAP_FLAG_ERRHANDLER_RUNNING is set for the
126 * duration of the error handler and cleared afterwards. This flag
127 * prevents the error handler from running recursively. The flag is
128 * heap level so that the flag properly controls even coroutines
129 * launched by an error handler. Since the flag is heap level, it is
130 * critical to restore it correctly.
132 * We ignore errors now: a success return and an error value both
133 * replace the original error value. (This would be easy to change.)
136 DUK_ASSERT(!DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr
->heap
)); /* since no recursive error handler calls */
137 DUK_HEAP_SET_ERRHANDLER_RUNNING(thr
->heap
);
139 call_flags
= DUK_CALL_FLAG_IGNORE_RECLIMIT
; /* ignore reclimit, not constructor */
141 rc
= duk_handle_call_protected(thr
,
143 call_flags
); /* call_flags */
144 DUK_UNREF(rc
); /* no need to check now: both success and error are OK */
146 DUK_ASSERT(DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr
->heap
));
147 DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(thr
->heap
);
151 #endif /* DUK_USE_ERRTHROW || DUK_USE_ERRCREATE */
154 * Add ._Tracedata to an error on the stack top.
157 #if defined(DUK_USE_TRACEBACKS)
158 DUK_LOCAL
void duk__add_traceback(duk_hthread
*thr
, duk_hthread
*thr_callstack
, const char *c_filename
, duk_int_t c_line
, duk_bool_t noblame_fileline
) {
159 duk_context
*ctx
= (duk_context
*) thr
;
160 duk_small_uint_t depth
;
162 duk_uarridx_t arr_idx
;
165 DUK_ASSERT(thr
!= NULL
);
166 DUK_ASSERT(thr_callstack
!= NULL
);
167 DUK_ASSERT(ctx
!= NULL
);
172 * The traceback format is pretty arcane in an attempt to keep it compact
173 * and cheap to create. It may change arbitrarily from version to version.
174 * It should be decoded/accessed through version specific accessors only.
176 * See doc/error-objects.rst.
179 DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T",
180 (duk_tval
*) duk_get_tval(ctx
, -1)));
182 duk_push_array(ctx
); /* XXX: specify array size, as we know it */
185 /* Compiler SyntaxErrors (and other errors) come first, and are
186 * blamed by default (not flagged "noblame").
188 if (thr
->compile_ctx
!= NULL
&& thr
->compile_ctx
->h_filename
!= NULL
) {
189 duk_push_hstring(ctx
, thr
->compile_ctx
->h_filename
);
190 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
193 duk_push_uint(ctx
, (duk_uint_t
) thr
->compile_ctx
->curr_token
.start_line
); /* (flags<<32) + (line), flags = 0 */
194 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
198 /* Filename/line from C macros (__FILE__, __LINE__) are added as an
199 * entry with a special format: (string, number). The number contains
200 * the line and flags.
203 /* XXX: optimize: allocate an array part to the necessary size (upwards
204 * estimate) and fill in the values directly into the array part; finally
208 /* XXX: using duk_put_prop_index() would cause obscure error cases when Array.prototype
209 * has write-protected array index named properties. This was seen as DoubleErrors
210 * in e.g. some test262 test cases. Using duk_xdef_prop_index() is better but heavier.
211 * The best fix is to fill in the tracedata directly into the array part. There are
212 * no side effect concerns if the array part is allocated directly and only INCREFs
216 /* [ ... error arr ] */
219 duk_push_string(ctx
, c_filename
);
220 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
223 d
= (noblame_fileline
? ((duk_double_t
) DUK_TB_FLAG_NOBLAME_FILELINE
) * DUK_DOUBLE_2TO32
: 0.0) +
224 (duk_double_t
) c_line
;
225 duk_push_number(ctx
, d
);
226 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
230 /* traceback depth doesn't take into account the filename/line
231 * special handling above (intentional)
233 depth
= DUK_USE_TRACEBACK_DEPTH
;
234 i_min
= (thr_callstack
->callstack_top
> (duk_size_t
) depth
? (duk_int_t
) (thr_callstack
->callstack_top
- depth
) : 0);
235 DUK_ASSERT(i_min
>= 0);
237 /* [ ... error arr ] */
239 DUK_ASSERT(thr_callstack
->callstack_top
<= DUK_INT_MAX
); /* callstack limits */
240 for (i
= (duk_int_t
) (thr_callstack
->callstack_top
- 1); i
>= i_min
; i
--) {
244 * Note: each API operation potentially resizes the callstack,
245 * so be careful to re-lookup after every operation. Currently
246 * these is no issue because we don't store a temporary 'act'
247 * pointer at all. (This would be a non-issue if we operated
248 * directly on the array part.)
253 DUK_ASSERT_DISABLE(thr_callstack
->callstack
[i
].pc
>= 0); /* unsigned */
255 /* Add function object. */
256 duk_push_tval(ctx
, &(thr_callstack
->callstack
+ i
)->tv_func
);
257 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
260 /* Add a number containing: pc, activation flags.
262 * PC points to next instruction, find offending PC. Note that
263 * PC == 0 for native code.
265 pc
= duk_hthread_get_act_prev_pc(thr_callstack
, thr_callstack
->callstack
+ i
);
266 DUK_ASSERT_DISABLE(pc
>= 0); /* unsigned */
267 DUK_ASSERT((duk_double_t
) pc
< DUK_DOUBLE_2TO32
); /* assume PC is at most 32 bits and non-negative */
268 d
= ((duk_double_t
) thr_callstack
->callstack
[i
].flags
) * DUK_DOUBLE_2TO32
+ (duk_double_t
) pc
;
269 duk_push_number(ctx
, d
); /* -> [... arr num] */
270 duk_xdef_prop_index_wec(ctx
, -2, arr_idx
);
274 /* XXX: set with duk_hobject_set_length() when tracedata is filled directly */
275 duk_push_uint(ctx
, (duk_uint_t
) arr_idx
);
276 duk_xdef_prop_stridx(ctx
, -2, DUK_STRIDX_LENGTH
, DUK_PROPDESC_FLAGS_WC
);
278 /* [ ... error arr ] */
280 duk_xdef_prop_stridx_wec(ctx
, -2, DUK_STRIDX_INT_TRACEDATA
); /* -> [ ... error ] */
282 #endif /* DUK_USE_TRACEBACKS */
285 * Add .fileName and .lineNumber to an error on the stack top.
288 #if !defined(DUK_USE_TRACEBACKS)
289 DUK_LOCAL
void duk__add_fileline(duk_hthread
*thr
, duk_hthread
*thr_callstack
, const char *c_filename
, duk_int_t c_line
, duk_bool_t noblame_fileline
) {
291 #if defined(DUK_USE_ASSERTIONS)
295 ctx
= (duk_context
*) thr
;
296 #if defined(DUK_USE_ASSERTIONS)
297 entry_top
= duk_get_top(ctx
);
301 * If tracebacks are disabled, 'fileName' and 'lineNumber' are added
302 * as plain own properties. Since Error.prototype has accessors of
303 * the same name, we need to define own properties directly (cannot
304 * just use e.g. duk_put_prop_stridx). Existing properties are not
305 * overwritten in case they already exist.
308 if (thr
->compile_ctx
!= NULL
&& thr
->compile_ctx
->h_filename
!= NULL
) {
309 /* Compiler SyntaxError (or other error) gets the primary blame.
310 * Currently no flag to prevent blaming.
312 duk_push_uint(ctx
, (duk_uint_t
) thr
->compile_ctx
->curr_token
.start_line
);
313 duk_push_hstring(ctx
, thr
->compile_ctx
->h_filename
);
314 } else if (c_filename
&& !noblame_fileline
) {
315 /* C call site gets blamed next, unless flagged not to do so.
316 * XXX: file/line is disabled in minimal builds, so disable this
317 * too when appropriate.
319 duk_push_int(ctx
, c_line
);
320 duk_push_string(ctx
, c_filename
);
322 /* Finally, blame the innermost callstack entry which has a
323 * .fileName property.
325 duk_small_uint_t depth
;
327 duk_uint32_t ecma_line
;
329 depth
= DUK_USE_TRACEBACK_DEPTH
;
330 i_min
= (thr_callstack
->callstack_top
> (duk_size_t
) depth
? (duk_int_t
) (thr_callstack
->callstack_top
- depth
) : 0);
331 DUK_ASSERT(i_min
>= 0);
333 DUK_ASSERT(thr_callstack
->callstack_top
<= DUK_INT_MAX
); /* callstack limits */
334 for (i
= (duk_int_t
) (thr_callstack
->callstack_top
- 1); i
>= i_min
; i
--) {
340 act
= thr_callstack
->callstack
+ i
;
341 DUK_ASSERT(act
>= thr_callstack
->callstack
&& act
< thr_callstack
->callstack
+ thr_callstack
->callstack_size
);
343 func
= DUK_ACT_GET_FUNC(act
);
345 /* Lightfunc, not blamed now. */
349 /* PC points to next instruction, find offending PC,
350 * PC == 0 for native code.
352 pc
= duk_hthread_get_act_prev_pc(thr
, act
); /* thr argument only used for thr->heap, so specific thread doesn't matter */
353 DUK_ASSERT_DISABLE(pc
>= 0); /* unsigned */
354 DUK_ASSERT((duk_double_t
) pc
< DUK_DOUBLE_2TO32
); /* assume PC is at most 32 bits and non-negative */
355 act
= NULL
; /* invalidated by pushes, so get out of the way */
357 duk_push_hobject(ctx
, func
);
359 /* [ ... error func ] */
361 duk_get_prop_stridx(ctx
, -1, DUK_STRIDX_FILE_NAME
);
362 if (!duk_is_string(ctx
, -1)) {
367 /* [ ... error func fileName ] */
370 #if defined(DUK_USE_PC2LINE)
371 if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func
)) {
372 ecma_line
= duk_hobject_pc2line_query(ctx
, -2, (duk_uint_fast32_t
) pc
);
374 /* Native function, no relevant lineNumber. */
376 #endif /* DUK_USE_PC2LINE */
377 duk_push_u32(ctx
, ecma_line
);
379 /* [ ... error func fileName lineNumber ] */
381 duk_replace(ctx
, -3);
383 /* [ ... error lineNumber fileName ] */
387 /* No activation matches, use undefined for both .fileName and
388 * .lineNumber (matches what we do with a _Tracedata based
391 duk_push_undefined(ctx
);
392 duk_push_undefined(ctx
);
396 /* [ ... error lineNumber fileName ] */
397 #if defined(DUK_USE_ASSERTIONS)
398 DUK_ASSERT(duk_get_top(ctx
) == entry_top
+ 2);
400 duk_xdef_prop_stridx(ctx
, -3, DUK_STRIDX_FILE_NAME
, DUK_PROPDESC_FLAGS_WC
| DUK_PROPDESC_FLAG_NO_OVERWRITE
);
401 duk_xdef_prop_stridx(ctx
, -2, DUK_STRIDX_LINE_NUMBER
, DUK_PROPDESC_FLAGS_WC
| DUK_PROPDESC_FLAG_NO_OVERWRITE
);
403 #endif /* !DUK_USE_TRACEBACKS */
406 * Add line number to a compiler error.
409 DUK_LOCAL
void duk__add_compiler_error_line(duk_hthread
*thr
) {
412 /* Append a "(line NNN)" to the "message" property of any error
413 * thrown during compilation. Usually compilation errors are
414 * SyntaxErrors but they can also be out-of-memory errors and
420 ctx
= (duk_context
*) thr
;
421 DUK_ASSERT(duk_is_object(ctx
, -1));
423 if (!(thr
->compile_ctx
!= NULL
&& thr
->compile_ctx
->h_filename
!= NULL
)) {
427 DUK_DDD(DUK_DDDPRINT("compile error, before adding line info: %!T",
428 (duk_tval
*) duk_get_tval(ctx
, -1)));
430 if (duk_get_prop_stridx(ctx
, -1, DUK_STRIDX_MESSAGE
)) {
431 duk_push_sprintf(ctx
, " (line %ld)", (long) thr
->compile_ctx
->curr_token
.start_line
);
433 duk_put_prop_stridx(ctx
, -2, DUK_STRIDX_MESSAGE
);
438 DUK_DDD(DUK_DDDPRINT("compile error, after adding line info: %!T",
439 (duk_tval
*) duk_get_tval(ctx
, -1)));
443 * Augment an error being created using Duktape specific properties
444 * like _Tracedata or .fileName/.lineNumber.
447 #if defined(DUK_USE_AUGMENT_ERROR_CREATE)
448 DUK_LOCAL
void duk__err_augment_builtin_create(duk_hthread
*thr
, duk_hthread
*thr_callstack
, const char *c_filename
, duk_int_t c_line
, duk_small_int_t noblame_fileline
, duk_hobject
*obj
) {
449 duk_context
*ctx
= (duk_context
*) thr
;
450 #if defined(DUK_USE_ASSERTIONS)
454 #if defined(DUK_USE_ASSERTIONS)
455 entry_top
= duk_get_top(ctx
);
457 DUK_ASSERT(obj
!= NULL
);
459 DUK_UNREF(obj
); /* unreferenced w/o tracebacks */
460 DUK_UNREF(ctx
); /* unreferenced w/o asserts */
462 duk__add_compiler_error_line(thr
);
464 #if defined(DUK_USE_TRACEBACKS)
465 /* If tracebacks are enabled, the '_Tracedata' property is the only
466 * thing we need: 'fileName' and 'lineNumber' are virtual properties
467 * which use '_Tracedata'.
469 if (duk_hobject_hasprop_raw(thr
, obj
, DUK_HTHREAD_STRING_INT_TRACEDATA(thr
))) {
470 DUK_DDD(DUK_DDDPRINT("error value already has a '_Tracedata' property, not modifying it"));
472 duk__add_traceback(thr
, thr_callstack
, c_filename
, c_line
, noblame_fileline
);
475 /* Without tracebacks the concrete .fileName and .lineNumber need
476 * to be added directly.
478 duk__add_fileline(thr
, thr_callstack
, c_filename
, c_line
, noblame_fileline
);
481 #if defined(DUK_USE_ASSERTIONS)
482 DUK_ASSERT(duk_get_top(ctx
) == entry_top
);
485 #endif /* DUK_USE_AUGMENT_ERROR_CREATE */
488 * Augment an error at creation time with _Tracedata/fileName/lineNumber
489 * and allow a user error handler (if defined) to process/replace the error.
490 * The error to be augmented is at the stack top.
492 * thr: thread containing the error value
493 * thr_callstack: thread which should be used for generating callstack etc.
494 * c_filename: C __FILE__ related to the error
495 * c_line: C __LINE__ related to the error
496 * noblame_fileline: if true, don't fileName/line as error source, otherwise use traceback
497 * (needed because user code filename/line are reported but internal ones
500 * XXX: rename noblame_fileline to flags field; combine it to some existing
501 * field (there are only a few call sites so this may not be worth it).
504 #if defined(DUK_USE_AUGMENT_ERROR_CREATE)
505 DUK_INTERNAL
void duk_err_augment_error_create(duk_hthread
*thr
, duk_hthread
*thr_callstack
, const char *c_filename
, duk_int_t c_line
, duk_bool_t noblame_fileline
) {
506 duk_context
*ctx
= (duk_context
*) thr
;
509 DUK_ASSERT(thr
!= NULL
);
510 DUK_ASSERT(thr_callstack
!= NULL
);
511 DUK_ASSERT(ctx
!= NULL
);
516 * Criteria for augmenting:
518 * - augmentation enabled in build (naturally)
519 * - error value internal prototype chain contains the built-in
520 * Error prototype object (i.e. 'val instanceof Error')
522 * Additional criteria for built-in augmenting:
524 * - error value is an extensible object
527 obj
= duk_get_hobject(ctx
, -1);
529 DUK_DDD(DUK_DDDPRINT("value is not an object, skip both built-in and user augment"));
532 if (!duk_hobject_prototype_chain_contains(thr
, obj
, thr
->builtins
[DUK_BIDX_ERROR_PROTOTYPE
], 1 /*ignore_loop*/)) {
533 /* If the value has a prototype loop, it's critical not to
534 * throw here. Instead, assume the value is not to be
537 DUK_DDD(DUK_DDDPRINT("value is not an error instance, skip both built-in and user augment"));
540 if (DUK_HOBJECT_HAS_EXTENSIBLE(obj
)) {
541 DUK_DDD(DUK_DDDPRINT("error meets criteria, built-in augment"));
542 duk__err_augment_builtin_create(thr
, thr_callstack
, c_filename
, c_line
, noblame_fileline
, obj
);
544 DUK_DDD(DUK_DDDPRINT("error does not meet criteria, no built-in augment"));
549 #if defined(DUK_USE_ERRCREATE)
550 duk__err_augment_user(thr
, DUK_STRIDX_ERR_CREATE
);
553 #endif /* DUK_USE_AUGMENT_ERROR_CREATE */
556 * Augment an error at throw time; allow a user error handler (if defined)
557 * to process/replace the error. The error to be augmented is at the
561 #if defined(DUK_USE_AUGMENT_ERROR_THROW)
562 DUK_INTERNAL
void duk_err_augment_error_throw(duk_hthread
*thr
) {
563 #if defined(DUK_USE_ERRTHROW)
564 duk__err_augment_user(thr
, DUK_STRIDX_ERR_THROW
);
565 #endif /* DUK_USE_ERRTHROW */
567 #endif /* DUK_USE_AUGMENT_ERROR_THROW */