| /* |
| * Error built-ins |
| */ |
| |
| #include "duk_internal.h" |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_constructor_shared(duk_context *ctx) { |
| /* Behavior for constructor and non-constructor call is |
| * the same except for augmenting the created error. When |
| * called as a constructor, the caller (duk_new()) will handle |
| * augmentation; when called as normal function, we need to do |
| * it here. |
| */ |
| |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_small_int_t bidx_prototype = duk_get_current_magic(ctx); |
| |
| /* same for both error and each subclass like TypeError */ |
| duk_uint_t flags_and_class = DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ERROR); |
| |
| DUK_UNREF(thr); |
| |
| duk_push_object_helper(ctx, flags_and_class, bidx_prototype); |
| |
| /* If message is undefined, the own property 'message' is not set at |
| * all to save property space. An empty message is inherited anyway. |
| */ |
| if (!duk_is_undefined(ctx, 0)) { |
| duk_to_string(ctx, 0); |
| duk_dup(ctx, 0); /* [ message error message ] */ |
| duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE, DUK_PROPDESC_FLAGS_WC); |
| } |
| |
| /* Augment the error if called as a normal function. __FILE__ and __LINE__ |
| * are not desirable in this case. |
| */ |
| |
| #ifdef DUK_USE_AUGMENT_ERROR_CREATE |
| if (!duk_is_constructor_call(ctx)) { |
| duk_err_augment_error_create(thr, thr, NULL, 0, 1 /*noblame_fileline*/); |
| } |
| #endif |
| |
| return 1; |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_to_string(duk_context *ctx) { |
| /* XXX: optimize with more direct internal access */ |
| |
| duk_push_this(ctx); |
| (void) duk_require_hobject_or_lfunc_coerce(ctx, -1); |
| |
| /* [ ... this ] */ |
| |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_NAME); |
| if (duk_is_undefined(ctx, -1)) { |
| duk_pop(ctx); |
| duk_push_string(ctx, "Error"); |
| } else { |
| duk_to_string(ctx, -1); |
| } |
| |
| /* [ ... this name ] */ |
| |
| /* XXX: Are steps 6 and 7 in E5 Section 15.11.4.4 duplicated by |
| * accident or are they actually needed? The first ToString() |
| * could conceivably return 'undefined'. |
| */ |
| duk_get_prop_stridx(ctx, -2, DUK_STRIDX_MESSAGE); |
| if (duk_is_undefined(ctx, -1)) { |
| duk_pop(ctx); |
| duk_push_string(ctx, ""); |
| } else { |
| duk_to_string(ctx, -1); |
| } |
| |
| /* [ ... this name message ] */ |
| |
| if (duk_get_length(ctx, -2) == 0) { |
| /* name is empty -> return message */ |
| return 1; |
| } |
| if (duk_get_length(ctx, -1) == 0) { |
| /* message is empty -> return name */ |
| duk_pop(ctx); |
| return 1; |
| } |
| duk_push_string(ctx, ": "); |
| duk_insert(ctx, -2); /* ... name ': ' message */ |
| duk_concat(ctx, 3); |
| |
| return 1; |
| } |
| |
| #if defined(DUK_USE_TRACEBACKS) |
| |
| /* |
| * Traceback handling |
| * |
| * The unified helper decodes the traceback and produces various requested |
| * outputs. It should be optimized for size, and may leave garbage on stack, |
| * only the topmost return value matters. For instance, traceback separator |
| * and decoded strings are pushed even when looking for filename only. |
| * |
| * NOTE: although _Tracedata is an internal property, user code can currently |
| * write to the array (or replace it with something other than an array). |
| * The code below must tolerate arbitrary _Tracedata. It can throw errors |
| * etc, but cannot cause a segfault or memory unsafe behavior. |
| */ |
| |
| /* constants arbitrary, chosen for small loads */ |
| #define DUK__OUTPUT_TYPE_TRACEBACK (-1) |
| #define DUK__OUTPUT_TYPE_FILENAME 0 |
| #define DUK__OUTPUT_TYPE_LINENUMBER 1 |
| |
| DUK_LOCAL duk_ret_t duk__error_getter_helper(duk_context *ctx, duk_small_int_t output_type) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_idx_t idx_td; |
| duk_small_int_t i; /* traceback depth fits into 16 bits */ |
| duk_small_int_t t; /* stack type fits into 16 bits */ |
| duk_small_int_t count_func = 0; /* traceback depth ensures fits into 16 bits */ |
| const char *str_tailcall = " tailcall"; |
| const char *str_strict = " strict"; |
| const char *str_construct = " construct"; |
| const char *str_prevyield = " preventsyield"; |
| const char *str_directeval = " directeval"; |
| const char *str_empty = ""; |
| |
| DUK_ASSERT_TOP(ctx, 0); /* fixed arg count */ |
| DUK_UNREF(thr); |
| |
| duk_push_this(ctx); |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_TRACEDATA); |
| idx_td = duk_get_top_index(ctx); |
| |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_NEWLINE_4SPACE); |
| duk_push_this(ctx); |
| |
| /* [ ... this tracedata sep this ] */ |
| |
| /* XXX: skip null filename? */ |
| |
| if (duk_check_type(ctx, idx_td, DUK_TYPE_OBJECT)) { |
| /* Current tracedata contains 2 entries per callstack entry. */ |
| for (i = 0; ; i += 2) { |
| duk_int_t pc; |
| duk_int_t line; |
| duk_int_t flags; |
| duk_double_t d; |
| const char *funcname; |
| const char *filename; |
| duk_hobject *h_func; |
| duk_hstring *h_name; |
| |
| duk_require_stack(ctx, 5); |
| duk_get_prop_index(ctx, idx_td, i); |
| duk_get_prop_index(ctx, idx_td, i + 1); |
| d = duk_to_number(ctx, -1); |
| pc = (duk_int_t) DUK_FMOD(d, DUK_DOUBLE_2TO32); |
| flags = (duk_int_t) DUK_FLOOR(d / DUK_DOUBLE_2TO32); |
| t = (duk_small_int_t) duk_get_type(ctx, -2); |
| |
| if (t == DUK_TYPE_OBJECT || t == DUK_TYPE_LIGHTFUNC) { |
| /* |
| * Ecmascript/native function call or lightfunc call |
| */ |
| |
| count_func++; |
| |
| /* [ ... v1(func) v2(pc+flags) ] */ |
| |
| h_func = duk_get_hobject(ctx, -2); /* NULL for lightfunc */ |
| |
| duk_get_prop_stridx(ctx, -2, DUK_STRIDX_NAME); |
| duk_get_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME); |
| |
| #if defined(DUK_USE_PC2LINE) |
| line = duk_hobject_pc2line_query(ctx, -4, (duk_uint_fast32_t) pc); |
| #else |
| line = 0; |
| #endif |
| |
| /* [ ... v1 v2 name filename ] */ |
| |
| /* When looking for .fileName/.lineNumber, blame first |
| * function which has a .fileName. |
| */ |
| if (duk_is_string(ctx, -1)) { |
| if (output_type == DUK__OUTPUT_TYPE_FILENAME) { |
| return 1; |
| } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { |
| duk_push_int(ctx, line); |
| return 1; |
| } |
| } |
| |
| /* XXX: Change 'anon' handling here too, to use empty string for anonymous functions? */ |
| /* XXX: Could be improved by coercing to a readable duk_tval (especially string escaping) */ |
| h_name = duk_get_hstring(ctx, -2); /* may be NULL */ |
| funcname = (h_name == NULL || h_name == DUK_HTHREAD_STRING_EMPTY_STRING(thr)) ? |
| "[anon]" : (const char *) DUK_HSTRING_GET_DATA(h_name); |
| filename = duk_get_string(ctx, -1); |
| filename = filename ? filename : ""; |
| DUK_ASSERT(funcname != NULL); |
| DUK_ASSERT(filename != NULL); |
| |
| if (h_func == NULL) { |
| duk_push_sprintf(ctx, "at %s light%s%s%s%s%s", |
| (const char *) funcname, |
| (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); |
| } else if (DUK_HOBJECT_HAS_NATIVEFUNCTION(h_func)) { |
| duk_push_sprintf(ctx, "at %s (%s) native%s%s%s%s%s", |
| (const char *) funcname, |
| (const char *) filename, |
| (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); |
| } else { |
| duk_push_sprintf(ctx, "at %s (%s:%ld)%s%s%s%s%s", |
| (const char *) funcname, |
| (const char *) filename, |
| (long) line, |
| (const char *) ((flags & DUK_ACT_FLAG_STRICT) ? str_strict : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_TAILCALLED) ? str_tailcall : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_CONSTRUCT) ? str_construct : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_DIRECT_EVAL) ? str_directeval : str_empty), |
| (const char *) ((flags & DUK_ACT_FLAG_PREVENT_YIELD) ? str_prevyield : str_empty)); |
| } |
| duk_replace(ctx, -5); /* [ ... v1 v2 name filename str ] -> [ ... str v2 name filename ] */ |
| duk_pop_n(ctx, 3); /* -> [ ... str ] */ |
| } else if (t == DUK_TYPE_STRING) { |
| /* |
| * __FILE__ / __LINE__ entry, here 'pc' is line number directly. |
| * Sometimes __FILE__ / __LINE__ is reported as the source for |
| * the error (fileName, lineNumber), sometimes not. |
| */ |
| |
| /* [ ... v1(filename) v2(line+flags) ] */ |
| |
| /* When looking for .fileName/.lineNumber, blame compilation |
| * or C call site unless flagged not to do so. |
| */ |
| if (!(flags & DUK_TB_FLAG_NOBLAME_FILELINE)) { |
| if (output_type == DUK__OUTPUT_TYPE_FILENAME) { |
| duk_pop(ctx); |
| return 1; |
| } else if (output_type == DUK__OUTPUT_TYPE_LINENUMBER) { |
| duk_push_int(ctx, pc); |
| return 1; |
| } |
| } |
| |
| duk_push_sprintf(ctx, "at [anon] (%s:%ld) internal", |
| (const char *) duk_get_string(ctx, -2), (long) pc); |
| duk_replace(ctx, -3); /* [ ... v1 v2 str ] -> [ ... str v2 ] */ |
| duk_pop(ctx); /* -> [ ... str ] */ |
| } else { |
| /* unknown, ignore */ |
| duk_pop_2(ctx); |
| break; |
| } |
| } |
| |
| if (count_func >= DUK_USE_TRACEBACK_DEPTH) { |
| /* Possibly truncated; there is no explicit truncation |
| * marker so this is the best we can do. |
| */ |
| |
| duk_push_hstring_stridx(ctx, DUK_STRIDX_BRACKETED_ELLIPSIS); |
| } |
| } |
| |
| /* [ ... this tracedata sep this str1 ... strN ] */ |
| |
| if (output_type != DUK__OUTPUT_TYPE_TRACEBACK) { |
| return 0; |
| } else { |
| /* The 'this' after 'sep' will get ToString() coerced by |
| * duk_join() automatically. We don't want to do that |
| * coercion when providing .fileName or .lineNumber (GH-254). |
| */ |
| duk_join(ctx, duk_get_top(ctx) - (idx_td + 2) /*count, not including sep*/); |
| return 1; |
| } |
| } |
| |
| /* XXX: Output type could be encoded into native function 'magic' value to |
| * save space. For setters the stridx could be encoded into 'magic'. |
| */ |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx) { |
| return duk__error_getter_helper(ctx, DUK__OUTPUT_TYPE_TRACEBACK); |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx) { |
| return duk__error_getter_helper(ctx, DUK__OUTPUT_TYPE_FILENAME); |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx) { |
| return duk__error_getter_helper(ctx, DUK__OUTPUT_TYPE_LINENUMBER); |
| } |
| |
| #undef DUK__OUTPUT_TYPE_TRACEBACK |
| #undef DUK__OUTPUT_TYPE_FILENAME |
| #undef DUK__OUTPUT_TYPE_LINENUMBER |
| |
| #else /* DUK_USE_TRACEBACKS */ |
| |
| /* |
| * Traceback handling when tracebacks disabled. |
| * |
| * The fileName / lineNumber stubs are now necessary because built-in |
| * data will include the accessor properties in Error.prototype. If those |
| * are removed for builds without tracebacks, these can also be removed. |
| * 'stack' should still be present and produce a ToString() equivalent: |
| * this is useful for user code which prints a stacktrace and expects to |
| * see something useful. A normal stacktrace also begins with a ToString() |
| * of the error so this makes sense. |
| */ |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_getter(duk_context *ctx) { |
| /* XXX: remove this native function and map 'stack' accessor |
| * to the toString() implementation directly. |
| */ |
| return duk_bi_error_prototype_to_string(ctx); |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_getter(duk_context *ctx) { |
| DUK_UNREF(ctx); |
| return 0; |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_getter(duk_context *ctx) { |
| DUK_UNREF(ctx); |
| return 0; |
| } |
| |
| #endif /* DUK_USE_TRACEBACKS */ |
| |
| DUK_LOCAL duk_ret_t duk__error_setter_helper(duk_context *ctx, duk_small_uint_t stridx_key) { |
| /* Attempt to write 'stack', 'fileName', 'lineNumber' works as if |
| * user code called Object.defineProperty() to create an overriding |
| * own property. This allows user code to overwrite .fileName etc |
| * intuitively as e.g. "err.fileName = 'dummy'" as one might expect. |
| * See https://github.com/svaarala/duktape/issues/387. |
| */ |
| |
| DUK_ASSERT_TOP(ctx, 1); /* fixed arg count: value */ |
| |
| duk_push_this(ctx); |
| duk_push_hstring_stridx(ctx, (duk_small_int_t) stridx_key); |
| duk_dup(ctx, 0); |
| |
| /* [ ... obj key value ] */ |
| |
| DUK_DD(DUK_DDPRINT("error setter: %!T %!T %!T", |
| duk_get_tval(ctx, -3), duk_get_tval(ctx, -2), duk_get_tval(ctx, -1))); |
| |
| duk_def_prop(ctx, -3, DUK_DEFPROP_HAVE_VALUE | |
| DUK_DEFPROP_HAVE_WRITABLE | DUK_DEFPROP_WRITABLE | |
| DUK_DEFPROP_HAVE_ENUMERABLE | /*not enumerable*/ |
| DUK_DEFPROP_HAVE_CONFIGURABLE | DUK_DEFPROP_CONFIGURABLE); |
| return 0; |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_stack_setter(duk_context *ctx) { |
| return duk__error_setter_helper(ctx, DUK_STRIDX_STACK); |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_filename_setter(duk_context *ctx) { |
| return duk__error_setter_helper(ctx, DUK_STRIDX_FILE_NAME); |
| } |
| |
| DUK_INTERNAL duk_ret_t duk_bi_error_prototype_linenumber_setter(duk_context *ctx) { |
| return duk__error_setter_helper(ctx, DUK_STRIDX_LINE_NUMBER); |
| } |