DUK_INTERNAL duk_ret_t duk_bi_duktape_object_act(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_activation *act; duk_uint_fast32_t pc; duk_uint_fast32_t line; duk_int_t level; /* -1 = top callstack entry, callstack[callstack_top - 1] * -callstack_top = bottom callstack entry, callstack[0] */ level = duk_to_int(ctx, 0); if (level >= 0 || -level > (duk_int_t) thr->callstack_top) { return 0; } DUK_ASSERT(level >= -((duk_int_t) thr->callstack_top) && level <= -1); act = thr->callstack + thr->callstack_top + level; duk_push_object(ctx); duk_push_tval(ctx, &act->tv_func); /* Relevant PC is just before current one because PC is * post-incremented. This should match what error augment * code does. */ pc = duk_hthread_get_act_prev_pc(thr, act); duk_push_uint(ctx, (duk_uint_t) pc); #if defined(DUK_USE_PC2LINE) line = duk_hobject_pc2line_query(ctx, -2, pc); #else line = 0; #endif duk_push_uint(ctx, (duk_uint_t) line); /* Providing access to e.g. act->lex_env would be dangerous: these * internal structures must never be accessible to the application. * Duktape relies on them having consistent data, and this consistency * is only asserted for, not checked for. */ /* [ level obj func pc line ] */ /* XXX: version specific array format instead? */ duk_xdef_prop_stridx_wec(ctx, -4, DUK_STRIDX_LINE_NUMBER); duk_xdef_prop_stridx_wec(ctx, -3, DUK_STRIDX_PC); duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_LC_FUNCTION); return 1; }
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) { duk_context *ctx; #if defined(DUK_USE_ASSERTIONS) duk_int_t entry_top; #endif ctx = (duk_context *) thr; #if defined(DUK_USE_ASSERTIONS) entry_top = duk_get_top(ctx); #endif /* * If tracebacks are disabled, 'fileName' and 'lineNumber' are added * as plain own properties. Since Error.prototype has accessors of * the same name, we need to define own properties directly (cannot * just use e.g. duk_put_prop_stridx). Existing properties are not * overwritten in case they already exist. */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { /* Compiler SyntaxError (or other error) gets the primary blame. * Currently no flag to prevent blaming. */ duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line); duk_push_hstring(ctx, thr->compile_ctx->h_filename); } else if (c_filename && !noblame_fileline) { /* C call site gets blamed next, unless flagged not to do so. * XXX: file/line is disabled in minimal builds, so disable this * too when appropriate. */ duk_push_int(ctx, c_line); duk_push_string(ctx, c_filename); } else { /* Finally, blame the innermost callstack entry which has a * .fileName property. */ duk_small_uint_t depth; duk_int_t i, i_min; duk_uint32_t ecma_line; depth = DUK_USE_TRACEBACK_DEPTH; i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0); DUK_ASSERT(i_min >= 0); DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) { duk_activation *act; duk_hobject *func; duk_uint32_t pc; act = thr_callstack->callstack + i; DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size); func = DUK_ACT_GET_FUNC(act); if (func == NULL) { /* Lightfunc, not blamed now. */ continue; } /* PC points to next instruction, find offending PC, * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr, act); /* thr argument only used for thr->heap, so specific thread doesn't matter */ DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ act = NULL; /* invalidated by pushes, so get out of the way */ duk_push_hobject(ctx, func); /* [ ... error func ] */ duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME); if (!duk_is_string(ctx, -1)) { duk_pop_2(ctx); continue; } /* [ ... error func fileName ] */ ecma_line = 0; #if defined(DUK_USE_PC2LINE) if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { ecma_line = duk_hobject_pc2line_query(ctx, -2, (duk_uint_fast32_t) pc); } else { /* Native function, no relevant lineNumber. */ } #endif /* DUK_USE_PC2LINE */ duk_push_u32(ctx, ecma_line); /* [ ... error func fileName lineNumber ] */ duk_replace(ctx, -3); /* [ ... error lineNumber fileName ] */ goto define_props; } /* No activation matches, use undefined for both .fileName and * .lineNumber (matches what we do with a _Tracedata based * no-match lookup. */ duk_push_undefined(ctx); duk_push_undefined(ctx); } define_props: /* [ ... error lineNumber fileName ] */ #if defined(DUK_USE_ASSERTIONS) DUK_ASSERT(duk_get_top(ctx) == entry_top + 2); #endif duk_xdef_prop_stridx(ctx, -3, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE); }
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) { duk_context *ctx = (duk_context *) thr; duk_small_uint_t depth; duk_int_t i, i_min; duk_uarridx_t arr_idx; duk_double_t d; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); DUK_ASSERT(ctx != NULL); /* [ ... error ] */ /* * The traceback format is pretty arcane in an attempt to keep it compact * and cheap to create. It may change arbitrarily from version to version. * It should be decoded/accessed through version specific accessors only. * * See doc/error-objects.rst. */ DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T", (duk_tval *) duk_get_tval(ctx, -1))); duk_push_array(ctx); /* XXX: specify array size, as we know it */ arr_idx = 0; /* Compiler SyntaxErrors (and other errors) come first, and are * blamed by default (not flagged "noblame"). */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { duk_push_hstring(ctx, thr->compile_ctx->h_filename); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; duk_push_uint(ctx, (duk_uint_t) thr->compile_ctx->curr_token.start_line); /* (flags<<32) + (line), flags = 0 */ duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* Filename/line from C macros (__FILE__, __LINE__) are added as an * entry with a special format: (string, number). The number contains * the line and flags. */ /* XXX: optimize: allocate an array part to the necessary size (upwards * estimate) and fill in the values directly into the array part; finally * update 'length'. */ /* XXX: using duk_put_prop_index() would cause obscure error cases when Array.prototype * has write-protected array index named properties. This was seen as DoubleErrors * in e.g. some test262 test cases. Using duk_xdef_prop_index() is better but heavier. * The best fix is to fill in the tracedata directly into the array part. There are * no side effect concerns if the array part is allocated directly and only INCREFs * happen after that. */ /* [ ... error arr ] */ if (c_filename) { duk_push_string(ctx, c_filename); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) + (duk_double_t) c_line; duk_push_number(ctx, d); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* traceback depth doesn't take into account the filename/line * special handling above (intentional) */ depth = DUK_USE_TRACEBACK_DEPTH; i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0); DUK_ASSERT(i_min >= 0); /* [ ... error arr ] */ DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) { duk_uint32_t pc; /* * Note: each API operation potentially resizes the callstack, * so be careful to re-lookup after every operation. Currently * these is no issue because we don't store a temporary 'act' * pointer at all. (This would be a non-issue if we operated * directly on the array part.) */ /* [... arr] */ DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */ /* Add function object. */ duk_push_tval(ctx, &(thr_callstack->callstack + i)->tv_func); duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; /* Add a number containing: pc, activation flags. * * PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr_callstack, thr_callstack->callstack + i); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc; duk_push_number(ctx, d); /* -> [... arr num] */ duk_xdef_prop_index_wec(ctx, -2, arr_idx); arr_idx++; } /* XXX: set with duk_hobject_set_length() when tracedata is filled directly */ duk_push_uint(ctx, (duk_uint_t) arr_idx); duk_xdef_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC); /* [ ... error arr ] */ duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */ }
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) { duk_context *ctx = (duk_context *) thr; duk_small_uint_t depth; duk_int_t i, i_min; duk_int_t arr_size; duk_harray *a; duk_tval *tv; duk_hstring *s; duk_uint32_t u32; duk_double_t d; DUK_ASSERT(thr != NULL); DUK_ASSERT(thr_callstack != NULL); DUK_ASSERT(ctx != NULL); /* [ ... error ] */ /* * The traceback format is pretty arcane in an attempt to keep it compact * and cheap to create. It may change arbitrarily from version to version. * It should be decoded/accessed through version specific accessors only. * * See doc/error-objects.rst. */ DUK_DDD(DUK_DDDPRINT("adding traceback to object: %!T", (duk_tval *) duk_get_tval(ctx, -1))); /* Preallocate array to correct size, so that we can just write out * the _Tracedata values into the array part. */ depth = DUK_USE_TRACEBACK_DEPTH; arr_size = (duk_int_t) (thr_callstack->callstack_top <= depth ? thr_callstack->callstack_top : depth) * 2; if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { arr_size += 2; } if (c_filename) { /* We need the C filename to be interned before getting the * array part pointer to avoid any GC interference while the * array part is populated. */ duk_push_string(ctx, c_filename); arr_size += 2; } DUK_D(DUK_DPRINT("preallocated _Tracedata to %ld items", (long) arr_size)); a = duk_push_harray_with_size(ctx, (duk_uint32_t) arr_size); /* XXX: call which returns array part pointer directly */ DUK_ASSERT(a != NULL); tv = DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a); DUK_ASSERT(tv != NULL || arr_size == 0); /* Compiler SyntaxErrors (and other errors) come first, and are * blamed by default (not flagged "noblame"). */ if (thr->compile_ctx != NULL && thr->compile_ctx->h_filename != NULL) { s = thr->compile_ctx->h_filename; DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; u32 = (duk_uint32_t) thr->compile_ctx->curr_token.start_line; /* (flags<<32) + (line), flags = 0 */ DUK_TVAL_SET_U32(tv, u32); tv++; } /* Filename/line from C macros (__FILE__, __LINE__) are added as an * entry with a special format: (string, number). The number contains * the line and flags. */ /* [ ... error c_filename? arr ] */ if (c_filename) { DUK_ASSERT(DUK_TVAL_IS_STRING(thr->valstack_top - 2)); s = DUK_TVAL_GET_STRING(thr->valstack_top - 2); /* interned c_filename */ DUK_ASSERT(s != NULL); DUK_TVAL_SET_STRING(tv, s); DUK_HSTRING_INCREF(thr, s); tv++; d = (noblame_fileline ? ((duk_double_t) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) + (duk_double_t) c_line; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } /* traceback depth doesn't take into account the filename/line * special handling above (intentional) */ depth = DUK_USE_TRACEBACK_DEPTH; i_min = (thr_callstack->callstack_top > (duk_size_t) depth ? (duk_int_t) (thr_callstack->callstack_top - depth) : 0); DUK_ASSERT(i_min >= 0); /* [ ... error c_filename? arr ] */ DUK_ASSERT(thr_callstack->callstack_top <= DUK_INT_MAX); /* callstack limits */ for (i = (duk_int_t) (thr_callstack->callstack_top - 1); i >= i_min; i--) { duk_uint32_t pc; duk_tval *tv_src; /* * Note: each API operation potentially resizes the callstack, * so be careful to re-lookup after every operation. Currently * these is no issue because we don't store a temporary 'act' * pointer at all. (This would be a non-issue if we operated * directly on the array part.) */ /* [... arr] */ DUK_ASSERT_DISABLE(thr_callstack->callstack[i].pc >= 0); /* unsigned */ /* Add function object. */ tv_src = &(thr_callstack->callstack + i)->tv_func; /* object (function) or lightfunc */ DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv_src) || DUK_TVAL_IS_LIGHTFUNC(tv_src)); DUK_TVAL_SET_TVAL(tv, tv_src); DUK_TVAL_INCREF(thr, tv); tv++; /* Add a number containing: pc, activation flags. * * PC points to next instruction, find offending PC. Note that * PC == 0 for native code. */ pc = duk_hthread_get_act_prev_pc(thr_callstack, thr_callstack->callstack + i); DUK_ASSERT_DISABLE(pc >= 0); /* unsigned */ DUK_ASSERT((duk_double_t) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */ d = ((duk_double_t) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (duk_double_t) pc; DUK_TVAL_SET_DOUBLE(tv, d); tv++; } DUK_ASSERT((duk_uint32_t) (tv - DUK_HOBJECT_A_GET_BASE(thr->heap, (duk_hobject *) a)) == a->length); DUK_ASSERT(a->length == (duk_uint32_t) arr_size); /* [ ... error c_filename? arr ] */ if (c_filename) { duk_remove(ctx, -2); } /* [ ... error arr ] */ duk_xdef_prop_stridx_wec(ctx, -2, DUK_STRIDX_INT_TRACEDATA); /* -> [ ... error ] */ }