You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
9.2 KiB

/*
* Augment an error object with custom fields like raw traceback data.
* An error is augmented when it is created, not when it is thrown
* (otherwise rethrowing would work poorly).
*
* May throw an error (e.g. alloc error).
*
* Ecmascript allows throwing any values, so all values cannot be
* augmented. Currently, we only augment error values which are Error
* instances (= have the built-in Error.prototype in their prototype
* chain) and are also extensible.
*/
#include "duk_internal.h"
#ifdef DUK_USE_AUGMENT_ERRORS
#ifdef DUK_USE_TRACEBACKS
static void add_traceback(duk_hthread *thr, duk_hthread *thr_callstack, duk_hobject *obj, int err_index, const char *filename, int line, int noblame_fileline) {
duk_context *ctx = (duk_context *) thr;
int depth;
int i, i_min;
int arr_idx;
double d;
DUK_UNREF(obj); /* FIXME: remove entire argument (it's accessed through the stack)? */
DUK_ASSERT(thr != NULL);
DUK_ASSERT(thr_callstack != NULL);
DUK_ASSERT(obj != NULL);
DUK_ASSERT(err_index >= 0);
DUK_ASSERT(ctx != NULL);
/*
* 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.txt.
*/
DUK_DDDPRINT("adding traceback to object: %!O", (duk_heaphdr *) obj);
duk_push_array(ctx); /* XXX: specify array size, as we know it */
arr_idx = 0;
/* 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.
*/
/* FIXME: optimize: allocate an array part to the necessary size (upwards
* estimate) and fill in the values directly into the array part; finally
* update 'length'.
*/
/* FIXME: 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_def_prop_index() is better but currently
* there is no fast path variant for that; the current implementation interns the array
* index as a string. This can be fixed directly, or perhaps the traceback can be fixed
* altogether to fill in the tracedata directly into the array part.
*/
if (filename) {
duk_push_string(ctx, filename);
duk_def_prop_index(ctx, -2, arr_idx, DUK_PROPDESC_FLAGS_WEC);
arr_idx++;
d = (noblame_fileline ? ((double) DUK_TB_FLAG_NOBLAME_FILELINE) * DUK_DOUBLE_2TO32 : 0.0) +
(double) line;
duk_push_number(ctx, d);
duk_def_prop_index(ctx, -2, arr_idx, DUK_PROPDESC_FLAGS_WEC);
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 > (size_t) depth ? (int) (thr_callstack->callstack_top - depth) : 0);
DUK_ASSERT(i_min >= 0);
for (i = thr_callstack->callstack_top - 1; i >= i_min; i--) {
int 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(thr_callstack->callstack[i].func != NULL);
DUK_ASSERT(thr_callstack->callstack[i].pc >= 0);
/* add function */
duk_push_hobject(ctx, thr_callstack->callstack[i].func); /* -> [... arr func] */
duk_def_prop_index(ctx, -2, arr_idx, DUK_PROPDESC_FLAGS_WEC);
arr_idx++;
/* add a number containing: pc, activation flags */
/* Add a number containing: pc, activation flag
*
* PC points to next instruction, find offending PC. Note that
* PC == 0 for native code.
*/
pc = thr_callstack->callstack[i].pc;
if (pc > 0) {
pc--;
}
DUK_ASSERT(pc >= 0 && (double) pc < DUK_DOUBLE_2TO32); /* assume PC is at most 32 bits and non-negative */
d = ((double) thr_callstack->callstack[i].flags) * DUK_DOUBLE_2TO32 + (double) pc;
duk_push_number(ctx, d); /* -> [... arr num] */
duk_def_prop_index(ctx, -2, arr_idx, DUK_PROPDESC_FLAGS_WEC);
arr_idx++;
}
/* FIXME: set with duk_hobject_set_length() when tracedata is filled directly */
duk_push_int(ctx, (int) arr_idx);
duk_def_prop_stridx(ctx, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC);
/* [... arr] */
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_TRACEDATA, DUK_PROPDESC_FLAGS_WEC); /* -> [...] */
}
#endif /* DUK_USE_TRACEBACKS */
/*
* Augment an error with tracedata, fileName, lineNumber, etc.
*
* thr: thread containing the error value
* thr_callstack: thread which should be used for generating callstack etc.
* err_index: index of error object to augment
* filename: C __FILE__ related to the error
* line: C __LINE__ related to the error
* noblame_fileline: if true, don't fileName/line as error source, otherwise use traceback
* (needed because user code filename/line are reported but internal ones
* are not)
*
* FIXME: rename noblame_fileline to flags field; combine it to some existing
* field (there are only a few call sites so this may not be worth it).
*/
void duk_err_augment_error(duk_hthread *thr, duk_hthread *thr_callstack, int err_index, const char *filename, int line, int noblame_fileline) {
duk_context *ctx = (duk_context *) thr;
duk_hobject *obj;
DUK_ASSERT(thr != NULL);
DUK_ASSERT(thr_callstack != NULL);
DUK_ASSERT(ctx != NULL);
err_index = duk_require_normalize_index(ctx, err_index);
/*
* Criteria for augmenting:
*
* - augmentation enabled in build (naturally)
* - error value is an extensible object
* - error value internal prototype chain contains the built-in
* Error prototype object (i.e. 'val instanceof Error')
*/
obj = duk_require_hobject(ctx, err_index);
if (!obj) {
DUK_DDDPRINT("error value not an object, not augmented");
return;
}
if (!DUK_HOBJECT_HAS_EXTENSIBLE(obj)) {
DUK_DDDPRINT("error value not extensible, not augmented");
return;
}
if (!duk_hobject_prototype_chain_contains(thr, obj, thr->builtins[DUK_BIDX_ERROR_PROTOTYPE])) {
DUK_DDDPRINT("error value not inherited from Error, not augmented");
return;
}
/* Yes, augment error. */
#ifdef DUK_USE_TRACEBACKS
/*
* If tracebacks are enabled, the 'tracedata' property is the only
* thing we need: 'fileName' and 'lineNumber' are virtual properties
* which use 'tracedata'.
*/
if (duk_hobject_hasprop_raw(thr, obj, DUK_HTHREAD_STRING_TRACEDATA(thr))) {
DUK_DDDPRINT("error value already has a 'traceback' property, not modifying it");
} else {
add_traceback(thr, thr_callstack, obj, err_index, filename, line, noblame_fileline);
}
#else
/*
* 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 (filename && !noblame_fileline) {
/* FIXME: file/line is disabled in minimal builds, so disable this too
* when appropriate.
*/
duk_push_string(ctx, filename);
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
duk_push_int(ctx, line);
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
} else if (thr_callstack->callstack_top > 0) {
duk_activation *act;
duk_hobject *func;
duk_hbuffer *pc2line;
act = thr_callstack->callstack + thr_callstack->callstack_top - 1;
DUK_ASSERT(act >= thr_callstack->callstack && act < thr_callstack->callstack + thr_callstack->callstack_size);
func = act->func;
if (func) {
int pc;
duk_uint32_t line;
/* PC points to next instruction, find offending PC. Note that
* PC == 0 for native code.
*/
pc = act->pc;
if (pc > 0) {
pc--;
}
DUK_ASSERT(pc >= 0 && (double) 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);
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_FILE_NAME);
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) {
#if 0
duk_push_number(ctx, pc);
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_PC, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAGS_NO_OVERWRITE);
#endif
duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_PC2LINE);
if (duk_is_buffer(ctx, -1)) {
pc2line = duk_get_hbuffer(ctx, -1);
DUK_ASSERT(pc2line != NULL);
DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(pc2line));
line = duk_hobject_pc2line_query((duk_hbuffer_fixed *) pc2line, (duk_uint_fast32_t) pc);
duk_push_number(ctx, (double) line); /* FIXME: u32 */
duk_def_prop_stridx(ctx, err_index, DUK_STRIDX_LINE_NUMBER, DUK_PROPDESC_FLAGS_WC | DUK_PROPDESC_FLAG_NO_OVERWRITE);
}
duk_pop(ctx);
} else {
/* Native function, no relevant lineNumber. */
}
duk_pop(ctx);
}
}
#endif /* DUK_USE_TRACEBACKS */
}
#endif /* DUK_USE_AUGMENT_ERRORS */