Initial import
This commit is contained in:
407
third_party/duktape/duk_hthread_stacks.c
vendored
Normal file
407
third_party/duktape/duk_hthread_stacks.c
vendored
Normal file
@@ -0,0 +1,407 @@
|
||||
/*
|
||||
* 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 "third_party/duktape/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 */
|
||||
Reference in New Issue
Block a user