]> git.proxmox.com Git - ceph.git/blob - ceph/src/civetweb/src/third_party/duktape-1.3.0/src-separate/duk_error_augment.c
add subtree-ish sources for 12.0.3
[ceph.git] / ceph / src / civetweb / src / third_party / duktape-1.3.0 / src-separate / duk_error_augment.c
1 /*
2 * Augmenting errors at their creation site and their throw site.
3 *
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.
8 *
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
13 * a useful manner.
14 *
15 * Error augmentation may throw an internal error (e.g. alloc error).
16 *
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.
22 */
23
24 #include "duk_internal.h"
25
26 /*
27 * Helper for calling a user error handler.
28 *
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.
33 *
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
37 * arguments and such.
38 *
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.
47 *
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.
52 *
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
58 * finishes.
59 */
60
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;
64 duk_tval *tv_hnd;
65 duk_small_uint_t call_flags;
66 duk_int_t rc;
67
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);
72
73 if (DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)) {
74 DUK_DD(DUK_DDPRINT("recursive call to error handler, ignore"));
75 return;
76 }
77
78 /*
79 * Check whether or not we have an error handler.
80 *
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.
86 */
87
88 DUK_ASSERT_VALSTACK_SPACE(thr, 4); /* 3 entries actually needed below */
89
90 /* [ ... errval ] */
91
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.
95 */
96 DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring"));
97 return;
98 }
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));
105 return;
106 }
107 DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T",
108 (duk_tval *) tv_hnd));
109 duk_push_tval(ctx, tv_hnd);
110
111 /* [ ... errval errhandler ] */
112
113 duk_insert(ctx, -2); /* -> [ ... errhandler errval ] */
114 duk_push_undefined(ctx);
115 duk_insert(ctx, -2); /* -> [ ... errhandler undefined(= this) errval ] */
116
117 /* [ ... errhandler undefined errval ] */
118
119 /*
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.
124 *
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.
131 *
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.)
134 */
135
136 DUK_ASSERT(!DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap)); /* since no recursive error handler calls */
137 DUK_HEAP_SET_ERRHANDLER_RUNNING(thr->heap);
138
139 call_flags = DUK_CALL_FLAG_PROTECTED |
140 DUK_CALL_FLAG_IGNORE_RECLIMIT; /* protected, ignore reclimit, not constructor */
141
142 rc = duk_handle_call(thr,
143 1, /* num args */
144 call_flags); /* call_flags */
145 DUK_UNREF(rc); /* no need to check now: both success and error are OK */
146
147 DUK_ASSERT(DUK_HEAP_HAS_ERRHANDLER_RUNNING(thr->heap));
148 DUK_HEAP_CLEAR_ERRHANDLER_RUNNING(thr->heap);
149
150 /* [ ... errval ] */
151 }
152 #endif /* DUK_USE_ERRTHROW || DUK_USE_ERRCREATE */
153
154 /*
155 * Add tracedata to an error on the stack top.
156 */
157
158 #ifdef DUK_USE_TRACEBACKS
159 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) {
160 duk_context *ctx = (duk_context *) thr;
161 duk_small_uint_t depth;
162 duk_int_t i, i_min;
163 duk_uarridx_t arr_idx;
164 duk_double_t d;
165
166 DUK_ASSERT(thr != NULL);
167 DUK_ASSERT(thr_callstack != NULL);
168 DUK_ASSERT(ctx != NULL);
169
170 /* [ ... error ] */
171
172 /*
173 * The traceback format is pretty arcane in an attempt to keep it compact
174 * and cheap to create. It may change arbitrarily from version to version.
175 * It should be decoded/accessed through version specific accessors only.
176 *
177 * See doc/error-objects.rst.
178 */
179
180 DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T",
181 (duk_tval *) duk_get_tval(ctx, -1)));
182
183 duk_push_array(ctx); /* XXX: specify array size, as we know it */
184 arr_idx = 0;
185
186 /* compiler SyntaxErrors (and other errors) come first; blame the source
187 * code file/line primarily.
188 */
189 if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) {
190 duk_push_hstring(ctx, thr->compile_ctx->h_filename);
191 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
192 arr_idx++;
193
194 duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line); /* (flags<<32) + (line), flags = 0 */
195 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
196 arr_idx++;
197 }
198
199 /* filename/line from C macros (__FILE__, __LINE__) are added as an
200 * entry with a special format: (string, number). The number contains
201 * the line and flags.
202 */
203
204 /* XXX: optimize: allocate an array part to the necessary size (upwards
205 * estimate) and fill in the values directly into the array part; finally
206 * update 'length'.
207 */
208
209 /* XXX: using duk_put_prop_index() would cause obscure error cases when Array.prototype
210 * has write-protected array index named properties. This was seen as DoubleErrors
211 * in e.g. some test262 test cases. Using duk_xdef_prop_index() is better but heavier.
212 * The best fix is to fill in the tracedata directly into the array part.
213 */
214
215 /* [ ... error arr ] */
216
217 if (c_filename) {
218 duk_push_string(ctx, c_filename);
219 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
220 arr_idx++;
221
222 d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) +
223 (duk_double_t) c_line;
224 duk_push_number(ctx, d);
225 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
226 arr_idx++;
227 }
228
229 /* traceback depth doesn't take into account the filename/line
230 * special handling above (intentional)
231 */
232 depth = DUK_USE_TRACEBACK_DEPTH;
233 i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0);
234 DUK_ASSERT(i_min >= 0);
235
236 /* [ ... error arr ] */
237
238 DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */
239 for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) {
240 duk_uint32_t pc;
241
242 /*
243 * Note: each API operation potentially resizes the callstack,
244 * so be careful to re-lookup after every operation. Currently
245 * these is no issue because we don't store a temporary 'act'
246 * pointer at all. (This would be a non-issue if we operated
247 * directly on the array part.)
248 */
249
250 /* [... arr] */
251
252 DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */
253
254 /* Add function object. */
255 duk_push_tval(ctx, &(thr_callstack->callstack + i)->tv_func);
256 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
257 arr_idx++;
258
259 /* Add a number containing: pc, activation flags.
260 *
261 * PC points to next instruction, find offending PC. Note that
262 * PC == 0 for native code.
263 */
264 pc = duk_hthread_get_act_prev_pc(thr_callstack, thr_callstack->callstack + i);
265 DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */
266 DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */
267 d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc;
268 duk_push_number(ctx, d); /* -> [... arr num] */
269 duk_xdef_prop_index_wec(ctx, -2, arr_idx);
270 arr_idx++;
271 }
272
273 /* XXX: set with duk_hobject_set_length() when tracedata is filled directly */
274 duk_push_uint(ctx, (duk_uint_t) arr_idx);
275 duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC);
276
277 /* [ ... error arr ] */
278
279 duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */
280 }
281 #endif /* DUK_USE_TRACEBACKS */
282
283 #if defined(DUK_USE_AUGMENT_ERROR_CREATE)
284 DUK_LOCAL void duk__err_augment_builtin_throw(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) {
285 duk_context *ctx = (duk_context *) thr;
286 #ifdef DUK_USE_ASSERTIONS
287 duk_int_t entry_top;
288 #endif
289
290 #ifdef DUK_USE_ASSERTIONS
291 entry_top = duk_get_top(ctx);
292 #endif
293 DUK_ASSERT(obj != NULL);
294
295 DUK_UNREF(obj); /* unreferenced w/o tracebacks */
296 DUK_UNREF(ctx); /* unreferenced w/ tracebacks */
297
298 #ifdef DUK_USE_TRACEBACKS
299 /*
300 * If tracebacks are enabled, the '_Tracedata' property is the only
301 * thing we need: 'fileName' and 'lineNumber' are virtual properties
302 * which use '_Tracedata'.
303 */
304
305 if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_INT_TRACEDATA(thr))) {
306 DUK_DDD(DUK_DDDPRINT("error value already has a '_Tracedata' property, not modifying it"));
307 } else {
308 duk__add_traceback(thr, thr_callstack, c_filename, c_line, noblame_fileline);
309 }
310 #else
311 /*
312 * If tracebacks are disabled, 'fileName' and 'lineNumber' are added
313 * as plain own properties. Since Error.prototype has accessors of
314 * the same name, we need to define own properties directly (cannot
315 * just use e.g. duk_put_prop_stridx). Existing properties are not
316 * overwritten in case they already exist.
317 */
318
319 if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) {
320 /* Compiler SyntaxError (or other error) gets the primary blame. */
321 duk_push_hstring(ctx, thr->compile_ctx->h_filename);
322 duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
323 duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line);
324 duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
325 } else if (c_filename && !noblame_fileline) {
326 /* XXX: file/line is disabled in minimal builds, so disable this too
327 * when appropriate.
328 */
329 duk_push_string(ctx, c_filename);
330 duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
331 duk_push_int(ctx, c_line);
332 duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
333 } else if (thr_callstack->callstack_top > 0) {
334 duk_activation *act;
335 duk_hobject *func;
336
337 act = thr_callstack->callstack + thr_callstack->callstack_top - 1;
338 DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size);
339 func = DUK_ACT_GET_FUNC(act);
340 if (func) {
341 duk_uint32_t pc;
342
343 /* PC points to next instruction, find offending PC. Note that
344 * PC == 0 for native code.
345 */
346 pc = duk_hthread_get_act_prev_pc(thr, act);
347 DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */
348 DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */
349 act = NULL; /* invalidated by pushes, so get out of the way */
350
351 duk_push_hobject(ctx, func);
352
353 /* [ ... error func ] */
354
355 duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME);
356 duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
357
358 #if defined(DUK_USE_PC2LINE)
359 if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
360 duk_uint32_t ecma_line;
361 #if 0
362 duk_push_u32(ctx, pc);
363 duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_PC, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAGS_NO_OVERWRITE);
364 #endif
365 ecma_line = duk_hobject_pc2line_query(ctx, -1, (duk_uint_fast32_t) pc);
366 if (ecma_line > 0) {
367 duk_push_u32(ctx, (duk_uint32_t) ecma_line); /* -> [ ... error func line ] */
368 duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
369 }
370 } else {
371 /* Native function, no relevant lineNumber. */
372 }
373 #endif /* DUK_USE_PC2LINE */
374
375 duk_pop(ctx);
376 }
377 }
378 #endif /* DUK_USE_TRACEBACKS */
379
380 #ifdef DUK_USE_ASSERTIONS
381 DUK_ASSERT(duk_get_top(ctx) == entry_top);
382 #endif
383 }
384 #endif /* DUK_USE_AUGMENT_ERROR_CREATE */
385
386 /*
387 * Augment an error at creation time with _Tracedata/fileName/lineNumber
388 * and allow a user error handler (if defined) to process/replace the error.
389 * The error to be augmented is at the stack top.
390 *
391 * thr: thread containing the error value
392 * thr_callstack: thread which should be used for generating callstack etc.
393 * c_filename: C __FILE__ related to the error
394 * c_line: C __LINE__ related to the error
395 * noblame_fileline: if true, don't fileName/line as error source, otherwise use traceback
396 * (needed because user code filename/line are reported but internal ones
397 * are not)
398 *
399 * XXX: rename noblame_fileline to flags field; combine it to some existing
400 * field (there are only a few call sites so this may not be worth it).
401 */
402
403 #if defined(DUK_USE_AUGMENT_ERROR_CREATE)
404 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) {
405 duk_context *ctx = (duk_context *) thr;
406 duk_hobject *obj;
407
408 DUK_ASSERT(thr != NULL);
409 DUK_ASSERT(thr_callstack != NULL);
410 DUK_ASSERT(ctx != NULL);
411
412 /* [ ... error ] */
413
414 /*
415 * Criteria for augmenting:
416 *
417 * - augmentation enabled in build (naturally)
418 * - error value internal prototype chain contains the built-in
419 * Error prototype object (i.e. 'val instanceof Error')
420 *
421 * Additional criteria for built-in augmenting:
422 *
423 * - error value is an extensible object
424 */
425
426 obj = duk_get_hobject(ctx, -1);
427 if (!obj) {
428 DUK_DDD(DUK_DDDPRINT("value is not an object, skip both built-in and user augment"));
429 return;
430 }
431 if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE], 1 /*ignore_loop*/)) {
432 /* If the value has a prototype loop, it's critical not to
433 * throw here. Instead, assume the value is not to be
434 * augmented.
435 */
436 DUK_DDD(DUK_DDDPRINT("value is not an error instance, skip both built-in and user augment"));
437 return;
438 }
439 if (DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
440 DUK_DDD(DUK_DDDPRINT("error meets criteria, built-in augment"));
441 duk__err_augment_builtin_throw(thr, thr_callstack, c_filename, c_line, noblame_fileline, obj);
442 } else {
443 DUK_DDD(DUK_DDDPRINT("error does not meet criteria, no built-in augment"));
444 }
445
446 /* [ ... error ] */
447
448 #if defined(DUK_USE_ERRCREATE)
449 duk__err_augment_user(thr, DUK_STRIDX_ERR_CREATE);
450 #endif
451 }
452 #endif /* DUK_USE_AUGMENT_ERROR_CREATE */
453
454 /*
455 * Augment an error at throw time; allow a user error handler (if defined)
456 * to process/replace the error. The error to be augmented is at the
457 * stack top.
458 */
459
460 #if defined(DUK_USE_AUGMENT_ERROR_THROW)
461 DUK_INTERNAL void duk_err_augment_error_throw(duk_hthread *thr) {
462 #if defined(DUK_USE_ERRTHROW)
463 duk__err_augment_user(thr, DUK_STRIDX_ERR_THROW);
464 #endif /* DUK_USE_ERRTHROW */
465 }
466 #endif /* DUK_USE_AUGMENT_ERROR_THROW */