cosmopolitan/third_party/duktape/duk_api_bytecode.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 */