230 lines
7.3 KiB
C
230 lines
7.3 KiB
C
/*
|
|
* Inspection
|
|
*/
|
|
|
|
#include "third_party/duktape/duk_internal.h"
|
|
|
|
/* For footprint efficient multiple value setting: arrays are much better than
|
|
* varargs, format string with parsing is often better than string pointer arrays.
|
|
*/
|
|
DUK_LOCAL void duk__inspect_multiple_uint(duk_hthread *thr, const char *fmt, duk_int_t *vals) {
|
|
duk_int_t val;
|
|
const char *p;
|
|
const char *p_curr;
|
|
duk_size_t len;
|
|
|
|
for (p = fmt;;) {
|
|
len = DUK_STRLEN(p);
|
|
p_curr = p;
|
|
p += len + 1;
|
|
if (len == 0) {
|
|
/* Double NUL (= empty key) terminates. */
|
|
break;
|
|
}
|
|
val = *vals++;
|
|
if (val >= 0) {
|
|
/* Negative values are markers to skip key. */
|
|
duk_push_string(thr, p_curr);
|
|
duk_push_int(thr, val);
|
|
duk_put_prop(thr, -3);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Raw helper to extract internal information / statistics about a value.
|
|
* The return value is an object with properties that are version specific.
|
|
* The properties must not expose anything that would lead to security
|
|
* issues (e.g. exposing compiled function 'data' buffer might be an issue).
|
|
* Currently only counts and sizes and such are given so there shouldn't
|
|
* be security implications.
|
|
*/
|
|
|
|
#define DUK__IDX_TYPE 0
|
|
#define DUK__IDX_ITAG 1
|
|
#define DUK__IDX_REFC 2
|
|
#define DUK__IDX_HBYTES 3
|
|
#define DUK__IDX_CLASS 4
|
|
#define DUK__IDX_PBYTES 5
|
|
#define DUK__IDX_ESIZE 6
|
|
#define DUK__IDX_ENEXT 7
|
|
#define DUK__IDX_ASIZE 8
|
|
#define DUK__IDX_HSIZE 9
|
|
#define DUK__IDX_BCBYTES 10
|
|
#define DUK__IDX_DBYTES 11
|
|
#define DUK__IDX_TSTATE 12
|
|
#define DUK__IDX_VARIANT 13
|
|
|
|
DUK_EXTERNAL void duk_inspect_value(duk_hthread *thr, duk_idx_t idx) {
|
|
duk_tval *tv;
|
|
duk_heaphdr *h;
|
|
/* The temporary values should be in an array rather than individual
|
|
* variables which (in practice) ensures that the compiler won't map
|
|
* them to registers and emit a lot of unnecessary shuffling code.
|
|
*/
|
|
duk_int_t vals[14];
|
|
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
|
|
/* Assume two's complement and set everything to -1. */
|
|
duk_memset((void *) &vals, (int) 0xff, sizeof(vals));
|
|
DUK_ASSERT(vals[DUK__IDX_TYPE] == -1); /* spot check one */
|
|
|
|
tv = duk_get_tval_or_unused(thr, idx);
|
|
h = (DUK_TVAL_IS_HEAP_ALLOCATED(tv) ? DUK_TVAL_GET_HEAPHDR(tv) : NULL);
|
|
|
|
vals[DUK__IDX_TYPE] = duk_get_type_tval(tv);
|
|
vals[DUK__IDX_ITAG] = (duk_int_t) DUK_TVAL_GET_TAG(tv);
|
|
|
|
duk_push_bare_object(thr); /* Invalidates 'tv'. */
|
|
tv = NULL;
|
|
|
|
if (h == NULL) {
|
|
goto finish;
|
|
}
|
|
duk_push_pointer(thr, (void *) h);
|
|
duk_put_prop_literal(thr, -2, "hptr");
|
|
|
|
#if 0
|
|
/* Covers a lot of information, e.g. buffer and string variants. */
|
|
duk_push_uint(thr, (duk_uint_t) DUK_HEAPHDR_GET_FLAGS(h));
|
|
duk_put_prop_literal(thr, -2, "hflags");
|
|
#endif
|
|
|
|
#if defined(DUK_USE_REFERENCE_COUNTING)
|
|
vals[DUK__IDX_REFC] = (duk_int_t) DUK_HEAPHDR_GET_REFCOUNT(h);
|
|
#endif
|
|
vals[DUK__IDX_VARIANT] = 0;
|
|
|
|
/* Heaphdr size and additional allocation size, followed by
|
|
* type specific stuff (with varying value count).
|
|
*/
|
|
switch ((duk_small_int_t) DUK_HEAPHDR_GET_TYPE(h)) {
|
|
case DUK_HTYPE_STRING: {
|
|
duk_hstring *h_str = (duk_hstring *) h;
|
|
vals[DUK__IDX_HBYTES] = (duk_int_t) (sizeof(duk_hstring) + DUK_HSTRING_GET_BYTELEN(h_str) + 1);
|
|
#if defined(DUK_USE_HSTRING_EXTDATA)
|
|
if (DUK_HSTRING_HAS_EXTDATA(h_str)) {
|
|
vals[DUK__IDX_VARIANT] = 1;
|
|
}
|
|
#endif
|
|
break;
|
|
}
|
|
case DUK_HTYPE_OBJECT: {
|
|
duk_hobject *h_obj = (duk_hobject *) h;
|
|
|
|
/* XXX: variants here are maybe pointless; class is enough? */
|
|
if (DUK_HOBJECT_IS_ARRAY(h_obj)) {
|
|
vals[DUK__IDX_HBYTES] = sizeof(duk_harray);
|
|
} else if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) {
|
|
vals[DUK__IDX_HBYTES] = sizeof(duk_hcompfunc);
|
|
} else if (DUK_HOBJECT_IS_NATFUNC(h_obj)) {
|
|
vals[DUK__IDX_HBYTES] = sizeof(duk_hnatfunc);
|
|
} else if (DUK_HOBJECT_IS_THREAD(h_obj)) {
|
|
vals[DUK__IDX_HBYTES] = sizeof(duk_hthread);
|
|
vals[DUK__IDX_TSTATE] = ((duk_hthread *) h_obj)->state;
|
|
#if defined(DUK_USE_BUFFEROBJECT_SUPPORT)
|
|
} else if (DUK_HOBJECT_IS_BUFOBJ(h_obj)) {
|
|
vals[DUK__IDX_HBYTES] = sizeof(duk_hbufobj);
|
|
/* XXX: some size information */
|
|
#endif
|
|
} else {
|
|
vals[DUK__IDX_HBYTES] = (duk_small_uint_t) sizeof(duk_hobject);
|
|
}
|
|
|
|
vals[DUK__IDX_CLASS] = (duk_int_t) DUK_HOBJECT_GET_CLASS_NUMBER(h_obj);
|
|
vals[DUK__IDX_PBYTES] = (duk_int_t) DUK_HOBJECT_P_ALLOC_SIZE(h_obj);
|
|
vals[DUK__IDX_ESIZE] = (duk_int_t) DUK_HOBJECT_GET_ESIZE(h_obj);
|
|
vals[DUK__IDX_ENEXT] = (duk_int_t) DUK_HOBJECT_GET_ENEXT(h_obj);
|
|
vals[DUK__IDX_ASIZE] = (duk_int_t) DUK_HOBJECT_GET_ASIZE(h_obj);
|
|
vals[DUK__IDX_HSIZE] = (duk_int_t) DUK_HOBJECT_GET_HSIZE(h_obj);
|
|
|
|
/* Note: e_next indicates the number of gc-reachable entries
|
|
* in the entry part, and also indicates the index where the
|
|
* next new property would be inserted. It does *not* indicate
|
|
* the number of non-NULL keys present in the object. That
|
|
* value could be counted separately but requires a pass through
|
|
* the key list.
|
|
*/
|
|
|
|
if (DUK_HOBJECT_IS_COMPFUNC(h_obj)) {
|
|
duk_hbuffer *h_data = (duk_hbuffer *) DUK_HCOMPFUNC_GET_DATA(thr->heap, (duk_hcompfunc *) h_obj);
|
|
vals[DUK__IDX_BCBYTES] = (duk_int_t) (h_data ? DUK_HBUFFER_GET_SIZE(h_data) : 0);
|
|
}
|
|
break;
|
|
}
|
|
case DUK_HTYPE_BUFFER: {
|
|
duk_hbuffer *h_buf = (duk_hbuffer *) h;
|
|
|
|
if (DUK_HBUFFER_HAS_DYNAMIC(h_buf)) {
|
|
if (DUK_HBUFFER_HAS_EXTERNAL(h_buf)) {
|
|
vals[DUK__IDX_VARIANT] = 2; /* buffer variant 2: external */
|
|
vals[DUK__IDX_HBYTES] = (duk_uint_t) (sizeof(duk_hbuffer_external));
|
|
} else {
|
|
/* When alloc_size == 0 the second allocation may not
|
|
* actually exist.
|
|
*/
|
|
vals[DUK__IDX_VARIANT] = 1; /* buffer variant 1: dynamic */
|
|
vals[DUK__IDX_HBYTES] = (duk_uint_t) (sizeof(duk_hbuffer_dynamic));
|
|
}
|
|
vals[DUK__IDX_DBYTES] = (duk_int_t) (DUK_HBUFFER_GET_SIZE(h_buf));
|
|
} else {
|
|
DUK_ASSERT(vals[DUK__IDX_VARIANT] == 0); /* buffer variant 0: fixed */
|
|
vals[DUK__IDX_HBYTES] = (duk_int_t) (sizeof(duk_hbuffer_fixed) + DUK_HBUFFER_GET_SIZE(h_buf));
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
finish:
|
|
duk__inspect_multiple_uint(thr,
|
|
"type" "\x00" "itag" "\x00" "refc" "\x00" "hbytes" "\x00" "class" "\x00"
|
|
"pbytes" "\x00" "esize" "\x00" "enext" "\x00" "asize" "\x00" "hsize" "\x00"
|
|
"bcbytes" "\x00" "dbytes" "\x00" "tstate" "\x00" "variant" "\x00" "\x00",
|
|
(duk_int_t *) &vals);
|
|
}
|
|
|
|
DUK_EXTERNAL void duk_inspect_callstack_entry(duk_hthread *thr, duk_int_t level) {
|
|
duk_activation *act;
|
|
duk_uint_fast32_t pc;
|
|
duk_uint_fast32_t line;
|
|
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
|
|
/* -1 = top callstack entry
|
|
* -2 = caller of level -1
|
|
* etc
|
|
*/
|
|
act = duk_hthread_get_activation_for_level(thr, level);
|
|
if (act == NULL) {
|
|
duk_push_undefined(thr);
|
|
return;
|
|
}
|
|
duk_push_bare_object(thr);
|
|
|
|
/* 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_tval(thr, &act->tv_func);
|
|
|
|
duk_push_uint(thr, (duk_uint_t) pc);
|
|
duk_put_prop_stridx_short(thr, -3, DUK_STRIDX_PC);
|
|
|
|
#if defined(DUK_USE_PC2LINE)
|
|
line = duk_hobject_pc2line_query(thr, -1, pc);
|
|
#else
|
|
line = 0;
|
|
#endif
|
|
duk_push_uint(thr, (duk_uint_t) line);
|
|
duk_put_prop_stridx_short(thr, -3, DUK_STRIDX_LINE_NUMBER);
|
|
|
|
duk_put_prop_stridx_short(thr, -2, DUK_STRIDX_LC_FUNCTION);
|
|
/* 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.
|
|
*/
|
|
}
|