407 lines
12 KiB
C
407 lines
12 KiB
C
/*
|
|
* Thread stack (mainly call stack) primitives: allocation of activations,
|
|
* unwinding catchers and activations, etc.
|
|
*
|
|
* Value stack handling is a part of the API implementation.
|
|
*/
|
|
|
|
#include "duk_internal.h"
|
|
|
|
/* Unwind the topmost catcher of the current activation (caller must check that
|
|
* both exist) without side effects.
|
|
*/
|
|
DUK_INTERNAL void duk_hthread_catcher_unwind_norz(duk_hthread *thr, duk_activation *act) {
|
|
duk_catcher *cat;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(act != NULL);
|
|
DUK_ASSERT(act->cat != NULL); /* caller must check */
|
|
cat = act->cat;
|
|
DUK_ASSERT(cat != NULL);
|
|
|
|
DUK_DDD(DUK_DDDPRINT("unwinding catch stack entry %p (lexenv check is done)", (void *) cat));
|
|
|
|
if (DUK_CAT_HAS_LEXENV_ACTIVE(cat)) {
|
|
duk_hobject *env;
|
|
|
|
env = act->lex_env; /* current lex_env of the activation (created for catcher) */
|
|
DUK_ASSERT(env != NULL); /* must be, since env was created when catcher was created */
|
|
act->lex_env = DUK_HOBJECT_GET_PROTOTYPE(thr->heap, env); /* prototype is lex_env before catcher created */
|
|
DUK_HOBJECT_INCREF(thr, act->lex_env);
|
|
DUK_HOBJECT_DECREF_NORZ(thr, env);
|
|
|
|
/* There is no need to decref anything else than 'env': if 'env'
|
|
* becomes unreachable, refzero will handle decref'ing its prototype.
|
|
*/
|
|
}
|
|
|
|
act->cat = cat->parent;
|
|
duk_hthread_catcher_free(thr, cat);
|
|
}
|
|
|
|
/* Same as above, but caller is certain no catcher-related lexenv may exist. */
|
|
DUK_INTERNAL void duk_hthread_catcher_unwind_nolexenv_norz(duk_hthread *thr, duk_activation *act) {
|
|
duk_catcher *cat;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(act != NULL);
|
|
DUK_ASSERT(act->cat != NULL); /* caller must check */
|
|
cat = act->cat;
|
|
DUK_ASSERT(cat != NULL);
|
|
|
|
DUK_DDD(DUK_DDDPRINT("unwinding catch stack entry %p (lexenv check is not done)", (void *) cat));
|
|
|
|
DUK_ASSERT(!DUK_CAT_HAS_LEXENV_ACTIVE(cat));
|
|
|
|
act->cat = cat->parent;
|
|
duk_hthread_catcher_free(thr, cat);
|
|
}
|
|
|
|
DUK_LOCAL
|
|
#if defined(DUK_USE_CACHE_CATCHER)
|
|
DUK_NOINLINE
|
|
#endif
|
|
duk_catcher *duk__hthread_catcher_alloc_slow(duk_hthread *thr) {
|
|
duk_catcher *cat;
|
|
|
|
cat = (duk_catcher *) DUK_ALLOC_CHECKED(thr, sizeof(duk_catcher));
|
|
DUK_ASSERT(cat != NULL);
|
|
return cat;
|
|
}
|
|
|
|
#if defined(DUK_USE_CACHE_CATCHER)
|
|
DUK_INTERNAL DUK_INLINE duk_catcher *duk_hthread_catcher_alloc(duk_hthread *thr) {
|
|
duk_catcher *cat;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
cat = thr->heap->catcher_free;
|
|
if (DUK_LIKELY(cat != NULL)) {
|
|
thr->heap->catcher_free = cat->parent;
|
|
return cat;
|
|
}
|
|
|
|
return duk__hthread_catcher_alloc_slow(thr);
|
|
}
|
|
#else /* DUK_USE_CACHE_CATCHER */
|
|
DUK_INTERNAL duk_catcher *duk_hthread_catcher_alloc(duk_hthread *thr) {
|
|
return duk__hthread_catcher_alloc_slow(thr);
|
|
}
|
|
#endif /* DUK_USE_CACHE_CATCHER */
|
|
|
|
DUK_INTERNAL void duk_hthread_catcher_free(duk_hthread *thr, duk_catcher *cat) {
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(cat != NULL);
|
|
|
|
#if defined(DUK_USE_CACHE_CATCHER)
|
|
/* Unconditional caching for now; freed in mark-and-sweep. */
|
|
cat->parent = thr->heap->catcher_free;
|
|
thr->heap->catcher_free = cat;
|
|
#else
|
|
DUK_FREE_CHECKED(thr, (void *) cat);
|
|
#endif
|
|
}
|
|
|
|
DUK_LOCAL
|
|
#if defined(DUK_USE_CACHE_ACTIVATION)
|
|
DUK_NOINLINE
|
|
#endif
|
|
duk_activation *duk__hthread_activation_alloc_slow(duk_hthread *thr) {
|
|
duk_activation *act;
|
|
|
|
act = (duk_activation *) DUK_ALLOC_CHECKED(thr, sizeof(duk_activation));
|
|
DUK_ASSERT(act != NULL);
|
|
return act;
|
|
}
|
|
|
|
#if defined(DUK_USE_CACHE_ACTIVATION)
|
|
DUK_INTERNAL DUK_INLINE duk_activation *duk_hthread_activation_alloc(duk_hthread *thr) {
|
|
duk_activation *act;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
|
|
act = thr->heap->activation_free;
|
|
if (DUK_LIKELY(act != NULL)) {
|
|
thr->heap->activation_free = act->parent;
|
|
return act;
|
|
}
|
|
|
|
return duk__hthread_activation_alloc_slow(thr);
|
|
}
|
|
#else /* DUK_USE_CACHE_ACTIVATION */
|
|
DUK_INTERNAL duk_activation *duk_hthread_activation_alloc(duk_hthread *thr) {
|
|
return duk__hthread_activation_alloc_slow(thr);
|
|
}
|
|
#endif /* DUK_USE_CACHE_ACTIVATION */
|
|
|
|
|
|
DUK_INTERNAL void duk_hthread_activation_free(duk_hthread *thr, duk_activation *act) {
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(act != NULL);
|
|
|
|
#if defined(DUK_USE_CACHE_ACTIVATION)
|
|
/* Unconditional caching for now; freed in mark-and-sweep. */
|
|
act->parent = thr->heap->activation_free;
|
|
thr->heap->activation_free = act;
|
|
#else
|
|
DUK_FREE_CHECKED(thr, (void *) act);
|
|
#endif
|
|
}
|
|
|
|
/* Internal helper: process the unwind for the topmost activation of a thread,
|
|
* but leave the duk_activation in place for possible tailcall reuse.
|
|
*/
|
|
DUK_LOCAL void duk__activation_unwind_nofree_norz(duk_hthread *thr) {
|
|
#if defined(DUK_USE_DEBUGGER_SUPPORT)
|
|
duk_heap *heap;
|
|
#endif
|
|
duk_activation *act;
|
|
duk_hobject *func;
|
|
duk_hobject *tmp;
|
|
|
|
DUK_ASSERT(thr != NULL);
|
|
DUK_ASSERT(thr->callstack_curr != NULL); /* caller must check */
|
|
DUK_ASSERT(thr->callstack_top > 0);
|
|
act = thr->callstack_curr;
|
|
DUK_ASSERT(act != NULL);
|
|
/* With lightfuncs, act 'func' may be NULL. */
|
|
|
|
/* With duk_activation records allocated separately, 'act' is a stable
|
|
* pointer and not affected by side effects.
|
|
*/
|
|
|
|
#if defined(DUK_USE_NONSTD_FUNC_CALLER_PROPERTY)
|
|
/*
|
|
* Restore 'caller' property for non-strict callee functions.
|
|
*/
|
|
|
|
func = DUK_ACT_GET_FUNC(act);
|
|
if (func != NULL && !DUK_HOBJECT_HAS_STRICT(func)) {
|
|
duk_tval *tv_caller;
|
|
duk_tval tv_tmp;
|
|
duk_hobject *h_tmp;
|
|
|
|
tv_caller = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, func, DUK_STRIDX_CALLER);
|
|
|
|
/* The act->prev_caller should only be set if the entry for 'caller'
|
|
* exists (as it is only set in that case, and the property is not
|
|
* configurable), but handle all the cases anyway.
|
|
*/
|
|
|
|
if (tv_caller) {
|
|
DUK_TVAL_SET_TVAL(&tv_tmp, tv_caller);
|
|
if (act->prev_caller) {
|
|
/* Just transfer the refcount from act->prev_caller to tv_caller,
|
|
* so no need for a refcount update. This is the expected case.
|
|
*/
|
|
DUK_TVAL_SET_OBJECT(tv_caller, act->prev_caller);
|
|
act->prev_caller = NULL;
|
|
} else {
|
|
DUK_TVAL_SET_NULL(tv_caller); /* no incref needed */
|
|
DUK_ASSERT(act->prev_caller == NULL);
|
|
}
|
|
DUK_TVAL_DECREF_NORZ(thr, &tv_tmp);
|
|
} else {
|
|
h_tmp = act->prev_caller;
|
|
if (h_tmp) {
|
|
act->prev_caller = NULL;
|
|
DUK_HOBJECT_DECREF_NORZ(thr, h_tmp);
|
|
}
|
|
}
|
|
DUK_ASSERT(act->prev_caller == NULL);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Unwind debugger state. If we unwind while stepping
|
|
* (for any step type), pause execution. This is the
|
|
* only place explicitly handling a step out.
|
|
*/
|
|
|
|
#if defined(DUK_USE_DEBUGGER_SUPPORT)
|
|
heap = thr->heap;
|
|
if (heap->dbg_pause_act == thr->callstack_curr) {
|
|
if (heap->dbg_pause_flags & DUK_PAUSE_FLAG_FUNC_EXIT) {
|
|
DUK_D(DUK_DPRINT("PAUSE TRIGGERED by function exit"));
|
|
duk_debug_set_paused(heap);
|
|
} else {
|
|
DUK_D(DUK_DPRINT("unwound past dbg_pause_act, set to NULL"));
|
|
heap->dbg_pause_act = NULL; /* avoid stale pointers */
|
|
}
|
|
DUK_ASSERT(heap->dbg_pause_act == NULL);
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* Unwind catchers.
|
|
*
|
|
* Since there are no references in the catcher structure,
|
|
* unwinding is quite simple. The only thing we need to
|
|
* look out for is popping a possible lexical environment
|
|
* established for an active catch clause.
|
|
*/
|
|
|
|
while (act->cat != NULL) {
|
|
duk_hthread_catcher_unwind_norz(thr, act);
|
|
}
|
|
|
|
/*
|
|
* Close environment record(s) if they exist.
|
|
*
|
|
* Only variable environments are closed. If lex_env != var_env, it
|
|
* cannot currently contain any register bound declarations.
|
|
*
|
|
* Only environments created for a NEWENV function are closed. If an
|
|
* environment is created for e.g. an eval call, it must not be closed.
|
|
*/
|
|
|
|
func = DUK_ACT_GET_FUNC(act);
|
|
if (func != NULL && !DUK_HOBJECT_HAS_NEWENV(func)) {
|
|
DUK_DDD(DUK_DDDPRINT("skip closing environments, envs not owned by this activation"));
|
|
goto skip_env_close;
|
|
}
|
|
/* func is NULL for lightfunc */
|
|
|
|
/* Catch sites are required to clean up their environments
|
|
* in FINALLY part before propagating, so this should
|
|
* always hold here.
|
|
*/
|
|
DUK_ASSERT(act->lex_env == act->var_env);
|
|
|
|
/* XXX: Closing the environment record copies values from registers
|
|
* into the scope object. It's side effect free as such, but may
|
|
* currently run out of memory which causes an error throw. This is
|
|
* an actual sandboxing problem for error unwinds, and needs to be
|
|
* fixed e.g. by preallocating the scope property slots.
|
|
*/
|
|
if (act->var_env != NULL) {
|
|
DUK_DDD(DUK_DDDPRINT("closing var_env record %p -> %!O",
|
|
(void *) act->var_env, (duk_heaphdr *) act->var_env));
|
|
duk_js_close_environment_record(thr, act->var_env);
|
|
}
|
|
|
|
skip_env_close:
|
|
|
|
/*
|
|
* Update preventcount
|
|
*/
|
|
|
|
if (act->flags & DUK_ACT_FLAG_PREVENT_YIELD) {
|
|
DUK_ASSERT(thr->callstack_preventcount >= 1);
|
|
thr->callstack_preventcount--;
|
|
}
|
|
|
|
/*
|
|
* Reference count updates, using NORZ macros so we don't
|
|
* need to handle side effects.
|
|
*
|
|
* duk_activation pointers like act->var_env are intentionally
|
|
* left as garbage and not NULLed. Without side effects they
|
|
* can't be used when the values are dangling/garbage.
|
|
*/
|
|
|
|
DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->var_env);
|
|
DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, act->lex_env);
|
|
tmp = DUK_ACT_GET_FUNC(act);
|
|
DUK_HOBJECT_DECREF_NORZ_ALLOWNULL(thr, tmp);
|
|
DUK_UNREF(tmp);
|
|
}
|
|
|
|
/* Unwind topmost duk_activation of a thread, caller must ensure that an
|
|
* activation exists. The call is side effect free, except that scope
|
|
* closure may currently throw an out-of-memory error.
|
|
*/
|
|
DUK_INTERNAL void duk_hthread_activation_unwind_norz(duk_hthread *thr) {
|
|
duk_activation *act;
|
|
|
|
duk__activation_unwind_nofree_norz(thr);
|
|
|
|
DUK_ASSERT(thr->callstack_curr != NULL);
|
|
DUK_ASSERT(thr->callstack_top > 0);
|
|
act = thr->callstack_curr;
|
|
thr->callstack_curr = act->parent;
|
|
thr->callstack_top--;
|
|
|
|
/* Ideally we'd restore value stack reserve here to caller's value.
|
|
* This doesn't work for current unwind call sites however, because
|
|
* the current (unwound) value stack top may be above the reserve.
|
|
* Thus value stack reserve is restored by the call sites.
|
|
*/
|
|
|
|
/* XXX: inline for performance builds? */
|
|
duk_hthread_activation_free(thr, act);
|
|
|
|
/* We could clear the book-keeping variables like retval_byteoff for
|
|
* the topmost activation, but don't do so now as it's not necessary.
|
|
*/
|
|
}
|
|
|
|
DUK_INTERNAL void duk_hthread_activation_unwind_reuse_norz(duk_hthread *thr) {
|
|
duk__activation_unwind_nofree_norz(thr);
|
|
}
|
|
|
|
/* Get duk_activation for given callstack level or NULL if level is invalid
|
|
* or deeper than the call stack. Level -1 refers to current activation, -2
|
|
* to its caller, etc. Starting from Duktape 2.2 finding the activation is
|
|
* a linked list scan which gets more expensive the deeper the lookup is.
|
|
*/
|
|
DUK_INTERNAL duk_activation *duk_hthread_get_activation_for_level(duk_hthread *thr, duk_int_t level) {
|
|
duk_activation *act;
|
|
|
|
if (level >= 0) {
|
|
return NULL;
|
|
}
|
|
act = thr->callstack_curr;
|
|
for (;;) {
|
|
if (act == NULL) {
|
|
return act;
|
|
}
|
|
if (level == -1) {
|
|
return act;
|
|
}
|
|
level++;
|
|
act = act->parent;
|
|
}
|
|
/* never here */
|
|
}
|
|
|
|
#if defined(DUK_USE_FINALIZER_TORTURE)
|
|
DUK_INTERNAL void duk_hthread_valstack_torture_realloc(duk_hthread *thr) {
|
|
duk_size_t alloc_size;
|
|
duk_tval *new_ptr;
|
|
duk_ptrdiff_t alloc_end_off;
|
|
duk_ptrdiff_t end_off;
|
|
duk_ptrdiff_t bottom_off;
|
|
duk_ptrdiff_t top_off;
|
|
|
|
if (thr->valstack == NULL) {
|
|
DUK_D(DUK_DPRINT("skip valstack torture realloc, valstack is NULL"));
|
|
return;
|
|
}
|
|
|
|
alloc_end_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_alloc_end - (duk_uint8_t *) thr->valstack);
|
|
end_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_end - (duk_uint8_t *) thr->valstack);
|
|
bottom_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_bottom - (duk_uint8_t *) thr->valstack);
|
|
top_off = (duk_ptrdiff_t) ((duk_uint8_t *) thr->valstack_top - (duk_uint8_t *) thr->valstack);
|
|
alloc_size = (duk_size_t) alloc_end_off;
|
|
if (alloc_size == 0) {
|
|
DUK_D(DUK_DPRINT("skip valstack torture realloc, alloc_size is zero"));
|
|
return;
|
|
}
|
|
|
|
/* Use DUK_ALLOC_RAW() to avoid side effects. */
|
|
new_ptr = (duk_tval *) DUK_ALLOC_RAW(thr->heap, alloc_size);
|
|
if (new_ptr != NULL) {
|
|
duk_memcpy((void *) new_ptr, (const void *) thr->valstack, alloc_size);
|
|
duk_memset((void *) thr->valstack, 0x55, alloc_size);
|
|
DUK_FREE_CHECKED(thr, (void *) thr->valstack);
|
|
thr->valstack = new_ptr;
|
|
thr->valstack_alloc_end = (duk_tval *) ((duk_uint8_t *) new_ptr + alloc_end_off);
|
|
thr->valstack_end = (duk_tval *) ((duk_uint8_t *) new_ptr + end_off);
|
|
thr->valstack_bottom = (duk_tval *) ((duk_uint8_t *) new_ptr + bottom_off);
|
|
thr->valstack_top = (duk_tval *) ((duk_uint8_t *) new_ptr + top_off);
|
|
} else {
|
|
DUK_D(DUK_DPRINT("failed to realloc valstack for torture, ignore"));
|
|
}
|
|
}
|
|
#endif /* DUK_USE_FINALIZER_TORTURE */
|