761 lines
26 KiB
C
761 lines
26 KiB
C
/*
|
|
* Bytecode dump/load
|
|
*
|
|
* The bytecode load primitive is more important performance-wise than the
|
|
* dump primitive.
|
|
*
|
|
* Unlike most Duktape API calls, bytecode dump/load is not guaranteed to be
|
|
* memory safe for invalid arguments - caller beware! There's little point
|
|
* in trying to achieve memory safety unless bytecode instructions are also
|
|
* validated which is not easy to do with indirect register references etc.
|
|
*/
|
|
|
|
#include "third_party/duktape/duk_internal.h"
|
|
|
|
#if defined(DUK_USE_BYTECODE_DUMP_SUPPORT)
|
|
|
|
#define DUK__SER_MARKER 0xbf
|
|
#define DUK__SER_STRING 0x00
|
|
#define DUK__SER_NUMBER 0x01
|
|
#define DUK__BYTECODE_INITIAL_ALLOC 256
|
|
#define DUK__NO_FORMALS 0xffffffffUL
|
|
|
|
/*
|
|
* Dump/load helpers, xxx_raw() helpers do no buffer checks
|
|
*/
|
|
|
|
DUK_LOCAL const duk_uint8_t *duk__load_string_raw(duk_hthread *thr, const duk_uint8_t *p) {
|
|
duk_uint32_t len;
|
|
|
|
len = DUK_RAW_READINC_U32_BE(p);
|
|
duk_push_lstring(thr, (const char *) p, len);
|
|
p += len;
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL const duk_uint8_t *duk__load_buffer_raw(duk_hthread *thr, const duk_uint8_t *p) {
|
|
duk_uint32_t len;
|
|
duk_uint8_t *buf;
|
|
|
|
len = DUK_RAW_READINC_U32_BE(p);
|
|
buf = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, (duk_size_t) len);
|
|
DUK_ASSERT(buf != NULL);
|
|
duk_memcpy((void *) buf, (const void *) p, (size_t) len);
|
|
p += len;
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_hstring_raw(duk_uint8_t *p, duk_hstring *h) {
|
|
duk_size_t len;
|
|
duk_uint32_t tmp32;
|
|
|
|
DUK_ASSERT(h != NULL);
|
|
|
|
len = DUK_HSTRING_GET_BYTELEN(h);
|
|
DUK_ASSERT(len <= 0xffffffffUL); /* string limits */
|
|
tmp32 = (duk_uint32_t) len;
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
duk_memcpy((void *) p,
|
|
(const void *) DUK_HSTRING_GET_DATA(h),
|
|
len);
|
|
p += len;
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_hbuffer_raw(duk_hthread *thr, duk_uint8_t *p, duk_hbuffer *h) {
|
|
duk_size_t len;
|
|
duk_uint32_t tmp32;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(h != NULL);
|
|
DUK_UNREF(thr);
|
|
|
|
len = DUK_HBUFFER_GET_SIZE(h);
|
|
DUK_ASSERT(len <= 0xffffffffUL); /* buffer limits */
|
|
tmp32 = (duk_uint32_t) len;
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
/* When len == 0, buffer data pointer may be NULL. */
|
|
duk_memcpy_unsafe((void *) p,
|
|
(const void *) DUK_HBUFFER_GET_DATA_PTR(thr->heap, h),
|
|
len);
|
|
p += len;
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_string_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx) {
|
|
duk_hstring *h_str;
|
|
duk_tval *tv;
|
|
|
|
tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx);
|
|
if (tv != NULL && DUK_TVAL_IS_STRING(tv)) {
|
|
h_str = DUK_TVAL_GET_STRING(tv);
|
|
DUK_ASSERT(h_str != NULL);
|
|
} else {
|
|
h_str = DUK_HTHREAD_STRING_EMPTY_STRING(thr);
|
|
DUK_ASSERT(h_str != NULL);
|
|
}
|
|
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(h_str), p);
|
|
p = duk__dump_hstring_raw(p, h_str);
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_buffer_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx) {
|
|
duk_tval *tv;
|
|
|
|
tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx);
|
|
if (tv != NULL && DUK_TVAL_IS_BUFFER(tv)) {
|
|
duk_hbuffer *h_buf;
|
|
h_buf = DUK_TVAL_GET_BUFFER(tv);
|
|
DUK_ASSERT(h_buf != NULL);
|
|
DUK_ASSERT(DUK_HBUFFER_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HBUFFER_GET_SIZE(h_buf), p);
|
|
p = duk__dump_hbuffer_raw(thr, p, h_buf);
|
|
} else {
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p);
|
|
DUK_RAW_WRITEINC_U32_BE(p, 0);
|
|
}
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_uint32_prop(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func, duk_small_uint_t stridx, duk_uint32_t def_value) {
|
|
duk_tval *tv;
|
|
duk_uint32_t val;
|
|
|
|
tv = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, (duk_hobject *) func, stridx);
|
|
if (tv != NULL && DUK_TVAL_IS_NUMBER(tv)) {
|
|
val = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv);
|
|
} else {
|
|
val = def_value;
|
|
}
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p);
|
|
DUK_RAW_WRITEINC_U32_BE(p, val);
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_varmap(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) {
|
|
duk_hobject *h;
|
|
|
|
h = duk_hobject_get_varmap(thr, (duk_hobject *) func);
|
|
if (h != NULL) {
|
|
duk_uint_fast32_t i;
|
|
|
|
/* We know _Varmap only has own properties so walk property
|
|
* table directly. We also know _Varmap is dense and all
|
|
* values are numbers; assert for these. GC and finalizers
|
|
* shouldn't affect _Varmap so side effects should be fine.
|
|
*/
|
|
for (i = 0; i < (duk_uint_fast32_t) DUK_HOBJECT_GET_ENEXT(h); i++) {
|
|
duk_hstring *key;
|
|
duk_tval *tv_val;
|
|
duk_uint32_t val;
|
|
|
|
key = DUK_HOBJECT_E_GET_KEY(thr->heap, h, i);
|
|
DUK_ASSERT(key != NULL); /* _Varmap is dense */
|
|
DUK_ASSERT(!DUK_HOBJECT_E_SLOT_IS_ACCESSOR(thr->heap, h, i));
|
|
tv_val = DUK_HOBJECT_E_GET_VALUE_TVAL_PTR(thr->heap, h, i);
|
|
DUK_ASSERT(tv_val != NULL);
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv_val)); /* known to be number; in fact an integer */
|
|
#if defined(DUK_USE_FASTINT)
|
|
DUK_ASSERT(DUK_TVAL_IS_FASTINT(tv_val));
|
|
DUK_ASSERT(DUK_TVAL_GET_FASTINT(tv_val) == (duk_int64_t) DUK_TVAL_GET_FASTINT_U32(tv_val)); /* known to be 32-bit */
|
|
val = DUK_TVAL_GET_FASTINT_U32(tv_val);
|
|
#else
|
|
val = (duk_uint32_t) DUK_TVAL_GET_NUMBER(tv_val);
|
|
#endif
|
|
|
|
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(key) + 4U, p);
|
|
p = duk__dump_hstring_raw(p, key);
|
|
DUK_RAW_WRITEINC_U32_BE(p, val);
|
|
}
|
|
}
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p);
|
|
DUK_RAW_WRITEINC_U32_BE(p, 0); /* end of _Varmap */
|
|
return p;
|
|
}
|
|
|
|
DUK_LOCAL duk_uint8_t *duk__dump_formals(duk_hthread *thr, duk_uint8_t *p, duk_bufwriter_ctx *bw_ctx, duk_hobject *func) {
|
|
duk_harray *h;
|
|
|
|
h = duk_hobject_get_formals(thr, (duk_hobject *) func);
|
|
if (h != NULL) {
|
|
duk_uint32_t i;
|
|
|
|
/* Here we rely on _Formals being a dense array containing
|
|
* strings. This should be the case unless _Formals has been
|
|
* tweaked by the application (which we don't support right
|
|
* now).
|
|
*/
|
|
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p);
|
|
DUK_ASSERT(h->length != DUK__NO_FORMALS); /* limits */
|
|
DUK_RAW_WRITEINC_U32_BE(p, h->length);
|
|
|
|
for (i = 0; i < h->length; i++) {
|
|
duk_tval *tv_val;
|
|
duk_hstring *varname;
|
|
|
|
tv_val = DUK_HOBJECT_A_GET_VALUE_PTR(thr->heap, (duk_hobject *) h, i);
|
|
DUK_ASSERT(tv_val != NULL);
|
|
DUK_ASSERT(DUK_TVAL_IS_STRING(tv_val));
|
|
|
|
varname = DUK_TVAL_GET_STRING(tv_val);
|
|
DUK_ASSERT(varname != NULL);
|
|
DUK_ASSERT(DUK_HSTRING_GET_BYTELEN(varname) >= 1);
|
|
|
|
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U + DUK_HSTRING_GET_BYTELEN(varname), p);
|
|
p = duk__dump_hstring_raw(p, varname);
|
|
}
|
|
} else {
|
|
DUK_DD(DUK_DDPRINT("dumping function without _Formals, emit marker to indicate missing _Formals"));
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 4U, p);
|
|
DUK_RAW_WRITEINC_U32_BE(p, DUK__NO_FORMALS); /* marker: no formals */
|
|
}
|
|
return p;
|
|
}
|
|
|
|
static duk_uint8_t *duk__dump_func(duk_hthread *thr, duk_hcompfunc *func, duk_bufwriter_ctx *bw_ctx, duk_uint8_t *p) {
|
|
duk_tval *tv, *tv_end;
|
|
duk_instr_t *ins, *ins_end;
|
|
duk_hobject **fn, **fn_end;
|
|
duk_hstring *h_str;
|
|
duk_uint32_t count_instr;
|
|
duk_uint32_t tmp32;
|
|
duk_uint16_t tmp16;
|
|
duk_double_t d;
|
|
|
|
DUK_DD(DUK_DDPRINT("dumping function %p to %p: "
|
|
"consts=[%p,%p[ (%ld bytes, %ld items), "
|
|
"funcs=[%p,%p[ (%ld bytes, %ld items), "
|
|
"code=[%p,%p[ (%ld bytes, %ld items)",
|
|
(void *) func,
|
|
(void *) p,
|
|
(void *) DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, func),
|
|
(void *) DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_CONSTS_SIZE(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_CONSTS_COUNT(thr->heap, func),
|
|
(void *) DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, func),
|
|
(void *) DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_FUNCS_SIZE(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_FUNCS_COUNT(thr->heap, func),
|
|
(void *) DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, func),
|
|
(void *) DUK_HCOMPFUNC_GET_CODE_END(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_CODE_SIZE(thr->heap, func),
|
|
(long) DUK_HCOMPFUNC_GET_CODE_COUNT(thr->heap, func)));
|
|
|
|
DUK_ASSERT(DUK_USE_ESBC_MAX_BYTES <= 0x7fffffffUL); /* ensures no overflow */
|
|
count_instr = (duk_uint32_t) DUK_HCOMPFUNC_GET_CODE_COUNT(thr->heap, func);
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 3U * 4U + 2U * 2U + 3U * 4U + count_instr * 4U, p);
|
|
|
|
/* Fixed header info. */
|
|
tmp32 = count_instr;
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
tmp32 = (duk_uint32_t) DUK_HCOMPFUNC_GET_CONSTS_COUNT(thr->heap, func);
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
tmp32 = (duk_uint32_t) DUK_HCOMPFUNC_GET_FUNCS_COUNT(thr->heap, func);
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
tmp16 = func->nregs;
|
|
DUK_RAW_WRITEINC_U16_BE(p, tmp16);
|
|
tmp16 = func->nargs;
|
|
DUK_RAW_WRITEINC_U16_BE(p, tmp16);
|
|
#if defined(DUK_USE_DEBUGGER_SUPPORT)
|
|
tmp32 = func->start_line;
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
tmp32 = func->end_line;
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
#else
|
|
DUK_RAW_WRITEINC_U32_BE(p, 0);
|
|
DUK_RAW_WRITEINC_U32_BE(p, 0);
|
|
#endif
|
|
tmp32 = DUK_HEAPHDR_GET_FLAGS((duk_heaphdr *) func); /* masks flags, only duk_hobject flags */
|
|
tmp32 &= ~(DUK_HOBJECT_FLAG_HAVE_FINALIZER); /* finalizer flag is lost */
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
|
|
/* Bytecode instructions: endian conversion needed unless
|
|
* platform is big endian.
|
|
*/
|
|
ins = DUK_HCOMPFUNC_GET_CODE_BASE(thr->heap, func);
|
|
ins_end = DUK_HCOMPFUNC_GET_CODE_END(thr->heap, func);
|
|
DUK_ASSERT((duk_size_t) (ins_end - ins) == (duk_size_t) count_instr);
|
|
#if defined(DUK_USE_INTEGER_BE)
|
|
duk_memcpy_unsafe((void *) p, (const void *) ins, (size_t) (ins_end - ins));
|
|
p += (size_t) (ins_end - ins);
|
|
#else
|
|
while (ins != ins_end) {
|
|
tmp32 = (duk_uint32_t) (*ins);
|
|
DUK_RAW_WRITEINC_U32_BE(p, tmp32);
|
|
ins++;
|
|
}
|
|
#endif
|
|
|
|
/* Constants: variable size encoding. */
|
|
tv = DUK_HCOMPFUNC_GET_CONSTS_BASE(thr->heap, func);
|
|
tv_end = DUK_HCOMPFUNC_GET_CONSTS_END(thr->heap, func);
|
|
while (tv != tv_end) {
|
|
/* constants are strings or numbers now */
|
|
DUK_ASSERT(DUK_TVAL_IS_STRING(tv) ||
|
|
DUK_TVAL_IS_NUMBER(tv));
|
|
|
|
if (DUK_TVAL_IS_STRING(tv)) {
|
|
h_str = DUK_TVAL_GET_STRING(tv);
|
|
DUK_ASSERT(h_str != NULL);
|
|
DUK_ASSERT(DUK_HSTRING_MAX_BYTELEN <= 0x7fffffffUL); /* ensures no overflow */
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 1U + 4U + DUK_HSTRING_GET_BYTELEN(h_str), p);
|
|
*p++ = DUK__SER_STRING;
|
|
p = duk__dump_hstring_raw(p, h_str);
|
|
} else {
|
|
DUK_ASSERT(DUK_TVAL_IS_NUMBER(tv));
|
|
p = DUK_BW_ENSURE_RAW(thr, bw_ctx, 1U + 8U, p);
|
|
*p++ = DUK__SER_NUMBER;
|
|
d = DUK_TVAL_GET_NUMBER(tv);
|
|
DUK_RAW_WRITEINC_DOUBLE_BE(p, d);
|
|
}
|
|
tv++;
|
|
}
|
|
|
|
/* Inner functions recursively. */
|
|
fn = (duk_hobject **) DUK_HCOMPFUNC_GET_FUNCS_BASE(thr->heap, func);
|
|
fn_end = (duk_hobject **) DUK_HCOMPFUNC_GET_FUNCS_END(thr->heap, func);
|
|
while (fn != fn_end) {
|
|
/* XXX: This causes recursion up to inner function depth
|
|
* which is normally not an issue, e.g. mark-and-sweep uses
|
|
* a recursion limiter to avoid C stack issues. Avoiding
|
|
* this would mean some sort of a work list or just refusing
|
|
* to serialize deep functions.
|
|
*/
|
|
DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC(*fn));
|
|
p = duk__dump_func(thr, (duk_hcompfunc *) *fn, bw_ctx, p);
|
|
fn++;
|
|
}
|
|
|
|
/* Lexenv and varenv are not dumped. */
|
|
|
|
/* Object extra properties.
|
|
*
|
|
* There are some difference between function templates and functions.
|
|
* For example, function templates don't have .length and nargs is
|
|
* normally used to instantiate the functions.
|
|
*/
|
|
|
|
p = duk__dump_uint32_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_LENGTH, (duk_uint32_t) func->nargs);
|
|
#if defined(DUK_USE_FUNC_NAME_PROPERTY)
|
|
p = duk__dump_string_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_NAME);
|
|
#endif
|
|
#if defined(DUK_USE_FUNC_FILENAME_PROPERTY)
|
|
p = duk__dump_string_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_FILE_NAME);
|
|
#endif
|
|
#if defined(DUK_USE_PC2LINE)
|
|
p = duk__dump_buffer_prop(thr, p, bw_ctx, (duk_hobject *) func, DUK_STRIDX_INT_PC2LINE);
|
|
#endif
|
|
p = duk__dump_varmap(thr, p, bw_ctx, (duk_hobject *) func);
|
|
p = duk__dump_formals(thr, p, bw_ctx, (duk_hobject *) func);
|
|
|
|
DUK_DD(DUK_DDPRINT("serialized function %p -> final pointer %p", (void *) func, (void *) p));
|
|
|
|
return p;
|
|
}
|
|
|
|
/* Load a function from bytecode. The function object returned here must
|
|
* match what is created by duk_js_push_closure() with respect to its flags,
|
|
* properties, etc.
|
|
*
|
|
* NOTE: there are intentionally no input buffer length / bound checks.
|
|
* Adding them would be easy but wouldn't ensure memory safety as untrusted
|
|
* or broken bytecode is unsafe during execution unless the opcodes themselves
|
|
* are validated (which is quite complex, especially for indirect opcodes).
|
|
*/
|
|
|
|
#define DUK__ASSERT_LEFT(n) do { \
|
|
DUK_ASSERT((duk_size_t) (p_end - p) >= (duk_size_t) (n)); \
|
|
} while (0)
|
|
|
|
static const duk_uint8_t *duk__load_func(duk_hthread *thr, const duk_uint8_t *p, const duk_uint8_t *p_end) {
|
|
duk_hcompfunc *h_fun;
|
|
duk_hbuffer *h_data;
|
|
duk_size_t data_size;
|
|
duk_uint32_t count_instr, count_const, count_funcs;
|
|
duk_uint32_t n;
|
|
duk_uint32_t tmp32;
|
|
duk_small_uint_t const_type;
|
|
duk_uint8_t *fun_data;
|
|
duk_uint8_t *q;
|
|
duk_idx_t idx_base;
|
|
duk_tval *tv1;
|
|
duk_uarridx_t arr_idx;
|
|
duk_uarridx_t arr_limit;
|
|
duk_hobject *func_env;
|
|
duk_bool_t need_pop;
|
|
|
|
/* XXX: There's some overlap with duk_js_closure() here, but
|
|
* seems difficult to share code. Ensure that the final function
|
|
* looks the same as created by duk_js_closure().
|
|
*/
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
DUK_DD(DUK_DDPRINT("loading function, p=%p, p_end=%p", (void *) p, (void *) p_end));
|
|
|
|
DUK__ASSERT_LEFT(3 * 4);
|
|
count_instr = DUK_RAW_READINC_U32_BE(p);
|
|
count_const = DUK_RAW_READINC_U32_BE(p);
|
|
count_funcs = DUK_RAW_READINC_U32_BE(p);
|
|
|
|
data_size = sizeof(duk_tval) * count_const +
|
|
sizeof(duk_hobject *) * count_funcs +
|
|
sizeof(duk_instr_t) * count_instr;
|
|
|
|
DUK_DD(DUK_DDPRINT("instr=%ld, const=%ld, funcs=%ld, data_size=%ld",
|
|
(long) count_instr, (long) count_const,
|
|
(long) count_const, (long) data_size));
|
|
|
|
/* Value stack is used to ensure reachability of constants and
|
|
* inner functions being loaded. Require enough space to handle
|
|
* large functions correctly.
|
|
*/
|
|
duk_require_stack(thr, (duk_idx_t) (2 + count_const + count_funcs));
|
|
idx_base = duk_get_top(thr);
|
|
|
|
/* Push function object, init flags etc. This must match
|
|
* duk_js_push_closure() quite carefully.
|
|
*/
|
|
h_fun = duk_push_hcompfunc(thr);
|
|
DUK_ASSERT(h_fun != NULL);
|
|
DUK_ASSERT(DUK_HOBJECT_IS_COMPFUNC((duk_hobject *) h_fun));
|
|
DUK_ASSERT(DUK_HCOMPFUNC_GET_DATA(thr->heap, h_fun) == NULL);
|
|
DUK_ASSERT(DUK_HCOMPFUNC_GET_FUNCS(thr->heap, h_fun) == NULL);
|
|
DUK_ASSERT(DUK_HCOMPFUNC_GET_BYTECODE(thr->heap, h_fun) == NULL);
|
|
DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_fun) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
|
|
|
|
h_fun->nregs = DUK_RAW_READINC_U16_BE(p);
|
|
h_fun->nargs = DUK_RAW_READINC_U16_BE(p);
|
|
#if defined(DUK_USE_DEBUGGER_SUPPORT)
|
|
h_fun->start_line = DUK_RAW_READINC_U32_BE(p);
|
|
h_fun->end_line = DUK_RAW_READINC_U32_BE(p);
|
|
#else
|
|
p += 8; /* skip line info */
|
|
#endif
|
|
|
|
/* duk_hcompfunc flags; quite version specific */
|
|
tmp32 = DUK_RAW_READINC_U32_BE(p);
|
|
DUK_HEAPHDR_SET_FLAGS((duk_heaphdr *) h_fun, tmp32); /* masks flags to only change duk_hobject flags */
|
|
|
|
/* standard prototype (no need to set here, already set) */
|
|
DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) h_fun) == thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
|
|
#if 0
|
|
DUK_HOBJECT_SET_PROTOTYPE_UPDREF(thr, &h_fun->obj, thr->builtins[DUK_BIDX_FUNCTION_PROTOTYPE]);
|
|
#endif
|
|
|
|
/* assert just a few critical flags */
|
|
DUK_ASSERT(DUK_HEAPHDR_GET_TYPE((duk_heaphdr *) h_fun) == DUK_HTYPE_OBJECT);
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&h_fun->obj));
|
|
DUK_ASSERT(DUK_HOBJECT_HAS_COMPFUNC(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_NATFUNC(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_IS_THREAD(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_IS_PROXY(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARRAY(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_STRINGOBJ(&h_fun->obj));
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_EXOTIC_ARGUMENTS(&h_fun->obj));
|
|
|
|
/* Create function 'data' buffer but don't attach it yet. */
|
|
fun_data = (duk_uint8_t *) duk_push_fixed_buffer_nozero(thr, data_size);
|
|
DUK_ASSERT(fun_data != NULL);
|
|
|
|
/* Load bytecode instructions. */
|
|
DUK_ASSERT(sizeof(duk_instr_t) == 4);
|
|
DUK__ASSERT_LEFT(count_instr * sizeof(duk_instr_t));
|
|
#if defined(DUK_USE_INTEGER_BE)
|
|
q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs;
|
|
duk_memcpy((void *) q,
|
|
(const void *) p,
|
|
sizeof(duk_instr_t) * count_instr);
|
|
p += sizeof(duk_instr_t) * count_instr;
|
|
#else
|
|
q = fun_data + sizeof(duk_tval) * count_const + sizeof(duk_hobject *) * count_funcs;
|
|
for (n = count_instr; n > 0; n--) {
|
|
*((duk_instr_t *) (void *) q) = DUK_RAW_READINC_U32_BE(p);
|
|
q += sizeof(duk_instr_t);
|
|
}
|
|
#endif
|
|
|
|
/* Load constants onto value stack but don't yet copy to buffer. */
|
|
for (n = count_const; n > 0; n--) {
|
|
DUK__ASSERT_LEFT(1);
|
|
const_type = DUK_RAW_READINC_U8(p);
|
|
switch (const_type) {
|
|
case DUK__SER_STRING: {
|
|
p = duk__load_string_raw(thr, p);
|
|
break;
|
|
}
|
|
case DUK__SER_NUMBER: {
|
|
/* Important to do a fastint check so that constants are
|
|
* properly read back as fastints.
|
|
*/
|
|
duk_tval tv_tmp;
|
|
duk_double_t val;
|
|
DUK__ASSERT_LEFT(8);
|
|
val = DUK_RAW_READINC_DOUBLE_BE(p);
|
|
DUK_TVAL_SET_NUMBER_CHKFAST_SLOW(&tv_tmp, val);
|
|
duk_push_tval(thr, &tv_tmp);
|
|
break;
|
|
}
|
|
default: {
|
|
goto format_error;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Load inner functions to value stack, but don't yet copy to buffer. */
|
|
for (n = count_funcs; n > 0; n--) {
|
|
p = duk__load_func(thr, p, p_end);
|
|
if (p == NULL) {
|
|
goto format_error;
|
|
}
|
|
}
|
|
|
|
/* With constants and inner functions on value stack, we can now
|
|
* atomically finish the function 'data' buffer, bump refcounts,
|
|
* etc.
|
|
*
|
|
* Here we take advantage of the value stack being just a duk_tval
|
|
* array: we can just memcpy() the constants as long as we incref
|
|
* them afterwards.
|
|
*/
|
|
|
|
h_data = (duk_hbuffer *) duk_known_hbuffer(thr, idx_base + 1);
|
|
DUK_ASSERT(!DUK_HBUFFER_HAS_DYNAMIC(h_data));
|
|
DUK_HCOMPFUNC_SET_DATA(thr->heap, h_fun, h_data);
|
|
DUK_HBUFFER_INCREF(thr, h_data);
|
|
|
|
tv1 = duk_get_tval(thr, idx_base + 2); /* may be NULL if no constants or inner funcs */
|
|
DUK_ASSERT((count_const == 0 && count_funcs == 0) || tv1 != NULL);
|
|
|
|
q = fun_data;
|
|
duk_memcpy_unsafe((void *) q, (const void *) tv1, sizeof(duk_tval) * count_const);
|
|
for (n = count_const; n > 0; n--) {
|
|
DUK_TVAL_INCREF_FAST(thr, (duk_tval *) (void *) q); /* no side effects */
|
|
q += sizeof(duk_tval);
|
|
}
|
|
tv1 += count_const;
|
|
|
|
DUK_HCOMPFUNC_SET_FUNCS(thr->heap, h_fun, (duk_hobject **) (void *) q);
|
|
for (n = count_funcs; n > 0; n--) {
|
|
duk_hobject *h_obj;
|
|
|
|
DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv1));
|
|
h_obj = DUK_TVAL_GET_OBJECT(tv1);
|
|
DUK_ASSERT(h_obj != NULL);
|
|
tv1++;
|
|
DUK_HOBJECT_INCREF(thr, h_obj);
|
|
|
|
*((duk_hobject **) (void *) q) = h_obj;
|
|
q += sizeof(duk_hobject *);
|
|
}
|
|
|
|
DUK_HCOMPFUNC_SET_BYTECODE(thr->heap, h_fun, (duk_instr_t *) (void *) q);
|
|
|
|
/* The function object is now reachable and refcounts are fine,
|
|
* so we can pop off all the temporaries.
|
|
*/
|
|
DUK_DDD(DUK_DDDPRINT("function is reachable, reset top; func: %!iT", duk_get_tval(thr, idx_base)));
|
|
duk_set_top(thr, idx_base + 1);
|
|
|
|
/* Setup function properties. */
|
|
tmp32 = DUK_RAW_READINC_U32_BE(p);
|
|
duk_push_u32(thr, tmp32);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_C);
|
|
|
|
#if defined(DUK_USE_FUNC_NAME_PROPERTY)
|
|
p = duk__load_string_raw(thr, p); /* -> [ func funcname ] */
|
|
func_env = thr->builtins[DUK_BIDX_GLOBAL_ENV];
|
|
DUK_ASSERT(func_env != NULL);
|
|
need_pop = 0;
|
|
if (DUK_HOBJECT_HAS_NAMEBINDING((duk_hobject *) h_fun)) {
|
|
/* Original function instance/template had NAMEBINDING.
|
|
* Must create a lexical environment on loading to allow
|
|
* recursive functions like 'function foo() { foo(); }'.
|
|
*/
|
|
duk_hdecenv *new_env;
|
|
|
|
new_env = duk_hdecenv_alloc(thr,
|
|
DUK_HOBJECT_FLAG_EXTENSIBLE |
|
|
DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_DECENV));
|
|
DUK_ASSERT(new_env != NULL);
|
|
DUK_ASSERT(new_env->thread == NULL); /* Closed. */
|
|
DUK_ASSERT(new_env->varmap == NULL);
|
|
DUK_ASSERT(new_env->regbase_byteoff == 0);
|
|
DUK_HDECENV_ASSERT_VALID(new_env);
|
|
DUK_ASSERT(DUK_HOBJECT_GET_PROTOTYPE(thr->heap, (duk_hobject *) new_env) == NULL);
|
|
DUK_HOBJECT_SET_PROTOTYPE(thr->heap, (duk_hobject *) new_env, func_env);
|
|
DUK_HOBJECT_INCREF(thr, func_env);
|
|
|
|
func_env = (duk_hobject *) new_env;
|
|
|
|
duk_push_hobject(thr, (duk_hobject *) new_env);
|
|
|
|
duk_dup_m2(thr); /* -> [ func funcname env funcname ] */
|
|
duk_dup(thr, idx_base); /* -> [ func funcname env funcname func ] */
|
|
duk_xdef_prop(thr, -3, DUK_PROPDESC_FLAGS_NONE); /* -> [ func funcname env ] */
|
|
|
|
need_pop = 1; /* Need to pop env, but -after- updating h_fun and increfs. */
|
|
}
|
|
DUK_ASSERT(func_env != NULL);
|
|
DUK_HCOMPFUNC_SET_LEXENV(thr->heap, h_fun, func_env);
|
|
DUK_HCOMPFUNC_SET_VARENV(thr->heap, h_fun, func_env);
|
|
DUK_HOBJECT_INCREF(thr, func_env);
|
|
DUK_HOBJECT_INCREF(thr, func_env);
|
|
if (need_pop) {
|
|
duk_pop(thr);
|
|
}
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_NAME, DUK_PROPDESC_FLAGS_C);
|
|
#endif /* DUK_USE_FUNC_NAME_PROPERTY */
|
|
|
|
#if defined(DUK_USE_FUNC_FILENAME_PROPERTY)
|
|
p = duk__load_string_raw(thr, p);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_FILE_NAME, DUK_PROPDESC_FLAGS_C);
|
|
#endif /* DUK_USE_FUNC_FILENAME_PROPERTY */
|
|
|
|
if (DUK_HOBJECT_HAS_CONSTRUCTABLE((duk_hobject *) h_fun)) {
|
|
/* Restore empty external .prototype only for constructable
|
|
* functions. The prototype object should inherit from
|
|
* Object.prototype.
|
|
*/
|
|
duk_push_object(thr);
|
|
DUK_ASSERT(!duk_is_bare_object(thr, -1));
|
|
duk_dup_m2(thr);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_CONSTRUCTOR, DUK_PROPDESC_FLAGS_WC); /* func.prototype.constructor = func */
|
|
duk_compact_m1(thr);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_PROTOTYPE, DUK_PROPDESC_FLAGS_W);
|
|
}
|
|
|
|
#if defined(DUK_USE_PC2LINE)
|
|
p = duk__load_buffer_raw(thr, p);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_PC2LINE, DUK_PROPDESC_FLAGS_WC);
|
|
#endif /* DUK_USE_PC2LINE */
|
|
|
|
duk_push_bare_object(thr); /* _Varmap */
|
|
for (;;) {
|
|
/* XXX: awkward */
|
|
p = duk__load_string_raw(thr, p);
|
|
if (duk_get_length(thr, -1) == 0) {
|
|
duk_pop(thr);
|
|
break;
|
|
}
|
|
tmp32 = DUK_RAW_READINC_U32_BE(p);
|
|
duk_push_u32(thr, tmp32);
|
|
duk_put_prop(thr, -3);
|
|
}
|
|
duk_compact_m1(thr);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_VARMAP, DUK_PROPDESC_FLAGS_NONE);
|
|
|
|
/* _Formals may have been missing in the original function, which is
|
|
* handled using a marker length.
|
|
*/
|
|
arr_limit = DUK_RAW_READINC_U32_BE(p);
|
|
if (arr_limit != DUK__NO_FORMALS) {
|
|
duk_push_bare_array(thr); /* _Formals */
|
|
for (arr_idx = 0; arr_idx < arr_limit; arr_idx++) {
|
|
p = duk__load_string_raw(thr, p);
|
|
duk_put_prop_index(thr, -2, arr_idx);
|
|
}
|
|
duk_compact_m1(thr);
|
|
duk_xdef_prop_stridx_short(thr, -2, DUK_STRIDX_INT_FORMALS, DUK_PROPDESC_FLAGS_NONE);
|
|
} else {
|
|
DUK_DD(DUK_DDPRINT("no _Formals in dumped function"));
|
|
}
|
|
|
|
/* Return with final function pushed on stack top. */
|
|
DUK_DD(DUK_DDPRINT("final loaded function: %!iT", duk_get_tval(thr, -1)));
|
|
DUK_ASSERT_TOP(thr, idx_base + 1);
|
|
return p;
|
|
|
|
format_error:
|
|
return NULL;
|
|
}
|
|
|
|
DUK_EXTERNAL void duk_dump_function(duk_hthread *thr) {
|
|
duk_hcompfunc *func;
|
|
duk_bufwriter_ctx bw_ctx_alloc;
|
|
duk_bufwriter_ctx *bw_ctx = &bw_ctx_alloc;
|
|
duk_uint8_t *p;
|
|
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
|
|
/* Bound functions don't have all properties so we'd either need to
|
|
* lookup the non-bound target function or reject bound functions.
|
|
* For now, bound functions are rejected with TypeError.
|
|
*/
|
|
func = duk_require_hcompfunc(thr, -1);
|
|
DUK_ASSERT(func != NULL);
|
|
DUK_ASSERT(!DUK_HOBJECT_HAS_BOUNDFUNC(&func->obj));
|
|
|
|
/* Estimating the result size beforehand would be costly, so
|
|
* start with a reasonable size and extend as needed.
|
|
*/
|
|
DUK_BW_INIT_PUSHBUF(thr, bw_ctx, DUK__BYTECODE_INITIAL_ALLOC);
|
|
p = DUK_BW_GET_PTR(thr, bw_ctx);
|
|
*p++ = DUK__SER_MARKER;
|
|
p = duk__dump_func(thr, func, bw_ctx, p);
|
|
DUK_BW_SET_PTR(thr, bw_ctx, p);
|
|
DUK_BW_COMPACT(thr, bw_ctx);
|
|
|
|
DUK_DD(DUK_DDPRINT("serialized result: %!T", duk_get_tval(thr, -1)));
|
|
|
|
duk_remove_m2(thr); /* [ ... func buf ] -> [ ... buf ] */
|
|
}
|
|
|
|
DUK_EXTERNAL void duk_load_function(duk_hthread *thr) {
|
|
const duk_uint8_t *p_buf, *p, *p_end;
|
|
duk_size_t sz;
|
|
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
|
|
p_buf = (duk_uint8_t *) duk_require_buffer(thr, -1, &sz);
|
|
DUK_ASSERT(p_buf != NULL);
|
|
|
|
/* The caller is responsible for being sure that bytecode being loaded
|
|
* is valid and trusted. Invalid bytecode can cause memory unsafe
|
|
* behavior directly during loading or later during bytecode execution
|
|
* (instruction validation would be quite complex to implement).
|
|
*
|
|
* This signature check is the only sanity check for detecting
|
|
* accidental invalid inputs. The initial byte ensures no ordinary
|
|
* string or Symbol will be accepted by accident.
|
|
*/
|
|
p = p_buf;
|
|
p_end = p_buf + sz;
|
|
if (sz < 1 || p[0] != DUK__SER_MARKER) {
|
|
goto format_error;
|
|
}
|
|
p++;
|
|
|
|
p = duk__load_func(thr, p, p_end);
|
|
if (p == NULL) {
|
|
goto format_error;
|
|
}
|
|
|
|
duk_remove_m2(thr); /* [ ... buf func ] -> [ ... func ] */
|
|
return;
|
|
|
|
format_error:
|
|
DUK_ERROR_TYPE(thr, DUK_STR_INVALID_BYTECODE);
|
|
DUK_WO_NORETURN(return;);
|
|
}
|
|
|
|
#else /* DUK_USE_BYTECODE_DUMP_SUPPORT */
|
|
|
|
DUK_EXTERNAL void duk_dump_function(duk_hthread *thr) {
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
DUK_ERROR_UNSUPPORTED(thr);
|
|
DUK_WO_NORETURN(return;);
|
|
}
|
|
|
|
DUK_EXTERNAL void duk_load_function(duk_hthread *thr) {
|
|
DUK_ASSERT_API_ENTRY(thr);
|
|
DUK_ERROR_UNSUPPORTED(thr);
|
|
DUK_WO_NORETURN(return;);
|
|
}
|
|
|
|
#endif /* DUK_USE_BYTECODE_DUMP_SUPPORT */
|