| /* |
| * Call handling. |
| * |
| * Main functions are: |
| * |
| * - duk_handle_call_unprotected(): unprotected call to Ecmascript or |
| * Duktape/C function |
| * - duk_handle_call_protected(): protected call to Ecmascript or |
| * Duktape/C function |
| * - duk_handle_safe_call(): make a protected C call within current |
| * activation |
| * - duk_handle_ecma_call_setup(): Ecmascript-to-Ecmascript calls |
| * (not always possible), including tail calls and coroutine resume |
| * |
| * See 'execution.rst'. |
| * |
| * Note: setjmp() and local variables have a nasty interaction, |
| * see execution.rst; non-volatile locals modified after setjmp() |
| * call are not guaranteed to keep their value. |
| */ |
| |
| #include "duk_internal.h" |
| |
| /* |
| * Forward declarations. |
| */ |
| |
| DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_small_uint_t call_flags, |
| duk_idx_t idx_func); |
| DUK_LOCAL void duk__handle_call_error(duk_hthread *thr, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_valstack_end, |
| duk_size_t entry_catchstack_top, |
| duk_size_t entry_callstack_top, |
| duk_int_t entry_call_recursion_depth, |
| duk_hthread *entry_curr_thread, |
| duk_uint_fast8_t entry_thread_state, |
| duk_instr_t **entry_ptr_curr_pc, |
| duk_idx_t idx_func, |
| duk_jmpbuf *old_jmpbuf_ptr); |
| DUK_LOCAL void duk__handle_safe_call_inner(duk_hthread *thr, |
| duk_safe_call_function func, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_callstack_top, |
| duk_size_t entry_catchstack_top); |
| DUK_LOCAL void duk__handle_safe_call_error(duk_hthread *thr, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_callstack_top, |
| duk_size_t entry_catchstack_top, |
| duk_jmpbuf *old_jmpbuf_ptr); |
| DUK_LOCAL void duk__handle_safe_call_shared(duk_hthread *thr, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_int_t entry_call_recursion_depth, |
| duk_hthread *entry_curr_thread, |
| duk_uint_fast8_t entry_thread_state, |
| duk_instr_t **entry_ptr_curr_pc); |
| |
| /* |
| * Interrupt counter fixup (for development only). |
| */ |
| |
| #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG) |
| DUK_LOCAL void duk__interrupt_fixup(duk_hthread *thr, duk_hthread *entry_curr_thread) { |
| /* Currently the bytecode executor and executor interrupt |
| * instruction counts are off because we don't execute the |
| * interrupt handler when we're about to exit from the initial |
| * user call into Duktape. |
| * |
| * If we were to execute the interrupt handler here, the counts |
| * would match. You can enable this block manually to check |
| * that this is the case. |
| */ |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(thr->heap != NULL); |
| |
| #if defined(DUK_USE_INTERRUPT_DEBUG_FIXUP) |
| if (entry_curr_thread == NULL) { |
| thr->interrupt_init = thr->interrupt_init - thr->interrupt_counter; |
| thr->heap->inst_count_interrupt += thr->interrupt_init; |
| DUK_DD(DUK_DDPRINT("debug test: updated interrupt count on exit to " |
| "user code, instruction counts: executor=%ld, interrupt=%ld", |
| (long) thr->heap->inst_count_exec, (long) thr->heap->inst_count_interrupt)); |
| DUK_ASSERT(thr->heap->inst_count_exec == thr->heap->inst_count_interrupt); |
| } |
| #else |
| DUK_UNREF(thr); |
| DUK_UNREF(entry_curr_thread); |
| #endif |
| } |
| #endif |
| |
| /* |
| * Arguments object creation. |
| * |
| * Creating arguments objects involves many small details, see E5 Section |
| * 10.6 for the specific requirements. Much of the arguments object exotic |
| * behavior is implemented in duk_hobject_props.c, and is enabled by the |
| * object flag DUK_HOBJECT_FLAG_EXOTIC_ARGUMENTS. |
| */ |
| |
| DUK_LOCAL void duk__create_arguments_object(duk_hthread *thr, |
| duk_hobject *func, |
| duk_hobject *varenv, |
| duk_idx_t idx_argbase, /* idx of first argument on stack */ |
| duk_idx_t num_stack_args) { /* num args starting from idx_argbase */ |
| duk_context *ctx = (duk_context *) thr; |
| duk_hobject *arg; /* 'arguments' */ |
| duk_hobject *formals; /* formals for 'func' (may be NULL if func is a C function) */ |
| duk_idx_t i_arg; |
| duk_idx_t i_map; |
| duk_idx_t i_mappednames; |
| duk_idx_t i_formals; |
| duk_idx_t i_argbase; |
| duk_idx_t n_formals; |
| duk_idx_t idx; |
| duk_bool_t need_map; |
| |
| DUK_DDD(DUK_DDDPRINT("creating arguments object for func=%!iO, varenv=%!iO, " |
| "idx_argbase=%ld, num_stack_args=%ld", |
| (duk_heaphdr *) func, (duk_heaphdr *) varenv, |
| (long) idx_argbase, (long) num_stack_args)); |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(DUK_HOBJECT_IS_NONBOUND_FUNCTION(func)); |
| DUK_ASSERT(varenv != NULL); |
| DUK_ASSERT(idx_argbase >= 0); /* assumed to bottom relative */ |
| DUK_ASSERT(num_stack_args >= 0); |
| |
| need_map = 0; |
| |
| i_argbase = idx_argbase; |
| DUK_ASSERT(i_argbase >= 0); |
| |
| duk_push_hobject(ctx, func); |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_INT_FORMALS); |
| formals = duk_get_hobject(ctx, -1); |
| n_formals = 0; |
| if (formals) { |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH); |
| n_formals = (duk_idx_t) duk_require_int(ctx, -1); |
| duk_pop(ctx); |
| } |
| duk_remove(ctx, -2); /* leave formals on stack for later use */ |
| i_formals = duk_require_top_index(ctx); |
| |
| DUK_ASSERT(n_formals >= 0); |
| DUK_ASSERT(formals != NULL || n_formals == 0); |
| |
| DUK_DDD(DUK_DDDPRINT("func=%!O, formals=%!O, n_formals=%ld", |
| (duk_heaphdr *) func, (duk_heaphdr *) formals, |
| (long) n_formals)); |
| |
| /* [ ... formals ] */ |
| |
| /* |
| * Create required objects: |
| * - 'arguments' object: array-like, but not an array |
| * - 'map' object: internal object, tied to 'arguments' |
| * - 'mappedNames' object: temporary value used during construction |
| */ |
| |
| i_arg = duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_FLAG_ARRAY_PART | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_ARGUMENTS), |
| DUK_BIDX_OBJECT_PROTOTYPE); |
| DUK_ASSERT(i_arg >= 0); |
| arg = duk_require_hobject(ctx, -1); |
| DUK_ASSERT(arg != NULL); |
| |
| i_map = duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), |
| -1); /* no prototype */ |
| DUK_ASSERT(i_map >= 0); |
| |
| i_mappednames = duk_push_object_helper(ctx, |
| DUK_HOBJECT_FLAG_EXTENSIBLE | |
| DUK_HOBJECT_CLASS_AS_FLAGS(DUK_HOBJECT_CLASS_OBJECT), |
| -1); /* no prototype */ |
| DUK_ASSERT(i_mappednames >= 0); |
| |
| /* [ ... formals arguments map mappedNames ] */ |
| |
| DUK_DDD(DUK_DDDPRINT("created arguments related objects: " |
| "arguments at index %ld -> %!O " |
| "map at index %ld -> %!O " |
| "mappednames at index %ld -> %!O", |
| (long) i_arg, (duk_heaphdr *) duk_get_hobject(ctx, i_arg), |
| (long) i_map, (duk_heaphdr *) duk_get_hobject(ctx, i_map), |
| (long) i_mappednames, (duk_heaphdr *) duk_get_hobject(ctx, i_mappednames))); |
| |
| /* |
| * Init arguments properties, map, etc. |
| */ |
| |
| duk_push_int(ctx, num_stack_args); |
| duk_xdef_prop_stridx(ctx, i_arg, DUK_STRIDX_LENGTH, DUK_PROPDESC_FLAGS_WC); |
| |
| /* |
| * Init argument related properties |
| */ |
| |
| /* step 11 */ |
| idx = num_stack_args - 1; |
| while (idx >= 0) { |
| DUK_DDD(DUK_DDDPRINT("arg idx %ld, argbase=%ld, argidx=%ld", |
| (long) idx, (long) i_argbase, (long) (i_argbase + idx))); |
| |
| DUK_DDD(DUK_DDDPRINT("define arguments[%ld]=arg", (long) idx)); |
| duk_dup(ctx, i_argbase + idx); |
| duk_xdef_prop_index_wec(ctx, i_arg, (duk_uarridx_t) idx); |
| DUK_DDD(DUK_DDDPRINT("defined arguments[%ld]=arg", (long) idx)); |
| |
| /* step 11.c is relevant only if non-strict (checked in 11.c.ii) */ |
| if (!DUK_HOBJECT_HAS_STRICT(func) && idx < n_formals) { |
| DUK_ASSERT(formals != NULL); |
| |
| DUK_DDD(DUK_DDDPRINT("strict function, index within formals (%ld < %ld)", |
| (long) idx, (long) n_formals)); |
| |
| duk_get_prop_index(ctx, i_formals, idx); |
| DUK_ASSERT(duk_is_string(ctx, -1)); |
| |
| duk_dup(ctx, -1); /* [ ... name name ] */ |
| |
| if (!duk_has_prop(ctx, i_mappednames)) { |
| /* steps 11.c.ii.1 - 11.c.ii.4, but our internal book-keeping |
| * differs from the reference model |
| */ |
| |
| /* [ ... name ] */ |
| |
| need_map = 1; |
| |
| DUK_DDD(DUK_DDDPRINT("set mappednames[%s]=%ld", |
| (const char *) duk_get_string(ctx, -1), |
| (long) idx)); |
| duk_dup(ctx, -1); /* name */ |
| duk_push_uint(ctx, (duk_uint_t) idx); /* index */ |
| duk_to_string(ctx, -1); |
| duk_xdef_prop_wec(ctx, i_mappednames); /* out of spec, must be configurable */ |
| |
| DUK_DDD(DUK_DDDPRINT("set map[%ld]=%s", |
| (long) idx, |
| duk_get_string(ctx, -1))); |
| duk_dup(ctx, -1); /* name */ |
| duk_xdef_prop_index_wec(ctx, i_map, (duk_uarridx_t) idx); /* out of spec, must be configurable */ |
| } else { |
| /* duk_has_prop() popped the second 'name' */ |
| } |
| |
| /* [ ... name ] */ |
| duk_pop(ctx); /* pop 'name' */ |
| } |
| |
| idx--; |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("actual arguments processed")); |
| |
| /* step 12 */ |
| if (need_map) { |
| DUK_DDD(DUK_DDDPRINT("adding 'map' and 'varenv' to arguments object")); |
| |
| /* should never happen for a strict callee */ |
| DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func)); |
| |
| duk_dup(ctx, i_map); |
| duk_xdef_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_MAP, DUK_PROPDESC_FLAGS_NONE); /* out of spec, don't care */ |
| |
| /* The variable environment for magic variable bindings needs to be |
| * given by the caller and recorded in the arguments object. |
| * |
| * See E5 Section 10.6, the creation of setters/getters. |
| * |
| * The variable environment also provides access to the callee, so |
| * an explicit (internal) callee property is not needed. |
| */ |
| |
| duk_push_hobject(ctx, varenv); |
| duk_xdef_prop_stridx(ctx, i_arg, DUK_STRIDX_INT_VARENV, DUK_PROPDESC_FLAGS_NONE); /* out of spec, don't care */ |
| } |
| |
| /* steps 13-14 */ |
| if (DUK_HOBJECT_HAS_STRICT(func)) { |
| /* Callee/caller are throwers and are not deletable etc. They |
| * could be implemented as virtual properties, but currently |
| * there is no support for virtual properties which are accessors |
| * (only plain virtual properties). This would not be difficult |
| * to change in duk_hobject_props, but we can make the throwers |
| * normal, concrete properties just as easily. |
| * |
| * Note that the specification requires that the *same* thrower |
| * built-in object is used here! See E5 Section 10.6 main |
| * algoritm, step 14, and Section 13.2.3 which describes the |
| * thrower. See test case test-arguments-throwers.js. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("strict function, setting caller/callee to throwers")); |
| |
| duk_xdef_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLER, DUK_PROPDESC_FLAGS_NONE); |
| duk_xdef_prop_stridx_thrower(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_NONE); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("non-strict function, setting callee to actual value")); |
| duk_push_hobject(ctx, func); |
| duk_xdef_prop_stridx(ctx, i_arg, DUK_STRIDX_CALLEE, DUK_PROPDESC_FLAGS_WC); |
| } |
| |
| /* set exotic behavior only after we're done */ |
| if (need_map) { |
| /* Exotic behaviors are only enabled for arguments objects |
| * which have a parameter map (see E5 Section 10.6 main |
| * algorithm, step 12). |
| * |
| * In particular, a non-strict arguments object with no |
| * mapped formals does *NOT* get exotic behavior, even |
| * for e.g. "caller" property. This seems counterintuitive |
| * but seems to be the case. |
| */ |
| |
| /* cannot be strict (never mapped variables) */ |
| DUK_ASSERT(!DUK_HOBJECT_HAS_STRICT(func)); |
| |
| DUK_DDD(DUK_DDDPRINT("enabling exotic behavior for arguments object")); |
| DUK_HOBJECT_SET_EXOTIC_ARGUMENTS(arg); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("not enabling exotic behavior for arguments object")); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("final arguments related objects: " |
| "arguments at index %ld -> %!O " |
| "map at index %ld -> %!O " |
| "mappednames at index %ld -> %!O", |
| (long) i_arg, (duk_heaphdr *) duk_get_hobject(ctx, i_arg), |
| (long) i_map, (duk_heaphdr *) duk_get_hobject(ctx, i_map), |
| (long) i_mappednames, (duk_heaphdr *) duk_get_hobject(ctx, i_mappednames))); |
| |
| /* [ args(n) [crud] formals arguments map mappednames ] */ |
| |
| duk_pop_2(ctx); |
| duk_remove(ctx, -2); |
| |
| /* [ args [crud] arguments ] */ |
| } |
| |
| /* Helper for creating the arguments object and adding it to the env record |
| * on top of the value stack. This helper has a very strict dependency on |
| * the shape of the input stack. |
| */ |
| DUK_LOCAL void duk__handle_createargs_for_call(duk_hthread *thr, |
| duk_hobject *func, |
| duk_hobject *env, |
| duk_idx_t num_stack_args) { |
| duk_context *ctx = (duk_context *) thr; |
| |
| DUK_DDD(DUK_DDDPRINT("creating arguments object for function call")); |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(env != NULL); |
| DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func)); |
| DUK_ASSERT(duk_get_top(ctx) >= num_stack_args + 1); |
| |
| /* [ ... arg1 ... argN envobj ] */ |
| |
| duk__create_arguments_object(thr, |
| func, |
| env, |
| duk_get_top(ctx) - num_stack_args - 1, /* idx_argbase */ |
| num_stack_args); |
| |
| /* [ ... arg1 ... argN envobj argobj ] */ |
| |
| duk_xdef_prop_stridx(ctx, |
| -2, |
| DUK_STRIDX_LC_ARGUMENTS, |
| DUK_HOBJECT_HAS_STRICT(func) ? DUK_PROPDESC_FLAGS_E : /* strict: non-deletable, non-writable */ |
| DUK_PROPDESC_FLAGS_WE); /* non-strict: non-deletable, writable */ |
| /* [ ... arg1 ... argN envobj ] */ |
| } |
| |
| /* |
| * Helper for handling a "bound function" chain when a call is being made. |
| * |
| * Follows the bound function chain until a non-bound function is found. |
| * Prepends the bound arguments to the value stack (at idx_func + 2), |
| * updating 'num_stack_args' in the process. The 'this' binding is also |
| * updated if necessary (at idx_func + 1). Note that for constructor calls |
| * the 'this' binding is never updated by [[BoundThis]]. |
| * |
| * XXX: bound function chains could be collapsed at bound function creation |
| * time so that each bound function would point directly to a non-bound |
| * function. This would make call time handling much easier. |
| */ |
| |
| DUK_LOCAL void duk__handle_bound_chain_for_call(duk_hthread *thr, |
| duk_idx_t idx_func, |
| duk_idx_t *p_num_stack_args, /* may be changed by call */ |
| duk_bool_t is_constructor_call) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_idx_t num_stack_args; |
| duk_tval *tv_func; |
| duk_hobject *func; |
| duk_uint_t sanity; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(p_num_stack_args != NULL); |
| |
| /* On entry, item at idx_func is a bound, non-lightweight function, |
| * but we don't rely on that below. |
| */ |
| |
| num_stack_args = *p_num_stack_args; |
| |
| sanity = DUK_HOBJECT_BOUND_CHAIN_SANITY; |
| do { |
| duk_idx_t i, len; |
| |
| tv_func = duk_require_tval(ctx, idx_func); |
| DUK_ASSERT(tv_func != NULL); |
| |
| if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) { |
| /* Lightweight function: never bound, so terminate. */ |
| break; |
| } else if (DUK_TVAL_IS_OBJECT(tv_func)) { |
| func = DUK_TVAL_GET_OBJECT(tv_func); |
| if (!DUK_HOBJECT_HAS_BOUND(func)) { |
| /* Normal non-bound function. */ |
| break; |
| } |
| } else { |
| /* Function.prototype.bind() should never let this happen, |
| * ugly error message is enough. |
| */ |
| DUK_ERROR_INTERNAL_DEFMSG(thr); |
| } |
| DUK_ASSERT(DUK_TVAL_GET_OBJECT(tv_func) != NULL); |
| |
| /* XXX: this could be more compact by accessing the internal properties |
| * directly as own properties (they cannot be inherited, and are not |
| * externally visible). |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("bound function encountered, ptr=%p, num_stack_args=%ld: %!T", |
| (void *) DUK_TVAL_GET_OBJECT(tv_func), (long) num_stack_args, tv_func)); |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| if (is_constructor_call) { |
| /* See: tests/ecmascript/test-spec-bound-constructor.js */ |
| DUK_DDD(DUK_DDDPRINT("constructor call: don't update this binding")); |
| } else { |
| duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_THIS); |
| duk_replace(ctx, idx_func + 1); /* idx_this = idx_func + 1 */ |
| } |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| /* XXX: duk_get_length? */ |
| duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_ARGS); /* -> [ ... func this arg1 ... argN _Args ] */ |
| duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LENGTH); /* -> [ ... func this arg1 ... argN _Args length ] */ |
| len = (duk_idx_t) duk_require_int(ctx, -1); |
| duk_pop(ctx); |
| for (i = 0; i < len; i++) { |
| /* XXX: very slow - better to bulk allocate a gap, and copy |
| * from args_array directly (we know it has a compact array |
| * part, etc). |
| */ |
| |
| /* [ ... func this <some bound args> arg1 ... argN _Args ] */ |
| duk_get_prop_index(ctx, -1, i); |
| duk_insert(ctx, idx_func + 2 + i); /* idx_args = idx_func + 2 */ |
| } |
| num_stack_args += len; /* must be updated to work properly (e.g. creation of 'arguments') */ |
| duk_pop(ctx); |
| |
| /* [ ... func this <bound args> arg1 ... argN ] */ |
| |
| duk_get_prop_stridx(ctx, idx_func, DUK_STRIDX_INT_TARGET); |
| duk_replace(ctx, idx_func); /* replace in stack */ |
| |
| DUK_DDD(DUK_DDDPRINT("bound function handled, num_stack_args=%ld, idx_func=%ld, curr func=%!T", |
| (long) num_stack_args, (long) idx_func, duk_get_tval(ctx, idx_func))); |
| } while (--sanity > 0); |
| |
| if (sanity == 0) { |
| DUK_ERROR_RANGE(thr, DUK_STR_BOUND_CHAIN_LIMIT); |
| } |
| |
| DUK_DDD(DUK_DDDPRINT("final non-bound function is: %!T", duk_get_tval(ctx, idx_func))); |
| |
| #if defined(DUK_USE_ASSERTIONS) |
| tv_func = duk_require_tval(ctx, idx_func); |
| DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_func) || DUK_TVAL_IS_OBJECT(tv_func)); |
| if (DUK_TVAL_IS_OBJECT(tv_func)) { |
| func = DUK_TVAL_GET_OBJECT(tv_func); |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func) || |
| DUK_HOBJECT_HAS_NATIVEFUNCTION(func)); |
| } |
| #endif |
| |
| /* write back */ |
| *p_num_stack_args = num_stack_args; |
| } |
| |
| /* |
| * Helper for setting up var_env and lex_env of an activation, |
| * assuming it does NOT have the DUK_HOBJECT_FLAG_NEWENV flag. |
| */ |
| |
| DUK_LOCAL void duk__handle_oldenv_for_call(duk_hthread *thr, |
| duk_hobject *func, |
| duk_activation *act) { |
| duk_tval *tv; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(act != NULL); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_NEWENV(func)); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func)); |
| |
| tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_LEXENV(thr)); |
| if (tv) { |
| DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); |
| DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv))); |
| act->lex_env = DUK_TVAL_GET_OBJECT(tv); |
| |
| tv = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_INT_VARENV(thr)); |
| if (tv) { |
| DUK_ASSERT(DUK_TVAL_IS_OBJECT(tv)); |
| DUK_ASSERT(DUK_HOBJECT_IS_ENV(DUK_TVAL_GET_OBJECT(tv))); |
| act->var_env = DUK_TVAL_GET_OBJECT(tv); |
| } else { |
| act->var_env = act->lex_env; |
| } |
| } else { |
| act->lex_env = thr->builtins[DUK_BIDX_GLOBAL_ENV]; |
| act->var_env = act->lex_env; |
| } |
| |
| DUK_HOBJECT_INCREF_ALLOWNULL(thr, act->lex_env); |
| DUK_HOBJECT_INCREF_ALLOWNULL(thr, act->var_env); |
| } |
| |
| /* |
| * Helper for updating callee 'caller' property. |
| */ |
| |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| DUK_LOCAL void duk__update_func_caller_prop(duk_hthread *thr, duk_hobject *func) { |
| duk_tval *tv_caller; |
| duk_hobject *h_tmp; |
| duk_activation *act_callee; |
| duk_activation *act_caller; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); /* bound chain resolved */ |
| DUK_ASSERT(thr->callstack_top >= 1); |
| |
| if (DUK_HOBJECT_HAS_STRICT(func)) { |
| /* Strict functions don't get their 'caller' updated. */ |
| return; |
| } |
| |
| act_callee = thr->callstack + thr->callstack_top - 1; |
| act_caller = (thr->callstack_top >= 2 ? act_callee - 1 : NULL); |
| |
| /* XXX: check .caller writability? */ |
| |
| /* Backup 'caller' property and update its value. */ |
| tv_caller = duk_hobject_find_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_CALLER(thr)); |
| if (tv_caller) { |
| /* If caller is global/eval code, 'caller' should be set to |
| * 'null'. |
| * |
| * XXX: there is no exotic flag to infer this correctly now. |
| * The NEWENV flag is used now which works as intended for |
| * everything (global code, non-strict eval code, and functions) |
| * except strict eval code. Bound functions are never an issue |
| * because 'func' has been resolved to a non-bound function. |
| */ |
| |
| if (act_caller) { |
| /* act_caller->func may be NULL in some finalization cases, |
| * just treat like we don't know the caller. |
| */ |
| if (act_caller->func && !DUK_HOBJECT_HAS_NEWENV(act_caller->func)) { |
| /* Setting to NULL causes 'caller' to be set to |
| * 'null' as desired. |
| */ |
| act_caller = NULL; |
| } |
| } |
| |
| if (DUK_TVAL_IS_OBJECT(tv_caller)) { |
| h_tmp = DUK_TVAL_GET_OBJECT(tv_caller); |
| DUK_ASSERT(h_tmp != NULL); |
| act_callee->prev_caller = h_tmp; |
| |
| /* Previous value doesn't need refcount changes because its ownership |
| * is transferred to prev_caller. |
| */ |
| |
| if (act_caller) { |
| DUK_ASSERT(act_caller->func != NULL); |
| DUK_TVAL_SET_OBJECT(tv_caller, act_caller->func); |
| DUK_TVAL_INCREF(thr, tv_caller); |
| } else { |
| DUK_TVAL_SET_NULL(tv_caller); /* no incref */ |
| } |
| } else { |
| /* 'caller' must only take on 'null' or function value */ |
| DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_caller)); |
| DUK_ASSERT(act_callee->prev_caller == NULL); |
| if (act_caller && act_caller->func) { |
| /* Tolerate act_caller->func == NULL which happens in |
| * some finalization cases; treat like unknown caller. |
| */ |
| DUK_TVAL_SET_OBJECT(tv_caller, act_caller->func); |
| DUK_TVAL_INCREF(thr, tv_caller); |
| } else { |
| DUK_TVAL_SET_NULL(tv_caller); /* no incref */ |
| } |
| } |
| } |
| } |
| #endif /* DUK_USE_NONSTD_FUNC_CALLER_PROPERTY */ |
| |
| /* |
| * Determine the effective 'this' binding and coerce the current value |
| * on the valstack to the effective one (in-place, at idx_this). |
| * |
| * The current this value in the valstack (at idx_this) represents either: |
| * - the caller's requested 'this' binding; or |
| * - a 'this' binding accumulated from the bound function chain |
| * |
| * The final 'this' binding for the target function may still be |
| * different, and is determined as described in E5 Section 10.4.3. |
| * |
| * For global and eval code (E5 Sections 10.4.1 and 10.4.2), we assume |
| * that the caller has provided the correct 'this' binding explicitly |
| * when calling, i.e.: |
| * |
| * - global code: this=global object |
| * - direct eval: this=copy from eval() caller's this binding |
| * - other eval: this=global object |
| * |
| * Note: this function may cause a recursive function call with arbitrary |
| * side effects, because ToObject() may be called. |
| */ |
| |
| DUK_LOCAL void duk__coerce_effective_this_binding(duk_hthread *thr, |
| duk_hobject *func, |
| duk_idx_t idx_this) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_tval *tv_this; |
| duk_hobject *obj_global; |
| |
| if (func == NULL || DUK_HOBJECT_HAS_STRICT(func)) { |
| /* Lightfuncs are always considered strict. */ |
| DUK_DDD(DUK_DDDPRINT("this binding: strict -> use directly")); |
| return; |
| } |
| |
| /* XXX: byte offset */ |
| tv_this = thr->valstack_bottom + idx_this; |
| switch (DUK_TVAL_GET_TAG(tv_this)) { |
| case DUK_TAG_OBJECT: |
| case DUK_TAG_LIGHTFUNC: /* lightfuncs are treated like objects and not coerced */ |
| DUK_DDD(DUK_DDDPRINT("this binding: non-strict, object -> use directly")); |
| break; |
| case DUK_TAG_UNDEFINED: |
| case DUK_TAG_NULL: |
| DUK_DDD(DUK_DDDPRINT("this binding: non-strict, undefined/null -> use global object")); |
| obj_global = thr->builtins[DUK_BIDX_GLOBAL]; |
| /* XXX: avoid this check somehow */ |
| if (DUK_LIKELY(obj_global != NULL)) { |
| DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_this)); /* no need to decref previous value */ |
| DUK_TVAL_SET_OBJECT(tv_this, obj_global); |
| DUK_HOBJECT_INCREF(thr, obj_global); |
| } else { |
| /* This may only happen if built-ins are being "torn down". |
| * This behavior is out of specification scope. |
| */ |
| DUK_D(DUK_DPRINT("this binding: wanted to use global object, but it is NULL -> using undefined instead")); |
| DUK_ASSERT(!DUK_TVAL_IS_HEAP_ALLOCATED(tv_this)); /* no need to decref previous value */ |
| DUK_TVAL_SET_UNDEFINED(tv_this); /* nothing to incref */ |
| } |
| break; |
| default: |
| DUK_ASSERT(!DUK_TVAL_IS_UNUSED(tv_this)); |
| DUK_DDD(DUK_DDDPRINT("this binding: non-strict, not object/undefined/null -> use ToObject(value)")); |
| duk_to_object(ctx, idx_this); /* may have side effects */ |
| break; |
| } |
| } |
| |
| /* |
| * Shared helper for non-bound func lookup. |
| * |
| * Returns duk_hobject * to the final non-bound function (NULL for lightfunc). |
| */ |
| |
| DUK_LOCAL duk_hobject *duk__nonbound_func_lookup(duk_context *ctx, |
| duk_idx_t idx_func, |
| duk_idx_t *out_num_stack_args, |
| duk_tval **out_tv_func, |
| duk_small_uint_t call_flags) { |
| duk_hthread *thr = (duk_hthread *) ctx; |
| duk_tval *tv_func; |
| duk_hobject *func; |
| |
| for (;;) { |
| /* Use loop to minimize code size of relookup after bound function case */ |
| tv_func = DUK_GET_TVAL_POSIDX(ctx, idx_func); |
| DUK_ASSERT(tv_func != NULL); |
| |
| if (DUK_TVAL_IS_OBJECT(tv_func)) { |
| func = DUK_TVAL_GET_OBJECT(tv_func); |
| if (!DUK_HOBJECT_IS_CALLABLE(func)) { |
| goto not_callable_error; |
| } |
| if (DUK_HOBJECT_HAS_BOUND(func)) { |
| duk__handle_bound_chain_for_call(thr, idx_func, out_num_stack_args, call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL); |
| |
| /* The final object may be a normal function or a lightfunc. |
| * We need to re-lookup tv_func because it may have changed |
| * (also value stack may have been resized). Loop again to |
| * do that; we're guaranteed not to come here again. |
| */ |
| DUK_ASSERT(DUK_TVAL_IS_OBJECT(duk_require_tval(ctx, idx_func)) || |
| DUK_TVAL_IS_LIGHTFUNC(duk_require_tval(ctx, idx_func))); |
| continue; |
| } |
| } else if (DUK_TVAL_IS_LIGHTFUNC(tv_func)) { |
| func = NULL; |
| } else { |
| goto not_callable_error; |
| } |
| break; |
| } |
| |
| DUK_ASSERT((DUK_TVAL_IS_OBJECT(tv_func) && DUK_HOBJECT_IS_CALLABLE(DUK_TVAL_GET_OBJECT(tv_func))) || |
| DUK_TVAL_IS_LIGHTFUNC(tv_func)); |
| DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPILEDFUNCTION(func) || |
| DUK_HOBJECT_IS_NATIVEFUNCTION(func))); |
| |
| *out_tv_func = tv_func; |
| return func; |
| |
| not_callable_error: |
| DUK_ASSERT(tv_func != NULL); |
| #if defined(DUK_USE_PARANOID_ERRORS) |
| DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE); |
| #else |
| DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "%s not callable", duk_push_string_tval_readable(ctx, tv_func)); |
| #endif |
| DUK_UNREACHABLE(); |
| return NULL; /* never executed */ |
| } |
| |
| /* |
| * Value stack resize and stack top adjustment helper. |
| * |
| * XXX: This should all be merged to duk_valstack_resize_raw(). |
| */ |
| |
| DUK_LOCAL void duk__adjust_valstack_and_top(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_idx_t idx_args, |
| duk_idx_t nregs, |
| duk_idx_t nargs, |
| duk_hobject *func) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_size_t vs_min_size; |
| duk_bool_t adjusted_top = 0; |
| |
| vs_min_size = (thr->valstack_bottom - thr->valstack) + /* bottom of current func */ |
| idx_args; /* bottom of new func */ |
| |
| if (nregs >= 0) { |
| DUK_ASSERT(nargs >= 0); |
| DUK_ASSERT(nregs >= nargs); |
| vs_min_size += nregs; |
| } else { |
| /* 'func' wants stack "as is" */ |
| vs_min_size += num_stack_args; /* num entries of new func at entry */ |
| } |
| if (func == NULL || DUK_HOBJECT_IS_NATIVEFUNCTION(func)) { |
| vs_min_size += DUK_VALSTACK_API_ENTRY_MINIMUM; /* Duktape/C API guaranteed entries (on top of args) */ |
| } |
| vs_min_size += DUK_VALSTACK_INTERNAL_EXTRA; /* + spare */ |
| |
| /* XXX: We can't resize the value stack to a size smaller than the |
| * current top, so the order of the resize and adjusting the stack |
| * top depends on the current vs. final size of the value stack. |
| * The operations could be combined to avoid this, but the proper |
| * fix is to only grow the value stack on a function call, and only |
| * shrink it (without throwing if the shrink fails) on function |
| * return. |
| */ |
| |
| if (vs_min_size < (duk_size_t) (thr->valstack_top - thr->valstack)) { |
| DUK_DDD(DUK_DDDPRINT(("final size smaller, set top before resize"))); |
| |
| DUK_ASSERT(nregs >= 0); /* can't happen when keeping current stack size */ |
| duk_set_top(ctx, idx_args + nargs); /* clamp anything above nargs */ |
| duk_set_top(ctx, idx_args + nregs); /* extend with undefined */ |
| adjusted_top = 1; |
| } |
| |
| (void) duk_valstack_resize_raw((duk_context *) thr, |
| vs_min_size, |
| DUK_VSRESIZE_FLAG_SHRINK | /* flags */ |
| 0 /* no compact */ | |
| DUK_VSRESIZE_FLAG_THROW); |
| |
| if (!adjusted_top) { |
| if (nregs >= 0) { |
| DUK_ASSERT(nregs >= nargs); |
| duk_set_top(ctx, idx_args + nargs); /* clamp anything above nargs */ |
| duk_set_top(ctx, idx_args + nregs); /* extend with undefined */ |
| } |
| } |
| } |
| |
| /* |
| * Manipulate value stack so that exactly 'num_stack_rets' return |
| * values are at 'idx_retbase' in every case, assuming there are |
| * 'rc' return values on top of stack. |
| * |
| * This is a bit tricky, because the called C function operates in |
| * the same activation record and may have e.g. popped the stack |
| * empty (below idx_retbase). |
| */ |
| |
| DUK_LOCAL void duk__safe_call_adjust_valstack(duk_hthread *thr, duk_idx_t idx_retbase, duk_idx_t num_stack_rets, duk_idx_t num_actual_rets) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_idx_t idx_rcbase; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(idx_retbase >= 0); |
| DUK_ASSERT(num_stack_rets >= 0); |
| DUK_ASSERT(num_actual_rets >= 0); |
| |
| idx_rcbase = duk_get_top(ctx) - num_actual_rets; /* base of known return values */ |
| |
| DUK_DDD(DUK_DDDPRINT("adjust valstack after func call: " |
| "num_stack_rets=%ld, num_actual_rets=%ld, stack_top=%ld, idx_retbase=%ld, idx_rcbase=%ld", |
| (long) num_stack_rets, (long) num_actual_rets, (long) duk_get_top(ctx), |
| (long) idx_retbase, (long) idx_rcbase)); |
| |
| DUK_ASSERT(idx_rcbase >= 0); /* caller must check */ |
| |
| /* Ensure space for final configuration (idx_retbase + num_stack_rets) |
| * and intermediate configurations. |
| */ |
| duk_require_stack_top(ctx, |
| (idx_rcbase > idx_retbase ? idx_rcbase : idx_retbase) + |
| num_stack_rets); |
| |
| /* Chop extra retvals away / extend with undefined. */ |
| duk_set_top(ctx, idx_rcbase + num_stack_rets); |
| |
| if (idx_rcbase >= idx_retbase) { |
| duk_idx_t count = idx_rcbase - idx_retbase; |
| duk_idx_t i; |
| |
| DUK_DDD(DUK_DDDPRINT("elements at/after idx_retbase have enough to cover func retvals " |
| "(idx_retbase=%ld, idx_rcbase=%ld)", (long) idx_retbase, (long) idx_rcbase)); |
| |
| /* nuke values at idx_retbase to get the first retval (initially |
| * at idx_rcbase) to idx_retbase |
| */ |
| |
| DUK_ASSERT(count >= 0); |
| |
| for (i = 0; i < count; i++) { |
| /* XXX: inefficient; block remove primitive */ |
| duk_remove(ctx, idx_retbase); |
| } |
| } else { |
| duk_idx_t count = idx_retbase - idx_rcbase; |
| duk_idx_t i; |
| |
| DUK_DDD(DUK_DDDPRINT("not enough elements at/after idx_retbase to cover func retvals " |
| "(idx_retbase=%ld, idx_rcbase=%ld)", (long) idx_retbase, (long) idx_rcbase)); |
| |
| /* insert 'undefined' values at idx_rcbase to get the |
| * return values to idx_retbase |
| */ |
| |
| DUK_ASSERT(count > 0); |
| |
| for (i = 0; i < count; i++) { |
| /* XXX: inefficient; block insert primitive */ |
| duk_push_undefined(ctx); |
| duk_insert(ctx, idx_rcbase); |
| } |
| } |
| } |
| |
| /* |
| * Misc shared helpers. |
| */ |
| |
| /* Get valstack index for the func argument or throw if insane stack. */ |
| DUK_LOCAL duk_idx_t duk__get_idx_func(duk_hthread *thr, duk_idx_t num_stack_args) { |
| duk_size_t off_stack_top; |
| duk_size_t off_stack_args; |
| duk_size_t off_stack_all; |
| duk_idx_t idx_func; /* valstack index of 'func' and retval (relative to entry valstack_bottom) */ |
| |
| /* Argument validation and func/args offset. */ |
| off_stack_top = (duk_size_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack_bottom); |
| off_stack_args = (duk_size_t) ((duk_size_t) num_stack_args * sizeof(duk_tval)); |
| off_stack_all = off_stack_args + 2 * sizeof(duk_tval); |
| if (DUK_UNLIKELY(off_stack_all > off_stack_top)) { |
| /* Since stack indices are not reliable, we can't do anything useful |
| * here. Invoke the existing setjmp catcher, or if it doesn't exist, |
| * call the fatal error handler. |
| */ |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| return 0; |
| } |
| idx_func = (duk_idx_t) ((off_stack_top - off_stack_all) / sizeof(duk_tval)); |
| return idx_func; |
| } |
| |
| /* |
| * duk_handle_call_protected() and duk_handle_call_unprotected(): |
| * call into a Duktape/C or an Ecmascript function from any state. |
| * |
| * Input stack (thr): |
| * |
| * [ func this arg1 ... argN ] |
| * |
| * Output stack (thr): |
| * |
| * [ retval ] (DUK_EXEC_SUCCESS) |
| * [ errobj ] (DUK_EXEC_ERROR (normal error), protected call) |
| * |
| * Even when executing a protected call an error may be thrown in rare cases |
| * such as an insane num_stack_args argument. If there is no catchpoint for |
| * such errors, the fatal error handler is called. |
| * |
| * The error handling path should be error free, even for out-of-memory |
| * errors, to ensure safe sandboxing. (As of Duktape 1.4.0 this is not |
| * yet the case, see XXX notes below.) |
| */ |
| |
| DUK_INTERNAL duk_int_t duk_handle_call_protected(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_small_uint_t call_flags) { |
| duk_context *ctx; |
| duk_size_t entry_valstack_bottom_index; |
| duk_size_t entry_valstack_end; |
| duk_size_t entry_callstack_top; |
| duk_size_t entry_catchstack_top; |
| duk_int_t entry_call_recursion_depth; |
| duk_hthread *entry_curr_thread; |
| duk_uint_fast8_t entry_thread_state; |
| duk_instr_t **entry_ptr_curr_pc; |
| duk_jmpbuf *old_jmpbuf_ptr = NULL; |
| duk_jmpbuf our_jmpbuf; |
| duk_idx_t idx_func; /* valstack index of 'func' and retval (relative to entry valstack_bottom) */ |
| |
| /* XXX: Multiple tv_func lookups are now avoided by making a local |
| * copy of tv_func. Another approach would be to compute an offset |
| * for tv_func from valstack bottom and recomputing the tv_func |
| * pointer quickly as valstack + offset instead of calling duk_get_tval(). |
| */ |
| |
| ctx = (duk_context *) thr; |
| DUK_UNREF(ctx); |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(num_stack_args >= 0); |
| /* XXX: currently NULL allocations are not supported; remove if later allowed */ |
| DUK_ASSERT(thr->valstack != NULL); |
| DUK_ASSERT(thr->callstack != NULL); |
| DUK_ASSERT(thr->catchstack != NULL); |
| |
| /* Argument validation and func/args offset. */ |
| idx_func = duk__get_idx_func(thr, num_stack_args); |
| |
| /* Preliminaries, required by setjmp() handler. Must be careful not |
| * to throw an unintended error here. |
| */ |
| |
| entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack); |
| #if defined(DUK_USE_PREFER_SIZE) |
| entry_valstack_end = (duk_size_t) (thr->valstack_end - thr->valstack); |
| #else |
| DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size); |
| entry_valstack_end = thr->valstack_size; |
| #endif |
| entry_callstack_top = thr->callstack_top; |
| entry_catchstack_top = thr->catchstack_top; |
| entry_call_recursion_depth = thr->heap->call_recursion_depth; |
| entry_curr_thread = thr->heap->curr_thread; /* Note: may be NULL if first call */ |
| entry_thread_state = thr->state; |
| entry_ptr_curr_pc = thr->ptr_curr_pc; /* may be NULL */ |
| |
| DUK_DD(DUK_DDPRINT("duk_handle_call_protected: thr=%p, num_stack_args=%ld, " |
| "call_flags=0x%08lx (ignorerec=%ld, constructor=%ld), " |
| "valstack_top=%ld, idx_func=%ld, idx_args=%ld, rec_depth=%ld/%ld, " |
| "entry_valstack_bottom_index=%ld, entry_callstack_top=%ld, entry_catchstack_top=%ld, " |
| "entry_call_recursion_depth=%ld, entry_curr_thread=%p, entry_thread_state=%ld", |
| (void *) thr, |
| (long) num_stack_args, |
| (unsigned long) call_flags, |
| (long) ((call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) != 0 ? 1 : 0), |
| (long) ((call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) != 0 ? 1 : 0), |
| (long) duk_get_top(ctx), |
| (long) idx_func, |
| (long) (idx_func + 2), |
| (long) thr->heap->call_recursion_depth, |
| (long) thr->heap->call_recursion_limit, |
| (long) entry_valstack_bottom_index, |
| (long) entry_callstack_top, |
| (long) entry_catchstack_top, |
| (long) entry_call_recursion_depth, |
| (void *) entry_curr_thread, |
| (long) entry_thread_state)); |
| |
| old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr; |
| thr->heap->lj.jmpbuf_ptr = &our_jmpbuf; |
| |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| try { |
| #else |
| DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == &our_jmpbuf); |
| if (DUK_SETJMP(our_jmpbuf.jb) == 0) { |
| #endif |
| /* Call handling and success path. Success path exit cleans |
| * up almost all state. |
| */ |
| duk__handle_call_inner(thr, num_stack_args, call_flags, idx_func); |
| |
| /* Success path handles */ |
| DUK_ASSERT(thr->heap->call_recursion_depth == entry_call_recursion_depth); |
| DUK_ASSERT(thr->ptr_curr_pc == entry_ptr_curr_pc); |
| |
| /* Longjmp state is kept clean in success path */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN); |
| DUK_ASSERT(thr->heap->lj.iserror == 0); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1)); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); |
| |
| thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr; |
| |
| return DUK_EXEC_SUCCESS; |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| } catch (duk_internal_exception &exc) { |
| #else |
| } else { |
| #endif |
| /* Error; error value is in heap->lj.value1. */ |
| |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| DUK_UNREF(exc); |
| #endif |
| |
| duk__handle_call_error(thr, |
| entry_valstack_bottom_index, |
| entry_valstack_end, |
| entry_catchstack_top, |
| entry_callstack_top, |
| entry_call_recursion_depth, |
| entry_curr_thread, |
| entry_thread_state, |
| entry_ptr_curr_pc, |
| idx_func, |
| old_jmpbuf_ptr); |
| |
| /* Longjmp state is cleaned up by error handling */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN); |
| DUK_ASSERT(thr->heap->lj.iserror == 0); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1)); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); |
| return DUK_EXEC_ERROR; |
| } |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| catch (std::exception &exc) { |
| const char *what = exc.what(); |
| if (!what) { |
| what = "unknown"; |
| } |
| DUK_D(DUK_DPRINT("unexpected c++ std::exception (perhaps thrown by user code)")); |
| try { |
| DUK_ERROR_FMT1(thr, DUK_ERR_API_ERROR, "caught invalid c++ std::exception '%s' (perhaps thrown by user code)", what); |
| } catch (duk_internal_exception exc) { |
| DUK_D(DUK_DPRINT("caught api error thrown from unexpected c++ std::exception")); |
| DUK_UNREF(exc); |
| duk__handle_call_error(thr, |
| entry_valstack_bottom_index, |
| entry_valstack_end, |
| entry_catchstack_top, |
| entry_callstack_top, |
| entry_call_recursion_depth, |
| entry_curr_thread, |
| entry_thread_state, |
| entry_ptr_curr_pc, |
| idx_func, |
| old_jmpbuf_ptr); |
| return DUK_EXEC_ERROR; |
| } |
| } catch (...) { |
| DUK_D(DUK_DPRINT("unexpected c++ exception (perhaps thrown by user code)")); |
| try { |
| DUK_ERROR_API(thr, "caught invalid c++ exception (perhaps thrown by user code)"); |
| } catch (duk_internal_exception exc) { |
| DUK_D(DUK_DPRINT("caught api error thrown from unexpected c++ exception")); |
| DUK_UNREF(exc); |
| duk__handle_call_error(thr, |
| entry_valstack_bottom_index, |
| entry_valstack_end, |
| entry_catchstack_top, |
| entry_callstack_top, |
| entry_call_recursion_depth, |
| entry_curr_thread, |
| entry_thread_state, |
| entry_ptr_curr_pc, |
| idx_func, |
| old_jmpbuf_ptr); |
| return DUK_EXEC_ERROR; |
| } |
| } |
| #endif |
| } |
| |
| DUK_INTERNAL void duk_handle_call_unprotected(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_small_uint_t call_flags) { |
| duk_idx_t idx_func; /* valstack index of 'func' and retval (relative to entry valstack_bottom) */ |
| |
| /* Argument validation and func/args offset. */ |
| idx_func = duk__get_idx_func(thr, num_stack_args); |
| |
| duk__handle_call_inner(thr, num_stack_args, call_flags, idx_func); |
| } |
| |
| DUK_LOCAL void duk__handle_call_inner(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_small_uint_t call_flags, |
| duk_idx_t idx_func) { |
| duk_context *ctx; |
| duk_size_t entry_valstack_bottom_index; |
| duk_size_t entry_valstack_end; |
| duk_size_t entry_callstack_top; |
| duk_size_t entry_catchstack_top; |
| duk_int_t entry_call_recursion_depth; |
| duk_hthread *entry_curr_thread; |
| duk_uint_fast8_t entry_thread_state; |
| duk_instr_t **entry_ptr_curr_pc; |
| duk_idx_t nargs; /* # argument registers target function wants (< 0 => "as is") */ |
| duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => "as is") */ |
| duk_hobject *func; /* 'func' on stack (borrowed reference) */ |
| duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) or tv_func_copy */ |
| duk_tval tv_func_copy; /* to avoid relookups */ |
| duk_activation *act; |
| duk_hobject *env; |
| duk_ret_t rc; |
| |
| ctx = (duk_context *) thr; |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_ASSERT(ctx != NULL); |
| DUK_ASSERT(num_stack_args >= 0); |
| /* XXX: currently NULL allocations are not supported; remove if later allowed */ |
| DUK_ASSERT(thr->valstack != NULL); |
| DUK_ASSERT(thr->callstack != NULL); |
| DUK_ASSERT(thr->catchstack != NULL); |
| |
| DUK_DD(DUK_DDPRINT("duk__handle_call_inner: num_stack_args=%ld, call_flags=0x%08lx, top=%ld", |
| (long) num_stack_args, (long) call_flags, (long) duk_get_top(ctx))); |
| |
| /* |
| * Store entry state. |
| */ |
| |
| entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack); |
| #if defined(DUK_USE_PREFER_SIZE) |
| entry_valstack_end = (duk_size_t) (thr->valstack_end - thr->valstack); |
| #else |
| DUK_ASSERT((duk_size_t) (thr->valstack_end - thr->valstack) == thr->valstack_size); |
| entry_valstack_end = thr->valstack_size; |
| #endif |
| entry_callstack_top = thr->callstack_top; |
| entry_catchstack_top = thr->catchstack_top; |
| entry_call_recursion_depth = thr->heap->call_recursion_depth; |
| entry_curr_thread = thr->heap->curr_thread; /* Note: may be NULL if first call */ |
| entry_thread_state = thr->state; |
| entry_ptr_curr_pc = thr->ptr_curr_pc; /* may be NULL */ |
| |
| /* If thr->ptr_curr_pc is set, sync curr_pc to act->pc. Then NULL |
| * thr->ptr_curr_pc so that it's not accidentally used with an incorrect |
| * activation when side effects occur. |
| */ |
| duk_hthread_sync_and_null_currpc(thr); |
| |
| DUK_DD(DUK_DDPRINT("duk__handle_call_inner: thr=%p, num_stack_args=%ld, " |
| "call_flags=0x%08lx (ignorerec=%ld, constructor=%ld), " |
| "valstack_top=%ld, idx_func=%ld, idx_args=%ld, rec_depth=%ld/%ld, " |
| "entry_valstack_bottom_index=%ld, entry_callstack_top=%ld, entry_catchstack_top=%ld, " |
| "entry_call_recursion_depth=%ld, entry_curr_thread=%p, entry_thread_state=%ld", |
| (void *) thr, |
| (long) num_stack_args, |
| (unsigned long) call_flags, |
| (long) ((call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) != 0 ? 1 : 0), |
| (long) ((call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) != 0 ? 1 : 0), |
| (long) duk_get_top(ctx), |
| (long) idx_func, |
| (long) (idx_func + 2), |
| (long) thr->heap->call_recursion_depth, |
| (long) thr->heap->call_recursion_limit, |
| (long) entry_valstack_bottom_index, |
| (long) entry_callstack_top, |
| (long) entry_catchstack_top, |
| (long) entry_call_recursion_depth, |
| (void *) entry_curr_thread, |
| (long) entry_thread_state)); |
| |
| |
| /* |
| * Thread state check and book-keeping. |
| */ |
| |
| if (thr == thr->heap->curr_thread) { |
| /* same thread */ |
| if (thr->state != DUK_HTHREAD_STATE_RUNNING) { |
| /* should actually never happen, but check anyway */ |
| goto thread_state_error; |
| } |
| } else { |
| /* different thread */ |
| DUK_ASSERT(thr->heap->curr_thread == NULL || |
| thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING); |
| if (thr->state != DUK_HTHREAD_STATE_INACTIVE) { |
| goto thread_state_error; |
| } |
| DUK_HEAP_SWITCH_THREAD(thr->heap, thr); |
| thr->state = DUK_HTHREAD_STATE_RUNNING; |
| |
| /* Note: multiple threads may be simultaneously in the RUNNING |
| * state, but not in the same "resume chain". |
| */ |
| } |
| DUK_ASSERT(thr->heap->curr_thread == thr); |
| DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); |
| |
| /* |
| * C call recursion depth check, which provides a reasonable upper |
| * bound on maximum C stack size (arbitrary C stack growth is only |
| * possible by recursive handle_call / handle_safe_call calls). |
| */ |
| |
| /* XXX: remove DUK_CALL_FLAG_IGNORE_RECLIMIT flag: there's now the |
| * reclimit bump? |
| */ |
| |
| DUK_ASSERT(thr->heap->call_recursion_depth >= 0); |
| DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit); |
| if (call_flags & DUK_CALL_FLAG_IGNORE_RECLIMIT) { |
| DUK_DD(DUK_DDPRINT("ignoring reclimit for this call (probably an errhandler call)")); |
| } else { |
| if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) { |
| /* XXX: error message is a bit misleading: we reached a recursion |
| * limit which is also essentially the same as a C callstack limit |
| * (except perhaps with some relaxed threading assumptions). |
| */ |
| DUK_ERROR_RANGE(thr, DUK_STR_C_CALLSTACK_LIMIT); |
| } |
| thr->heap->call_recursion_depth++; |
| } |
| |
| /* |
| * Check the function type, handle bound function chains, and prepare |
| * parameters for the rest of the call handling. Also figure out the |
| * effective 'this' binding, which replaces the current value at |
| * idx_func + 1. |
| * |
| * If the target function is a 'bound' one, follow the chain of 'bound' |
| * functions until a non-bound function is found. During this process, |
| * bound arguments are 'prepended' to existing ones, and the "this" |
| * binding is overridden. See E5 Section 15.3.4.5.1. |
| * |
| * Lightfunc detection happens here too. Note that lightweight functions |
| * can be wrapped by (non-lightweight) bound functions so we must resolve |
| * the bound function chain first. |
| */ |
| |
| func = duk__nonbound_func_lookup(ctx, idx_func, &num_stack_args, &tv_func, call_flags); |
| DUK_TVAL_SET_TVAL(&tv_func_copy, tv_func); |
| tv_func = &tv_func_copy; /* local copy to avoid relookups */ |
| |
| DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(func == NULL || (DUK_HOBJECT_IS_COMPILEDFUNCTION(func) || |
| DUK_HOBJECT_IS_NATIVEFUNCTION(func))); |
| |
| duk__coerce_effective_this_binding(thr, func, idx_func + 1); |
| DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T", |
| (duk_tval *) duk_get_tval(ctx, idx_func + 1))); |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| /* |
| * Setup a preliminary activation and figure out nargs/nregs. |
| * |
| * Don't touch valstack_bottom or valstack_top yet so that Duktape API |
| * calls work normally. |
| */ |
| |
| duk_hthread_callstack_grow(thr); |
| |
| if (thr->callstack_top > 0) { |
| /* |
| * Update idx_retval of current activation. |
| * |
| * Although it might seem this is not necessary (bytecode executor |
| * does this for Ecmascript-to-Ecmascript calls; other calls are |
| * handled here), this turns out to be necessary for handling yield |
| * and resume. For them, an Ecmascript-to-native call happens, and |
| * the Ecmascript call's idx_retval must be set for things to work. |
| */ |
| |
| (thr->callstack + thr->callstack_top - 1)->idx_retval = entry_valstack_bottom_index + idx_func; |
| } |
| |
| DUK_ASSERT(thr->callstack_top < thr->callstack_size); |
| act = thr->callstack + thr->callstack_top; |
| thr->callstack_top++; |
| DUK_ASSERT(thr->callstack_top <= thr->callstack_size); |
| DUK_ASSERT(thr->valstack_top > thr->valstack_bottom); /* at least effective 'this' */ |
| DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func)); |
| |
| act->flags = 0; |
| |
| /* For now all calls except Ecma-to-Ecma calls prevent a yield. */ |
| act->flags |= DUK_ACT_FLAG_PREVENT_YIELD; |
| if (call_flags & DUK_CALL_FLAG_CONSTRUCTOR_CALL) { |
| act->flags |= DUK_ACT_FLAG_CONSTRUCT; |
| } |
| if (call_flags & DUK_CALL_FLAG_DIRECT_EVAL) { |
| act->flags |= DUK_ACT_FLAG_DIRECT_EVAL; |
| } |
| |
| /* These base values are never used, but if the compiler doesn't know |
| * that DUK_ERROR() won't return, these are needed to silence warnings. |
| * On the other hand, scan-build will warn about the values not being |
| * used, so add a DUK_UNREF. |
| */ |
| nargs = 0; DUK_UNREF(nargs); |
| nregs = 0; DUK_UNREF(nregs); |
| |
| if (DUK_LIKELY(func != NULL)) { |
| if (DUK_HOBJECT_HAS_STRICT(func)) { |
| act->flags |= DUK_ACT_FLAG_STRICT; |
| } |
| if (DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { |
| nargs = ((duk_hcompiledfunction *) func)->nargs; |
| nregs = ((duk_hcompiledfunction *) func)->nregs; |
| DUK_ASSERT(nregs >= nargs); |
| } else if (DUK_HOBJECT_IS_NATIVEFUNCTION(func)) { |
| /* Note: nargs (and nregs) may be negative for a native, |
| * function, which indicates that the function wants the |
| * input stack "as is" (i.e. handles "vararg" arguments). |
| */ |
| nargs = ((duk_hnativefunction *) func)->nargs; |
| nregs = nargs; |
| } else { |
| /* XXX: this should be an assert */ |
| DUK_ERROR_TYPE(thr, DUK_STR_NOT_CALLABLE); |
| } |
| } else { |
| duk_small_uint_t lf_flags; |
| |
| DUK_ASSERT(DUK_TVAL_IS_LIGHTFUNC(tv_func)); |
| lf_flags = DUK_TVAL_GET_LIGHTFUNC_FLAGS(tv_func); |
| nargs = DUK_LFUNC_FLAGS_GET_NARGS(lf_flags); |
| if (nargs == DUK_LFUNC_NARGS_VARARGS) { |
| nargs = -1; /* vararg */ |
| } |
| nregs = nargs; |
| |
| act->flags |= DUK_ACT_FLAG_STRICT; |
| } |
| |
| act->func = func; /* NULL for lightfunc */ |
| act->var_env = NULL; |
| act->lex_env = NULL; |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| act->prev_caller = NULL; |
| #endif |
| act->curr_pc = NULL; |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| act->prev_line = 0; |
| #endif |
| act->idx_bottom = entry_valstack_bottom_index + idx_func + 2; |
| #if 0 /* topmost activation idx_retval is considered garbage, no need to init */ |
| act->idx_retval = 0; |
| #endif |
| DUK_TVAL_SET_TVAL(&act->tv_func, tv_func); /* borrowed, no refcount */ |
| |
| /* XXX: remove the preventcount and make yield walk the callstack? |
| * Or perhaps just use a single flag, not a counter, faster to just |
| * set and restore? |
| */ |
| if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) { |
| /* duk_hthread_callstack_unwind() will decrease this on unwind */ |
| thr->callstack_preventcount++; |
| } |
| |
| /* XXX: Is this INCREF necessary? 'func' is always a borrowed |
| * reference reachable through the value stack? If changed, stack |
| * unwind code also needs to be fixed to match. |
| */ |
| DUK_HOBJECT_INCREF_ALLOWNULL(thr, func); /* act->func */ |
| |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| if (func) { |
| duk__update_func_caller_prop(thr, func); |
| } |
| act = thr->callstack + thr->callstack_top - 1; |
| #endif |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| /* |
| * Environment record creation and 'arguments' object creation. |
| * Named function expression name binding is handled by the |
| * compiler; the compiled function's parent env will contain |
| * the (immutable) binding already. |
| * |
| * This handling is now identical for C and Ecmascript functions. |
| * C functions always have the 'NEWENV' flag set, so their |
| * environment record initialization is delayed (which is good). |
| * |
| * Delayed creation (on demand) is handled in duk_js_var.c. |
| */ |
| |
| DUK_ASSERT(func == NULL || !DUK_HOBJECT_HAS_BOUND(func)); /* bound function chain has already been resolved */ |
| |
| if (DUK_LIKELY(func != NULL)) { |
| if (DUK_LIKELY(DUK_HOBJECT_HAS_NEWENV(func))) { |
| if (DUK_LIKELY(!DUK_HOBJECT_HAS_CREATEARGS(func))) { |
| /* Use a new environment but there's no 'arguments' object; |
| * delayed environment initialization. This is the most |
| * common case. |
| */ |
| DUK_ASSERT(act->lex_env == NULL); |
| DUK_ASSERT(act->var_env == NULL); |
| } else { |
| /* Use a new environment and there's an 'arguments' object. |
| * We need to initialize it right now. |
| */ |
| |
| /* third arg: absolute index (to entire valstack) of idx_bottom of new activation */ |
| env = duk_create_activation_environment_record(thr, func, act->idx_bottom); |
| DUK_ASSERT(env != NULL); |
| |
| /* [ ... func this arg1 ... argN envobj ] */ |
| |
| DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func)); |
| duk__handle_createargs_for_call(thr, func, env, num_stack_args); |
| |
| /* [ ... func this arg1 ... argN envobj ] */ |
| |
| act = thr->callstack + thr->callstack_top - 1; |
| act->lex_env = env; |
| act->var_env = env; |
| DUK_HOBJECT_INCREF(thr, env); |
| DUK_HOBJECT_INCREF(thr, env); /* XXX: incref by count (2) directly */ |
| duk_pop(ctx); |
| } |
| } else { |
| /* Use existing env (e.g. for non-strict eval); cannot have |
| * an own 'arguments' object (but can refer to an existing one). |
| */ |
| |
| DUK_ASSERT(!DUK_HOBJECT_HAS_CREATEARGS(func)); |
| |
| duk__handle_oldenv_for_call(thr, func, act); |
| |
| DUK_ASSERT(act->lex_env != NULL); |
| DUK_ASSERT(act->var_env != NULL); |
| } |
| } else { |
| /* Lightfuncs are always native functions and have "newenv". */ |
| DUK_ASSERT(act->lex_env == NULL); |
| DUK_ASSERT(act->var_env == NULL); |
| } |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| /* |
| * Setup value stack: clamp to 'nargs', fill up to 'nregs' |
| * |
| * Value stack may either grow or shrink, depending on the |
| * number of func registers and the number of actual arguments. |
| * If nregs >= 0, func wants args clamped to 'nargs'; else it |
| * wants all args (= 'num_stack_args'). |
| */ |
| |
| /* XXX: optimize value stack operation */ |
| /* XXX: don't want to shrink allocation here */ |
| |
| duk__adjust_valstack_and_top(thr, |
| num_stack_args, |
| idx_func + 2, |
| nregs, |
| nargs, |
| func); |
| |
| /* |
| * Determine call type, then finalize activation, shift to |
| * new value stack bottom, and call the target. |
| */ |
| |
| if (func != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { |
| /* |
| * Ecmascript call |
| */ |
| |
| duk_tval *tv_ret; |
| duk_tval *tv_funret; |
| |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func)); |
| act->curr_pc = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(thr->heap, (duk_hcompiledfunction *) func); |
| |
| thr->valstack_bottom = thr->valstack_bottom + idx_func + 2; |
| /* keep current valstack_top */ |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| /* [ ... func this | arg1 ... argN ] ('this' must precede new bottom) */ |
| |
| /* |
| * Bytecode executor call. |
| * |
| * Execute bytecode, handling any recursive function calls and |
| * thread resumptions. Returns when execution would return from |
| * the entry level activation. When the executor returns, a |
| * single return value is left on the stack top. |
| * |
| * The only possible longjmp() is an error (DUK_LJ_TYPE_THROW), |
| * other types are handled internally by the executor. |
| */ |
| |
| /* thr->ptr_curr_pc is set by bytecode executor early on entry */ |
| DUK_ASSERT(thr->ptr_curr_pc == NULL); |
| DUK_DDD(DUK_DDDPRINT("entering bytecode execution")); |
| duk_js_execute_bytecode(thr); |
| DUK_DDD(DUK_DDDPRINT("returned from bytecode execution")); |
| |
| /* Unwind. */ |
| |
| DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top); /* may need unwind */ |
| DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1); |
| DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1); |
| duk_hthread_catchstack_unwind(thr, entry_catchstack_top); |
| duk_hthread_catchstack_shrink_check(thr); |
| duk_hthread_callstack_unwind(thr, entry_callstack_top); |
| duk_hthread_callstack_shrink_check(thr); |
| |
| thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index; |
| /* keep current valstack_top */ |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1); |
| |
| /* Return value handling. */ |
| |
| /* [ ... func this (crud) retval ] */ |
| |
| tv_ret = thr->valstack_bottom + idx_func; |
| tv_funret = thr->valstack_top - 1; |
| #if defined(DUK_USE_FASTINT) |
| /* Explicit check for fastint downgrade. */ |
| DUK_TVAL_CHKFAST_INPLACE(tv_funret); |
| #endif |
| DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, tv_funret); /* side effects */ |
| } else { |
| /* |
| * Native call. |
| */ |
| |
| duk_tval *tv_ret; |
| duk_tval *tv_funret; |
| |
| thr->valstack_bottom = thr->valstack_bottom + idx_func + 2; |
| /* keep current valstack_top */ |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| DUK_ASSERT(func == NULL || ((duk_hnativefunction *) func)->func != NULL); |
| |
| /* [ ... func this | arg1 ... argN ] ('this' must precede new bottom) */ |
| |
| /* For native calls must be NULL so we don't sync back */ |
| DUK_ASSERT(thr->ptr_curr_pc == NULL); |
| |
| if (func) { |
| rc = ((duk_hnativefunction *) func)->func((duk_context *) thr); |
| } else { |
| duk_c_function funcptr = DUK_TVAL_GET_LIGHTFUNC_FUNCPTR(tv_func); |
| rc = funcptr((duk_context *) thr); |
| } |
| |
| /* Automatic error throwing, retval check. */ |
| |
| if (rc < 0) { |
| duk_error_throw_from_negative_rc(thr, rc); |
| DUK_UNREACHABLE(); |
| } else if (rc > 1) { |
| DUK_ERROR_API(thr, "c function returned invalid rc"); |
| } |
| DUK_ASSERT(rc == 0 || rc == 1); |
| |
| /* Unwind. */ |
| |
| DUK_ASSERT(thr->catchstack_top == entry_catchstack_top); /* no need to unwind */ |
| DUK_ASSERT(thr->callstack_top == entry_callstack_top + 1); |
| duk_hthread_callstack_unwind(thr, entry_callstack_top); |
| duk_hthread_callstack_shrink_check(thr); |
| |
| thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index; |
| /* keep current valstack_top */ |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| DUK_ASSERT(thr->valstack_top - thr->valstack_bottom >= idx_func + 1); |
| |
| /* Return value handling. */ |
| |
| /* XXX: should this happen in the callee's activation or after unwinding? */ |
| tv_ret = thr->valstack_bottom + idx_func; |
| if (rc == 0) { |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, tv_ret); /* side effects */ |
| } else { |
| /* [ ... func this (crud) retval ] */ |
| tv_funret = thr->valstack_top - 1; |
| #if defined(DUK_USE_FASTINT) |
| /* Explicit check for fastint downgrade. */ |
| DUK_TVAL_CHKFAST_INPLACE(tv_funret); |
| #endif |
| DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, tv_funret); /* side effects */ |
| } |
| } |
| |
| duk_set_top(ctx, idx_func + 1); /* XXX: unnecessary, handle in adjust */ |
| |
| /* [ ... retval ] */ |
| |
| /* Ensure there is internal valstack spare before we exit; this may |
| * throw an alloc error. The same guaranteed size must be available |
| * as before the call. This is not optimal now: we store the valstack |
| * allocated size during entry; this value may be higher than the |
| * minimal guarantee for an application. |
| */ |
| |
| /* XXX: we should never shrink here; when we error out later, we'd |
| * need to potentially grow the value stack in error unwind which could |
| * cause another error. |
| */ |
| |
| (void) duk_valstack_resize_raw((duk_context *) thr, |
| entry_valstack_end, /* same as during entry */ |
| DUK_VSRESIZE_FLAG_SHRINK | /* flags */ |
| DUK_VSRESIZE_FLAG_COMPACT | |
| DUK_VSRESIZE_FLAG_THROW); |
| |
| /* Restore entry thread executor curr_pc stack frame pointer. */ |
| thr->ptr_curr_pc = entry_ptr_curr_pc; |
| |
| DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread); /* may be NULL */ |
| thr->state = (duk_uint8_t) entry_thread_state; |
| |
| DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) || /* first call */ |
| (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) || /* other call */ |
| (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr)); /* current thread */ |
| |
| thr->heap->call_recursion_depth = entry_call_recursion_depth; |
| |
| /* If the debugger is active we need to force an interrupt so that |
| * debugger breakpoints are rechecked. This is important for function |
| * calls caused by side effects (e.g. when doing a DUK_OP_GETPROP), see |
| * GH-303. Only needed for success path, error path always causes a |
| * breakpoint recheck in the executor. It would be enough to set this |
| * only when returning to an Ecmascript activation, but setting the flag |
| * on every return should have no ill effect. |
| */ |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) { |
| DUK_DD(DUK_DDPRINT("returning with debugger enabled, force interrupt")); |
| DUK_ASSERT(thr->interrupt_counter <= thr->interrupt_init); |
| thr->interrupt_init -= thr->interrupt_counter; |
| thr->interrupt_counter = 0; |
| thr->heap->dbg_force_restart = 1; |
| } |
| #endif |
| |
| #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG) |
| duk__interrupt_fixup(thr, entry_curr_thread); |
| #endif |
| |
| return; |
| |
| thread_state_error: |
| DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for call (%ld)", (long) thr->state); |
| DUK_UNREACHABLE(); |
| return; /* never executed */ |
| } |
| |
| DUK_LOCAL void duk__handle_call_error(duk_hthread *thr, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_valstack_end, |
| duk_size_t entry_catchstack_top, |
| duk_size_t entry_callstack_top, |
| duk_int_t entry_call_recursion_depth, |
| duk_hthread *entry_curr_thread, |
| duk_uint_fast8_t entry_thread_state, |
| duk_instr_t **entry_ptr_curr_pc, |
| duk_idx_t idx_func, |
| duk_jmpbuf *old_jmpbuf_ptr) { |
| duk_context *ctx; |
| duk_tval *tv_ret; |
| |
| ctx = (duk_context *) thr; |
| |
| DUK_DDD(DUK_DDDPRINT("error caught during duk__handle_call_inner(): %!T", |
| (duk_tval *) &thr->heap->lj.value1)); |
| |
| /* Other longjmp types are handled by executor before propagating |
| * the error here. |
| */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW); |
| DUK_ASSERT(thr->callstack_top >= entry_callstack_top); |
| DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top); |
| |
| /* We don't need to sync back thr->ptr_curr_pc here because |
| * the bytecode executor always has a setjmp catchpoint which |
| * does that before errors propagate to here. |
| */ |
| DUK_ASSERT(thr->ptr_curr_pc == NULL); |
| |
| /* Restore the previous setjmp catcher so that any error in |
| * error handling will propagate outwards rather than re-enter |
| * the same handler. However, the error handling path must be |
| * designed to be error free so that sandboxing guarantees are |
| * reliable, see e.g. https://github.com/svaarala/duktape/issues/476. |
| */ |
| thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr; |
| |
| /* XXX: callstack unwind may now throw an error when closing |
| * scopes; this is a sandboxing issue, described in: |
| * https://github.com/svaarala/duktape/issues/476 |
| */ |
| duk_hthread_catchstack_unwind(thr, entry_catchstack_top); |
| duk_hthread_catchstack_shrink_check(thr); |
| duk_hthread_callstack_unwind(thr, entry_callstack_top); |
| duk_hthread_callstack_shrink_check(thr); |
| |
| thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index; |
| tv_ret = thr->valstack_bottom + idx_func; /* XXX: byte offset? */ |
| DUK_TVAL_SET_TVAL_UPDREF(thr, tv_ret, &thr->heap->lj.value1); /* side effects */ |
| #if defined(DUK_USE_FASTINT) |
| /* Explicit check for fastint downgrade. */ |
| DUK_TVAL_CHKFAST_INPLACE(tv_ret); |
| #endif |
| duk_set_top(ctx, idx_func + 1); /* XXX: could be eliminated with valstack adjust */ |
| |
| /* [ ... errobj ] */ |
| |
| /* Ensure there is internal valstack spare before we exit; this may |
| * throw an alloc error. The same guaranteed size must be available |
| * as before the call. This is not optimal now: we store the valstack |
| * allocated size during entry; this value may be higher than the |
| * minimal guarantee for an application. |
| */ |
| |
| /* XXX: this needs to be reworked so that we never shrink the value |
| * stack on function entry so that we never need to grow it here. |
| * Needing to grow here is a sandboxing issue because we need to |
| * allocate which may cause an error in the error handling path |
| * and thus propagate an error out of a protected call. |
| */ |
| |
| (void) duk_valstack_resize_raw((duk_context *) thr, |
| entry_valstack_end, /* same as during entry */ |
| DUK_VSRESIZE_FLAG_SHRINK | /* flags */ |
| DUK_VSRESIZE_FLAG_COMPACT | |
| DUK_VSRESIZE_FLAG_THROW); |
| |
| |
| /* These are just convenience "wiping" of state. Side effects should |
| * not be an issue here: thr->heap and thr->heap->lj have a stable |
| * pointer. Finalizer runs etc capture even out-of-memory errors so |
| * nothing should throw here. |
| */ |
| thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN; |
| thr->heap->lj.iserror = 0; |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value1); /* side effects */ |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value2); /* side effects */ |
| |
| /* Restore entry thread executor curr_pc stack frame pointer. */ |
| thr->ptr_curr_pc = entry_ptr_curr_pc; |
| |
| DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread); /* may be NULL */ |
| thr->state = (duk_uint8_t) entry_thread_state; |
| |
| DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) || /* first call */ |
| (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) || /* other call */ |
| (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr)); /* current thread */ |
| |
| thr->heap->call_recursion_depth = entry_call_recursion_depth; |
| |
| /* If the debugger is active we need to force an interrupt so that |
| * debugger breakpoints are rechecked. This is important for function |
| * calls caused by side effects (e.g. when doing a DUK_OP_GETPROP), see |
| * GH-303. Only needed for success path, error path always causes a |
| * breakpoint recheck in the executor. It would be enough to set this |
| * only when returning to an Ecmascript activation, but setting the flag |
| * on every return should have no ill effect. |
| */ |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) { |
| DUK_DD(DUK_DDPRINT("returning with debugger enabled, force interrupt")); |
| DUK_ASSERT(thr->interrupt_counter <= thr->interrupt_init); |
| thr->interrupt_init -= thr->interrupt_counter; |
| thr->interrupt_counter = 0; |
| thr->heap->dbg_force_restart = 1; |
| } |
| #endif |
| |
| #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG) |
| duk__interrupt_fixup(thr, entry_curr_thread); |
| #endif |
| } |
| |
| /* |
| * duk_handle_safe_call(): make a "C protected call" within the |
| * current activation. |
| * |
| * The allowed thread states for making a call are the same as for |
| * duk_handle_call_xxx(). |
| * |
| * Error handling is similar to duk_handle_call_xxx(); errors may be thrown |
| * (and result in a fatal error) for insane arguments. |
| */ |
| |
| /* XXX: bump preventcount by one for the duration of this call? */ |
| |
| DUK_INTERNAL duk_int_t duk_handle_safe_call(duk_hthread *thr, |
| duk_safe_call_function func, |
| duk_idx_t num_stack_args, |
| duk_idx_t num_stack_rets) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_size_t entry_valstack_bottom_index; |
| duk_size_t entry_callstack_top; |
| duk_size_t entry_catchstack_top; |
| duk_int_t entry_call_recursion_depth; |
| duk_hthread *entry_curr_thread; |
| duk_uint_fast8_t entry_thread_state; |
| duk_instr_t **entry_ptr_curr_pc; |
| duk_jmpbuf *old_jmpbuf_ptr = NULL; |
| duk_jmpbuf our_jmpbuf; |
| duk_idx_t idx_retbase; |
| duk_int_t retval; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(ctx != NULL); |
| |
| /* Note: careful with indices like '-x'; if 'x' is zero, it refers to bottom */ |
| entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack); |
| entry_callstack_top = thr->callstack_top; |
| entry_catchstack_top = thr->catchstack_top; |
| entry_call_recursion_depth = thr->heap->call_recursion_depth; |
| entry_curr_thread = thr->heap->curr_thread; /* Note: may be NULL if first call */ |
| entry_thread_state = thr->state; |
| entry_ptr_curr_pc = thr->ptr_curr_pc; /* may be NULL */ |
| idx_retbase = duk_get_top(ctx) - num_stack_args; /* Note: not a valid stack index if num_stack_args == 0 */ |
| |
| /* Note: cannot portably debug print a function pointer, hence 'func' not printed! */ |
| DUK_DD(DUK_DDPRINT("duk_handle_safe_call: thr=%p, num_stack_args=%ld, num_stack_rets=%ld, " |
| "valstack_top=%ld, idx_retbase=%ld, rec_depth=%ld/%ld, " |
| "entry_valstack_bottom_index=%ld, entry_callstack_top=%ld, entry_catchstack_top=%ld, " |
| "entry_call_recursion_depth=%ld, entry_curr_thread=%p, entry_thread_state=%ld", |
| (void *) thr, |
| (long) num_stack_args, |
| (long) num_stack_rets, |
| (long) duk_get_top(ctx), |
| (long) idx_retbase, |
| (long) thr->heap->call_recursion_depth, |
| (long) thr->heap->call_recursion_limit, |
| (long) entry_valstack_bottom_index, |
| (long) entry_callstack_top, |
| (long) entry_catchstack_top, |
| (long) entry_call_recursion_depth, |
| (void *) entry_curr_thread, |
| (long) entry_thread_state)); |
| |
| if (idx_retbase < 0) { |
| /* Since stack indices are not reliable, we can't do anything useful |
| * here. Invoke the existing setjmp catcher, or if it doesn't exist, |
| * call the fatal error handler. |
| */ |
| |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| } |
| |
| /* setjmp catchpoint setup */ |
| |
| old_jmpbuf_ptr = thr->heap->lj.jmpbuf_ptr; |
| thr->heap->lj.jmpbuf_ptr = &our_jmpbuf; |
| |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| try { |
| #else |
| DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == &our_jmpbuf); |
| if (DUK_SETJMP(our_jmpbuf.jb) == 0) { |
| /* Success path. */ |
| #endif |
| DUK_DDD(DUK_DDDPRINT("safe_call setjmp catchpoint setup complete")); |
| |
| duk__handle_safe_call_inner(thr, |
| func, |
| idx_retbase, |
| num_stack_rets, |
| entry_valstack_bottom_index, |
| entry_callstack_top, |
| entry_catchstack_top); |
| |
| /* Longjmp state is kept clean in success path */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN); |
| DUK_ASSERT(thr->heap->lj.iserror == 0); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1)); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); |
| |
| /* Note: either pointer may be NULL (at entry), so don't assert */ |
| thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr; |
| |
| retval = DUK_EXEC_SUCCESS; |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| } catch (duk_internal_exception &exc) { |
| DUK_UNREF(exc); |
| #else |
| } else { |
| /* Error path. */ |
| #endif |
| duk__handle_safe_call_error(thr, |
| idx_retbase, |
| num_stack_rets, |
| entry_valstack_bottom_index, |
| entry_callstack_top, |
| entry_catchstack_top, |
| old_jmpbuf_ptr); |
| |
| /* Longjmp state is cleaned up by error handling */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_UNKNOWN); |
| DUK_ASSERT(thr->heap->lj.iserror == 0); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value1)); |
| DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(&thr->heap->lj.value2)); |
| |
| retval = DUK_EXEC_ERROR; |
| } |
| #if defined(DUK_USE_CPP_EXCEPTIONS) |
| catch (std::exception &exc) { |
| const char *what = exc.what(); |
| if (!what) { |
| what = "unknown"; |
| } |
| DUK_D(DUK_DPRINT("unexpected c++ std::exception (perhaps thrown by user code)")); |
| try { |
| DUK_ERROR_FMT1(thr, DUK_ERR_API_ERROR, "caught invalid c++ std::exception '%s' (perhaps thrown by user code)", what); |
| } catch (duk_internal_exception exc) { |
| DUK_D(DUK_DPRINT("caught api error thrown from unexpected c++ std::exception")); |
| DUK_UNREF(exc); |
| duk__handle_safe_call_error(thr, |
| idx_retbase, |
| num_stack_rets, |
| entry_valstack_bottom_index, |
| entry_callstack_top, |
| entry_catchstack_top, |
| old_jmpbuf_ptr); |
| retval = DUK_EXEC_ERROR; |
| } |
| } catch (...) { |
| DUK_D(DUK_DPRINT("unexpected c++ exception (perhaps thrown by user code)")); |
| try { |
| DUK_ERROR_API(thr, "caught invalid c++ exception (perhaps thrown by user code)"); |
| } catch (duk_internal_exception exc) { |
| DUK_D(DUK_DPRINT("caught api error thrown from unexpected c++ exception")); |
| DUK_UNREF(exc); |
| duk__handle_safe_call_error(thr, |
| idx_retbase, |
| num_stack_rets, |
| entry_valstack_bottom_index, |
| entry_callstack_top, |
| entry_catchstack_top, |
| old_jmpbuf_ptr); |
| retval = DUK_EXEC_ERROR; |
| } |
| } |
| #endif |
| |
| DUK_ASSERT(thr->heap->lj.jmpbuf_ptr == old_jmpbuf_ptr); /* success/error path both do this */ |
| |
| duk__handle_safe_call_shared(thr, |
| idx_retbase, |
| num_stack_rets, |
| entry_call_recursion_depth, |
| entry_curr_thread, |
| entry_thread_state, |
| entry_ptr_curr_pc); |
| |
| return retval; |
| } |
| |
| DUK_LOCAL void duk__handle_safe_call_inner(duk_hthread *thr, |
| duk_safe_call_function func, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_callstack_top, |
| duk_size_t entry_catchstack_top) { |
| duk_context *ctx; |
| duk_ret_t rc; |
| |
| DUK_ASSERT(thr != NULL); |
| ctx = (duk_context *) thr; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(entry_valstack_bottom_index); |
| DUK_UNREF(entry_callstack_top); |
| DUK_UNREF(entry_catchstack_top); |
| |
| /* |
| * Thread state check and book-keeping. |
| */ |
| |
| if (thr == thr->heap->curr_thread) { |
| /* same thread */ |
| if (thr->state != DUK_HTHREAD_STATE_RUNNING) { |
| /* should actually never happen, but check anyway */ |
| goto thread_state_error; |
| } |
| } else { |
| /* different thread */ |
| DUK_ASSERT(thr->heap->curr_thread == NULL || |
| thr->heap->curr_thread->state == DUK_HTHREAD_STATE_RUNNING); |
| if (thr->state != DUK_HTHREAD_STATE_INACTIVE) { |
| goto thread_state_error; |
| } |
| DUK_HEAP_SWITCH_THREAD(thr->heap, thr); |
| thr->state = DUK_HTHREAD_STATE_RUNNING; |
| |
| /* Note: multiple threads may be simultaneously in the RUNNING |
| * state, but not in the same "resume chain". |
| */ |
| } |
| |
| DUK_ASSERT(thr->heap->curr_thread == thr); |
| DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); |
| |
| /* |
| * Recursion limit check. |
| * |
| * Note: there is no need for an "ignore recursion limit" flag |
| * for duk_handle_safe_call now. |
| */ |
| |
| DUK_ASSERT(thr->heap->call_recursion_depth >= 0); |
| DUK_ASSERT(thr->heap->call_recursion_depth <= thr->heap->call_recursion_limit); |
| if (thr->heap->call_recursion_depth >= thr->heap->call_recursion_limit) { |
| /* XXX: error message is a bit misleading: we reached a recursion |
| * limit which is also essentially the same as a C callstack limit |
| * (except perhaps with some relaxed threading assumptions). |
| */ |
| DUK_ERROR_RANGE(thr, DUK_STR_C_CALLSTACK_LIMIT); |
| } |
| thr->heap->call_recursion_depth++; |
| |
| /* |
| * Valstack spare check |
| */ |
| |
| duk_require_stack(ctx, 0); /* internal spare */ |
| |
| /* |
| * Make the C call |
| */ |
| |
| rc = func(ctx); |
| |
| DUK_DDD(DUK_DDDPRINT("safe_call, func rc=%ld", (long) rc)); |
| |
| /* |
| * Valstack manipulation for results. |
| */ |
| |
| /* we're running inside the caller's activation, so no change in call/catch stack or valstack bottom */ |
| DUK_ASSERT(thr->callstack_top == entry_callstack_top); |
| DUK_ASSERT(thr->catchstack_top == entry_catchstack_top); |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT((duk_size_t) (thr->valstack_bottom - thr->valstack) == entry_valstack_bottom_index); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| if (rc < 0) { |
| duk_error_throw_from_negative_rc(thr, rc); |
| } |
| DUK_ASSERT(rc >= 0); |
| |
| if (duk_get_top(ctx) < rc) { |
| DUK_ERROR_API(thr, "not enough stack values for safe_call rc"); |
| } |
| |
| DUK_ASSERT(thr->catchstack_top == entry_catchstack_top); /* no need to unwind */ |
| DUK_ASSERT(thr->callstack_top == entry_callstack_top); |
| |
| duk__safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, rc); |
| return; |
| |
| thread_state_error: |
| DUK_ERROR_FMT1(thr, DUK_ERR_TYPE_ERROR, "invalid thread state for safe_call (%ld)", (long) thr->state); |
| DUK_UNREACHABLE(); |
| } |
| |
| DUK_LOCAL void duk__handle_safe_call_error(duk_hthread *thr, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_size_t entry_valstack_bottom_index, |
| duk_size_t entry_callstack_top, |
| duk_size_t entry_catchstack_top, |
| duk_jmpbuf *old_jmpbuf_ptr) { |
| duk_context *ctx; |
| |
| DUK_ASSERT(thr != NULL); |
| ctx = (duk_context *) thr; |
| DUK_ASSERT_CTX_VALID(ctx); |
| |
| /* |
| * Error during call. The error value is at heap->lj.value1. |
| * |
| * The very first thing we do is restore the previous setjmp catcher. |
| * This means that any error in error handling will propagate outwards |
| * instead of causing a setjmp() re-entry above. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("error caught during protected duk_handle_safe_call()")); |
| |
| /* Other longjmp types are handled by executor before propagating |
| * the error here. |
| */ |
| DUK_ASSERT(thr->heap->lj.type == DUK_LJ_TYPE_THROW); |
| DUK_ASSERT(thr->callstack_top >= entry_callstack_top); |
| DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top); |
| |
| /* Note: either pointer may be NULL (at entry), so don't assert. */ |
| thr->heap->lj.jmpbuf_ptr = old_jmpbuf_ptr; |
| |
| DUK_ASSERT(thr->catchstack_top >= entry_catchstack_top); |
| DUK_ASSERT(thr->callstack_top >= entry_callstack_top); |
| duk_hthread_catchstack_unwind(thr, entry_catchstack_top); |
| duk_hthread_catchstack_shrink_check(thr); |
| duk_hthread_callstack_unwind(thr, entry_callstack_top); |
| duk_hthread_callstack_shrink_check(thr); |
| thr->valstack_bottom = thr->valstack + entry_valstack_bottom_index; |
| |
| /* [ ... | (crud) ] */ |
| |
| /* XXX: space in valstack? see discussion in duk_handle_call_xxx(). */ |
| duk_push_tval(ctx, &thr->heap->lj.value1); |
| |
| /* [ ... | (crud) errobj ] */ |
| |
| DUK_ASSERT(duk_get_top(ctx) >= 1); /* at least errobj must be on stack */ |
| |
| /* check that the valstack has space for the final amount and any |
| * intermediate space needed; this is unoptimal but should be safe |
| */ |
| duk_require_stack_top(ctx, idx_retbase + num_stack_rets); /* final configuration */ |
| duk_require_stack(ctx, num_stack_rets); |
| |
| duk__safe_call_adjust_valstack(thr, idx_retbase, num_stack_rets, 1); /* 1 = num actual 'return values' */ |
| |
| /* [ ... | ] or [ ... | errobj (M * undefined)] where M = num_stack_rets - 1 */ |
| |
| /* These are just convenience "wiping" of state. Side effects should |
| * not be an issue here: thr->heap and thr->heap->lj have a stable |
| * pointer. Finalizer runs etc capture even out-of-memory errors so |
| * nothing should throw here. |
| */ |
| thr->heap->lj.type = DUK_LJ_TYPE_UNKNOWN; |
| thr->heap->lj.iserror = 0; |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value1); /* side effects */ |
| DUK_TVAL_SET_UNDEFINED_UPDREF(thr, &thr->heap->lj.value2); /* side effects */ |
| } |
| |
| DUK_LOCAL void duk__handle_safe_call_shared(duk_hthread *thr, |
| duk_idx_t idx_retbase, |
| duk_idx_t num_stack_rets, |
| duk_int_t entry_call_recursion_depth, |
| duk_hthread *entry_curr_thread, |
| duk_uint_fast8_t entry_thread_state, |
| duk_instr_t **entry_ptr_curr_pc) { |
| duk_context *ctx; |
| |
| DUK_ASSERT(thr != NULL); |
| ctx = (duk_context *) thr; |
| DUK_ASSERT_CTX_VALID(ctx); |
| DUK_UNREF(ctx); |
| DUK_UNREF(idx_retbase); |
| DUK_UNREF(num_stack_rets); |
| |
| /* Restore entry thread executor curr_pc stack frame pointer. */ |
| thr->ptr_curr_pc = entry_ptr_curr_pc; |
| |
| /* XXX: because we unwind stacks above, thr->heap->curr_thread is at |
| * risk of pointing to an already freed thread. This was indeed the |
| * case in test-bug-multithread-valgrind.c, until duk_handle_call() |
| * was fixed to restore thr->heap->curr_thread before rethrowing an |
| * uncaught error. |
| */ |
| DUK_HEAP_SWITCH_THREAD(thr->heap, entry_curr_thread); /* may be NULL */ |
| thr->state = (duk_uint8_t) entry_thread_state; |
| |
| DUK_ASSERT((thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread == NULL) || /* first call */ |
| (thr->state == DUK_HTHREAD_STATE_INACTIVE && thr->heap->curr_thread != NULL) || /* other call */ |
| (thr->state == DUK_HTHREAD_STATE_RUNNING && thr->heap->curr_thread == thr)); /* current thread */ |
| |
| thr->heap->call_recursion_depth = entry_call_recursion_depth; |
| |
| /* stack discipline consistency check */ |
| DUK_ASSERT(duk_get_top(ctx) == idx_retbase + num_stack_rets); |
| |
| /* A debugger forced interrupt check is not needed here, as |
| * problematic safe calls are not caused by side effects. |
| */ |
| |
| #if defined(DUK_USE_INTERRUPT_COUNTER) && defined(DUK_USE_DEBUG) |
| duk__interrupt_fixup(thr, entry_curr_thread); |
| #endif |
| } |
| |
| /* |
| * Helper for handling an Ecmascript-to-Ecmascript call or an Ecmascript |
| * function (initial) Duktape.Thread.resume(). |
| * |
| * Compared to normal calls handled by duk_handle_call(), there are a |
| * bunch of differences: |
| * |
| * - the call is never protected |
| * - there is no C recursion depth increase (hence an "ignore recursion |
| * limit" flag is not applicable) |
| * - instead of making the call, this helper just performs the thread |
| * setup and returns; the bytecode executor then restarts execution |
| * internally |
| * - ecmascript functions are never 'vararg' functions (they access |
| * varargs through the 'arguments' object) |
| * |
| * The callstack of the target contains an earlier Ecmascript call in case |
| * of an Ecmascript-to-Ecmascript call (whose idx_retval is updated), or |
| * is empty in case of an initial Duktape.Thread.resume(). |
| * |
| * The first thing to do here is to figure out whether an ecma-to-ecma |
| * call is actually possible. It's not always the case if the target is |
| * a bound function; the final function may be native. In that case, |
| * return an error so caller can fall back to a normal call path. |
| */ |
| |
| DUK_INTERNAL duk_bool_t duk_handle_ecma_call_setup(duk_hthread *thr, |
| duk_idx_t num_stack_args, |
| duk_small_uint_t call_flags) { |
| duk_context *ctx = (duk_context *) thr; |
| duk_size_t entry_valstack_bottom_index; |
| duk_idx_t idx_func; /* valstack index of 'func' and retval (relative to entry valstack_bottom) */ |
| duk_idx_t idx_args; /* valstack index of start of args (arg1) (relative to entry valstack_bottom) */ |
| duk_idx_t nargs; /* # argument registers target function wants (< 0 => never for ecma calls) */ |
| duk_idx_t nregs; /* # total registers target function wants on entry (< 0 => never for ecma calls) */ |
| duk_hobject *func; /* 'func' on stack (borrowed reference) */ |
| duk_tval *tv_func; /* duk_tval ptr for 'func' on stack (borrowed reference) */ |
| duk_activation *act; |
| duk_hobject *env; |
| duk_bool_t use_tailcall; |
| duk_instr_t **entry_ptr_curr_pc; |
| |
| DUK_ASSERT(thr != NULL); |
| DUK_ASSERT(ctx != NULL); |
| DUK_ASSERT(!((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 && (call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0)); |
| |
| /* XXX: assume these? */ |
| DUK_ASSERT(thr->valstack != NULL); |
| DUK_ASSERT(thr->callstack != NULL); |
| DUK_ASSERT(thr->catchstack != NULL); |
| |
| /* no need to handle thread state book-keeping here */ |
| DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 || |
| (thr->state == DUK_HTHREAD_STATE_RUNNING && |
| thr->heap->curr_thread == thr)); |
| |
| /* If thr->ptr_curr_pc is set, sync curr_pc to act->pc. Then NULL |
| * thr->ptr_curr_pc so that it's not accidentally used with an incorrect |
| * activation when side effects occur. If we end up not making the |
| * call we must restore the value. |
| */ |
| entry_ptr_curr_pc = thr->ptr_curr_pc; |
| duk_hthread_sync_and_null_currpc(thr); |
| |
| /* if a tail call: |
| * - an Ecmascript activation must be on top of the callstack |
| * - there cannot be any active catchstack entries |
| */ |
| #if defined(DUK_USE_ASSERTIONS) |
| if (call_flags & DUK_CALL_FLAG_IS_TAILCALL) { |
| duk_size_t our_callstack_index; |
| duk_size_t i; |
| |
| DUK_ASSERT(thr->callstack_top >= 1); |
| our_callstack_index = thr->callstack_top - 1; |
| DUK_ASSERT_DISABLE(our_callstack_index >= 0); |
| DUK_ASSERT(our_callstack_index < thr->callstack_size); |
| DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack + our_callstack_index) != NULL); |
| DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(thr->callstack + our_callstack_index))); |
| |
| /* No entry in the catchstack which would actually catch a |
| * throw can refer to the callstack entry being reused. |
| * There *can* be catchstack entries referring to the current |
| * callstack entry as long as they don't catch (e.g. label sites). |
| */ |
| |
| for (i = 0; i < thr->catchstack_top; i++) { |
| DUK_ASSERT(thr->catchstack[i].callstack_index < our_callstack_index || /* refer to callstack entries below current */ |
| DUK_CAT_GET_TYPE(thr->catchstack + i) == DUK_CAT_TYPE_LABEL); /* or a non-catching entry */ |
| } |
| } |
| #endif /* DUK_USE_ASSERTIONS */ |
| |
| entry_valstack_bottom_index = (duk_size_t) (thr->valstack_bottom - thr->valstack); |
| /* XXX: rework */ |
| idx_func = duk_normalize_index(thr, -num_stack_args - 2); |
| idx_args = idx_func + 2; |
| |
| DUK_DD(DUK_DDPRINT("handle_ecma_call_setup: thr=%p, " |
| "num_stack_args=%ld, call_flags=0x%08lx (resume=%ld, tailcall=%ld), " |
| "idx_func=%ld, idx_args=%ld, entry_valstack_bottom_index=%ld", |
| (void *) thr, |
| (long) num_stack_args, |
| (unsigned long) call_flags, |
| (long) ((call_flags & DUK_CALL_FLAG_IS_RESUME) != 0 ? 1 : 0), |
| (long) ((call_flags & DUK_CALL_FLAG_IS_TAILCALL) != 0 ? 1 : 0), |
| (long) idx_func, |
| (long) idx_args, |
| (long) entry_valstack_bottom_index)); |
| |
| if (DUK_UNLIKELY(idx_func < 0 || idx_args < 0)) { |
| /* XXX: assert? compiler is responsible for this never happening */ |
| DUK_ERROR_API(thr, DUK_STR_INVALID_CALL_ARGS); |
| } |
| |
| /* |
| * Check the function type, handle bound function chains, and prepare |
| * parameters for the rest of the call handling. Also figure out the |
| * effective 'this' binding, which replaces the current value at |
| * idx_func + 1. |
| * |
| * If the target function is a 'bound' one, follow the chain of 'bound' |
| * functions until a non-bound function is found. During this process, |
| * bound arguments are 'prepended' to existing ones, and the "this" |
| * binding is overridden. See E5 Section 15.3.4.5.1. |
| * |
| * If the final target function cannot be handled by an ecma-to-ecma |
| * call, return to the caller with a return value indicating this case. |
| * The bound chain is resolved and the caller can resume with a plain |
| * function call. |
| */ |
| |
| func = duk__nonbound_func_lookup(ctx, idx_func, &num_stack_args, &tv_func, call_flags); |
| if (func == NULL || !DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { |
| DUK_DDD(DUK_DDDPRINT("final target is a lightfunc/nativefunc, cannot do ecma-to-ecma call")); |
| thr->ptr_curr_pc = entry_ptr_curr_pc; |
| return 0; |
| } |
| /* XXX: tv_func is not actually needed */ |
| |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(func)); |
| |
| duk__coerce_effective_this_binding(thr, func, idx_func + 1); |
| DUK_DDD(DUK_DDDPRINT("effective 'this' binding is: %!T", |
| duk_get_tval(ctx, idx_func + 1))); |
| |
| nargs = ((duk_hcompiledfunction *) func)->nargs; |
| nregs = ((duk_hcompiledfunction *) func)->nregs; |
| DUK_ASSERT(nregs >= nargs); |
| |
| /* [ ... func this arg1 ... argN ] */ |
| |
| /* |
| * Preliminary activation record and valstack manipulation. |
| * The concrete actions depend on whether the we're dealing |
| * with a tail call (reuse an existing activation), a resume, |
| * or a normal call. |
| * |
| * The basic actions, in varying order, are: |
| * |
| * - Check stack size for call handling |
| * - Grow call stack if necessary (non-tail-calls) |
| * - Update current activation (idx_retval) if necessary |
| * (non-tail, non-resume calls) |
| * - Move start of args (idx_args) to valstack bottom |
| * (tail calls) |
| * |
| * Don't touch valstack_bottom or valstack_top yet so that Duktape API |
| * calls work normally. |
| */ |
| |
| /* XXX: some overlapping code; cleanup */ |
| use_tailcall = call_flags & DUK_CALL_FLAG_IS_TAILCALL; |
| #if !defined(DUK_USE_TAILCALL) |
| DUK_ASSERT(use_tailcall == 0); /* compiler ensures this */ |
| #endif |
| if (use_tailcall) { |
| /* tailcall cannot be flagged to resume calls, and a |
| * previous frame must exist |
| */ |
| DUK_ASSERT(thr->callstack_top >= 1); |
| DUK_ASSERT((call_flags & DUK_CALL_FLAG_IS_RESUME) == 0); |
| |
| act = thr->callstack + thr->callstack_top - 1; |
| if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) { |
| /* See: test-bug-tailcall-preventyield-assert.c. */ |
| DUK_DDD(DUK_DDDPRINT("tail call prevented by current activation having DUK_ACT_FLAG_PREVENTYIELD")); |
| use_tailcall = 0; |
| } else if (DUK_HOBJECT_HAS_NOTAIL(func)) { |
| DUK_D(DUK_DPRINT("tail call prevented by function having a notail flag")); |
| use_tailcall = 0; |
| } |
| } |
| |
| if (use_tailcall) { |
| duk_tval *tv1, *tv2; |
| duk_size_t cs_index; |
| duk_int_t i_stk; /* must be signed for loop structure */ |
| duk_idx_t i_arg; |
| |
| /* |
| * Tailcall handling |
| * |
| * Although the callstack entry is reused, we need to explicitly unwind |
| * the current activation (or simulate an unwind). In particular, the |
| * current activation must be closed, otherwise something like |
| * test-bug-reduce-judofyr.js results. Also catchstack needs be unwound |
| * because there may be non-error-catching label entries in valid tail calls. |
| */ |
| |
| DUK_DDD(DUK_DDDPRINT("is tail call, reusing activation at callstack top, at index %ld", |
| (long) (thr->callstack_top - 1))); |
| |
| /* 'act' already set above */ |
| |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func)); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func)); |
| DUK_ASSERT((act->flags & DUK_ACT_FLAG_PREVENT_YIELD) == 0); |
| |
| /* Unwind catchstack entries referring to the callstack entry we're reusing */ |
| cs_index = thr->callstack_top - 1; |
| DUK_ASSERT(thr->catchstack_top <= DUK_INT_MAX); /* catchstack limits */ |
| for (i_stk = (duk_int_t) (thr->catchstack_top - 1); i_stk >= 0; i_stk--) { |
| duk_catcher *cat = thr->catchstack + i_stk; |
| if (cat->callstack_index != cs_index) { |
| /* 'i' is the first entry we'll keep */ |
| break; |
| } |
| } |
| duk_hthread_catchstack_unwind(thr, i_stk + 1); |
| |
| /* Unwind the topmost callstack entry before reusing it */ |
| DUK_ASSERT(thr->callstack_top > 0); |
| duk_hthread_callstack_unwind(thr, thr->callstack_top - 1); |
| |
| /* Then reuse the unwound activation; callstack was not shrunk so there is always space */ |
| thr->callstack_top++; |
| DUK_ASSERT(thr->callstack_top <= thr->callstack_size); |
| act = thr->callstack + thr->callstack_top - 1; |
| |
| /* Start filling in the activation */ |
| act->func = func; /* don't want an intermediate exposed state with func == NULL */ |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| act->prev_caller = NULL; |
| #endif |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func)); |
| /* don't want an intermediate exposed state with invalid pc */ |
| act->curr_pc = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(thr->heap, (duk_hcompiledfunction *) func); |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| act->prev_line = 0; |
| #endif |
| DUK_TVAL_SET_OBJECT(&act->tv_func, func); /* borrowed, no refcount */ |
| #if defined(DUK_USE_REFERENCE_COUNTING) |
| DUK_HOBJECT_INCREF(thr, func); |
| act = thr->callstack + thr->callstack_top - 1; /* side effects (currently none though) */ |
| #endif |
| |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| #if defined(DUK_USE_TAILCALL) |
| #error incorrect options: tail calls enabled with function caller property |
| #endif |
| /* XXX: this doesn't actually work properly for tail calls, so |
| * tail calls are disabled when DUK_USE_NONSTD_FUNC_CALLER_PROPERTY |
| * is in use. |
| */ |
| duk__update_func_caller_prop(thr, func); |
| act = thr->callstack + thr->callstack_top - 1; |
| #endif |
| |
| act->flags = (DUK_HOBJECT_HAS_STRICT(func) ? |
| DUK_ACT_FLAG_STRICT | DUK_ACT_FLAG_TAILCALLED : |
| DUK_ACT_FLAG_TAILCALLED); |
| |
| DUK_ASSERT(DUK_ACT_GET_FUNC(act) == func); /* already updated */ |
| DUK_ASSERT(act->var_env == NULL); /* already NULLed (by unwind) */ |
| DUK_ASSERT(act->lex_env == NULL); /* already NULLed (by unwind) */ |
| act->idx_bottom = entry_valstack_bottom_index; /* tail call -> reuse current "frame" */ |
| DUK_ASSERT(nregs >= 0); |
| #if 0 /* topmost activation idx_retval is considered garbage, no need to init */ |
| act->idx_retval = 0; |
| #endif |
| |
| /* |
| * Manipulate valstack so that args are on the current bottom and the |
| * previous caller's 'this' binding (which is the value preceding the |
| * current bottom) is replaced with the new 'this' binding: |
| * |
| * [ ... this_old | (crud) func this_new arg1 ... argN ] |
| * --> [ ... this_new | arg1 ... argN ] |
| * |
| * For tail calling to work properly, the valstack bottom must not grow |
| * here; otherwise crud would accumulate on the valstack. |
| */ |
| |
| tv1 = thr->valstack_bottom - 1; |
| tv2 = thr->valstack_bottom + idx_func + 1; |
| DUK_ASSERT(tv1 >= thr->valstack && tv1 < thr->valstack_top); /* tv1 is -below- valstack_bottom */ |
| DUK_ASSERT(tv2 >= thr->valstack_bottom && tv2 < thr->valstack_top); |
| DUK_TVAL_SET_TVAL_UPDREF(thr, tv1, tv2); /* side effects */ |
| |
| for (i_arg = 0; i_arg < idx_args; i_arg++) { |
| /* XXX: block removal API primitive */ |
| /* Note: 'func' is popped from valstack here, but it is |
| * already reachable from the activation. |
| */ |
| duk_remove(ctx, 0); |
| } |
| idx_func = 0; DUK_UNREF(idx_func); /* really 'not applicable' anymore, should not be referenced after this */ |
| idx_args = 0; |
| |
| /* [ ... this_new | arg1 ... argN ] */ |
| } else { |
| DUK_DDD(DUK_DDDPRINT("not a tail call, pushing a new activation to callstack, to index %ld", |
| (long) (thr->callstack_top))); |
| |
| duk_hthread_callstack_grow(thr); |
| |
| if (call_flags & DUK_CALL_FLAG_IS_RESUME) { |
| DUK_DDD(DUK_DDDPRINT("is resume -> no update to current activation (may not even exist)")); |
| } else { |
| DUK_DDD(DUK_DDDPRINT("update to current activation idx_retval")); |
| DUK_ASSERT(thr->callstack_top < thr->callstack_size); |
| DUK_ASSERT(thr->callstack_top >= 1); |
| act = thr->callstack + thr->callstack_top - 1; |
| DUK_ASSERT(DUK_ACT_GET_FUNC(act) != NULL); |
| DUK_ASSERT(DUK_HOBJECT_IS_COMPILEDFUNCTION(DUK_ACT_GET_FUNC(act))); |
| act->idx_retval = entry_valstack_bottom_index + idx_func; |
| } |
| |
| DUK_ASSERT(thr->callstack_top < thr->callstack_size); |
| act = thr->callstack + thr->callstack_top; |
| thr->callstack_top++; |
| DUK_ASSERT(thr->callstack_top <= thr->callstack_size); |
| |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); |
| DUK_ASSERT(!DUK_HOBJECT_HAS_NATIVEFUNCTION(func)); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func)); |
| |
| act->flags = (DUK_HOBJECT_HAS_STRICT(func) ? |
| DUK_ACT_FLAG_STRICT : |
| 0); |
| act->func = func; |
| act->var_env = NULL; |
| act->lex_env = NULL; |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| act->prev_caller = NULL; |
| #endif |
| DUK_ASSERT(func != NULL); |
| DUK_ASSERT(DUK_HOBJECT_HAS_COMPILEDFUNCTION(func)); |
| act->curr_pc = DUK_HCOMPILEDFUNCTION_GET_CODE_BASE(thr->heap, (duk_hcompiledfunction *) func); |
| #if defined(DUK_USE_DEBUGGER_SUPPORT) |
| act->prev_line = 0; |
| #endif |
| act->idx_bottom = entry_valstack_bottom_index + idx_args; |
| DUK_ASSERT(nregs >= 0); |
| #if 0 /* topmost activation idx_retval is considered garbage, no need to init */ |
| act->idx_retval = 0; |
| #endif |
| DUK_TVAL_SET_OBJECT(&act->tv_func, func); /* borrowed, no refcount */ |
| |
| DUK_HOBJECT_INCREF(thr, func); /* act->func */ |
| |
| #if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY) |
| duk__update_func_caller_prop(thr, func); |
| act = thr->callstack + thr->callstack_top - 1; |
| #endif |
| } |
| |
| /* [ ... func this arg1 ... argN ] (not tail call) |
| * [ this | arg1 ... argN ] (tail call) |
| * |
| * idx_args updated to match |
| */ |
| |
| /* |
| * Environment record creation and 'arguments' object creation. |
| * Named function expression name binding is handled by the |
| * compiler; the compiled function's parent env will contain |
| * the (immutable) binding already. |
| * |
| * Delayed creation (on demand) is handled in duk_js_var.c. |
| */ |
| |
| /* XXX: unify handling with native call. */ |
| |
| DUK_ASSERT(!DUK_HOBJECT_HAS_BOUND(func)); /* bound function chain has already been resolved */ |
| |
| if (!DUK_HOBJECT_HAS_NEWENV(func)) { |
| /* use existing env (e.g. for non-strict eval); cannot have |
| * an own 'arguments' object (but can refer to the existing one) |
| */ |
| |
| duk__handle_oldenv_for_call(thr, func, act); |
| |
| DUK_ASSERT(act->lex_env != NULL); |
| DUK_ASSERT(act->var_env != NULL); |
| goto env_done; |
| } |
| |
| DUK_ASSERT(DUK_HOBJECT_HAS_NEWENV(func)); |
| |
| if (!DUK_HOBJECT_HAS_CREATEARGS(func)) { |
| /* no need to create environment record now; leave as NULL */ |
| DUK_ASSERT(act->lex_env == NULL); |
| DUK_ASSERT(act->var_env == NULL); |
| goto env_done; |
| } |
| |
| /* third arg: absolute index (to entire valstack) of idx_bottom of new activation */ |
| env = duk_create_activation_environment_record(thr, func, act->idx_bottom); |
| DUK_ASSERT(env != NULL); |
| |
| /* [ ... arg1 ... argN envobj ] */ |
| |
| /* original input stack before nargs/nregs handling must be |
| * intact for 'arguments' object |
| */ |
| DUK_ASSERT(DUK_HOBJECT_HAS_CREATEARGS(func)); |
| duk__handle_createargs_for_call(thr, func, env, num_stack_args); |
| |
| /* [ ... arg1 ... argN envobj ] */ |
| |
| act = thr->callstack + thr->callstack_top - 1; |
| act->lex_env = env; |
| act->var_env = env; |
| DUK_HOBJECT_INCREF(thr, act->lex_env); |
| DUK_HOBJECT_INCREF(thr, act->var_env); |
| duk_pop(ctx); |
| |
| env_done: |
| /* [ ... arg1 ... argN ] */ |
| |
| /* |
| * Setup value stack: clamp to 'nargs', fill up to 'nregs' |
| */ |
| |
| duk__adjust_valstack_and_top(thr, |
| num_stack_args, |
| idx_args, |
| nregs, |
| nargs, |
| func); |
| |
| /* |
| * Shift to new valstack_bottom. |
| */ |
| |
| thr->valstack_bottom = thr->valstack_bottom + idx_args; |
| /* keep current valstack_top */ |
| DUK_ASSERT(thr->valstack_bottom >= thr->valstack); |
| DUK_ASSERT(thr->valstack_top >= thr->valstack_bottom); |
| DUK_ASSERT(thr->valstack_end >= thr->valstack_top); |
| |
| /* |
| * Return to bytecode executor, which will resume execution from |
| * the topmost activation. |
| */ |
| |
| return 1; |
| } |