void duk_hthread_callstack_unwind(duk_hthread *thr, int new_top) { int idx; /* FIXME: typing of idx and new_top */ DUK_DDDPRINT("unwind callstack top of thread %p from %d to %d", (void *) thr, (thr != NULL ? (int) thr->callstack_top : (int) -1), (int) new_top); DUK_ASSERT(thr); DUK_ASSERT(thr->heap); DUK_ASSERT(new_top >= 0); DUK_ASSERT((duk_size_t) new_top <= thr->callstack_top); /* cannot grow */ /* * The loop below must avoid issues with potential callstack * reallocations. A resize (and other side effects) may happen * e.g. due to finalizer/errhandler calls caused by a refzero or * mark-and-sweep. Arbitrary finalizers may run, because when * an environment record is refzero'd, it may refer to arbitrary * values which also become refzero'd. * * So, the pointer 'p' is re-looked-up below whenever a side effect * might have changed it. */ idx = thr->callstack_top; while (idx > new_top) { duk_activation *p; #ifdef DUK_USE_REFERENCE_COUNTING duk_hobject *tmp; #endif idx--; DUK_ASSERT(idx >= 0); DUK_ASSERT((duk_size_t) idx < thr->callstack_size); /* true, despite side effect resizes */ p = &thr->callstack[idx]; DUK_ASSERT(p->func != NULL); #ifdef DUK_USE_FUNC_NONSTD_CALLER_PROPERTY /* * Restore 'caller' property for non-strict callee functions. */ if (!DUK_HOBJECT_HAS_STRICT(p->func)) { duk_tval *tv_caller; duk_tval tv_tmp; duk_hobject *h_tmp; tv_caller = duk_hobject_find_existing_entry_tval_ptr(p->func, DUK_HTHREAD_STRING_CALLER(thr)); /* The p->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 (p->prev_caller) { /* Just transfer the refcount from p->prev_caller to tv_caller, * so no need for a refcount update. This is the expected case. */ DUK_TVAL_SET_OBJECT(tv_caller, p->prev_caller); p->prev_caller = NULL; } else { DUK_TVAL_SET_NULL(tv_caller); /* no incref needed */ DUK_ASSERT(p->prev_caller == NULL); } DUK_TVAL_DECREF(thr, &tv_tmp); /* side effects */ } else { h_tmp = p->prev_caller; if (h_tmp) { p->prev_caller = NULL; DUK_HOBJECT_DECREF(thr, h_tmp); /* side effects */ } } p = &thr->callstack[idx]; /* avoid side effects */ DUK_ASSERT(p->prev_caller == NULL); } #endif /* * 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. */ if (!DUK_HOBJECT_HAS_NEWENV(p->func)) { DUK_DDDPRINT("skip closing environments, envs not owned by this activation"); goto skip_env_close; } DUK_ASSERT(p->lex_env == p->var_env); if (p->var_env != NULL) { DUK_DDDPRINT("closing var_env record %p -> %!O", (void *) p->var_env, (duk_heaphdr *) p->var_env); duk_js_close_environment_record(thr, p->var_env, p->func, p->idx_bottom); p = &thr->callstack[idx]; /* avoid side effect issues */ } #if 0 if (p->lex_env != NULL) { if (p->lex_env == p->var_env) { /* common case, already closed, so skip */ DUK_DDPRINT("lex_env and var_env are the same and lex_env " "already closed -> skip closing lex_env"); ; } else { DUK_DDPRINT("closing lex_env record %p -> %!O", (void *) p->lex_env, (duk_heaphdr *) p->lex_env); duk_js_close_environment_record(thr, p->lex_env, p->func, p->idx_bottom); p = &thr->callstack[idx]; /* avoid side effect issues */ } } #endif DUK_ASSERT((p->lex_env == NULL) || ((duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL))); DUK_ASSERT((p->var_env == NULL) || ((duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(p->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL))); skip_env_close: /* * Update preventcount */ if (p->flags & DUK_ACT_FLAG_PREVENT_YIELD) { DUK_ASSERT(thr->callstack_preventcount >= 1); thr->callstack_preventcount--; } /* * Reference count updates * * Note: careful manipulation of refcounts. The top is * not updated yet, so all the activations are reachable * for mark-and-sweep (which may be triggered by decref). * However, the pointers are NULL so this is not an issue. */ #ifdef DUK_USE_REFERENCE_COUNTING tmp = p->var_env; #endif p->var_env = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF(thr, tmp); p = &thr->callstack[idx]; /* avoid side effect issues */ #endif #ifdef DUK_USE_REFERENCE_COUNTING tmp = p->lex_env; #endif p->lex_env = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF(thr, tmp); p = &thr->callstack[idx]; /* avoid side effect issues */ #endif /* Note: this may cause a corner case situation where a finalizer * may see a currently reachable activation whose 'func' is NULL. */ #ifdef DUK_USE_REFERENCE_COUNTING tmp = p->func; #endif p->func = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF(thr, tmp); p = &thr->callstack[idx]; /* avoid side effect issues */ #endif } thr->callstack_top = new_top; /* * We could clear the book-keeping variables for the topmost activation, * but don't do so now. */ #if 0 if (thr->callstack_top > 0) { duk_activation *p = thr->callstack + thr->callstack_top - 1; p->idx_retval = -1; } #endif /* Note: any entries above the callstack top are garbage and not zeroed. * Also topmost activation idx_retval is garbage and not zeroed. */ }
DUK_INTERNAL void duk_hthread_callstack_unwind(duk_hthread *thr, duk_size_t new_top) { duk_size_t idx; DUK_DDD(DUK_DDDPRINT("unwind callstack top of thread %p from %ld to %ld", (void *) thr, (thr != NULL ? (long) thr->callstack_top : (long) -1), (long) new_top)); DUK_ASSERT(thr); DUK_ASSERT(thr->heap); DUK_ASSERT_DISABLE(new_top >= 0); /* unsigned */ DUK_ASSERT((duk_size_t) new_top <= thr->callstack_top); /* cannot grow */ /* * The loop below must avoid issues with potential callstack * reallocations. A resize (and other side effects) may happen * e.g. due to finalizer/errhandler calls caused by a refzero or * mark-and-sweep. Arbitrary finalizers may run, because when * an environment record is refzero'd, it may refer to arbitrary * values which also become refzero'd. * * So, the pointer 'p' is re-looked-up below whenever a side effect * might have changed it. */ idx = thr->callstack_top; while (idx > new_top) { duk_activation *act; duk_hobject *func; #ifdef DUK_USE_REFERENCE_COUNTING duk_hobject *tmp; #endif #ifdef DUK_USE_DEBUGGER_SUPPORT duk_heap *heap; #endif idx--; DUK_ASSERT_DISABLE(idx >= 0); /* unsigned */ DUK_ASSERT((duk_size_t) idx < thr->callstack_size); /* true, despite side effect resizes */ act = thr->callstack + idx; /* With lightfuncs, act 'func' may be NULL */ #ifdef 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_existing_entry_tval_ptr(thr->heap, func, DUK_HTHREAD_STRING_CALLER(thr)); /* 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(thr, &tv_tmp); /* side effects */ } else { h_tmp = act->prev_caller; if (h_tmp) { act->prev_caller = NULL; DUK_HOBJECT_DECREF(thr, h_tmp); /* side effects */ } } act = thr->callstack + idx; /* avoid side effects */ DUK_ASSERT(act->prev_caller == NULL); } #endif /* * Unwind debugger state. If we unwind while stepping * (either step over or step into), pause execution. */ #if defined(DUK_USE_DEBUGGER_SUPPORT) heap = thr->heap; if (heap->dbg_step_thread == thr && heap->dbg_step_csindex == idx) { /* Pause for all step types: step into, step over, step out. * This is the only place explicitly handling a step out. */ DUK_HEAP_SET_PAUSED(heap); DUK_ASSERT(heap->dbg_step_thread == NULL); } #endif /* * 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 */ DUK_ASSERT(act->lex_env == act->var_env); 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, func, act->idx_bottom); act = thr->callstack + idx; /* avoid side effect issues */ } #if 0 if (act->lex_env != NULL) { if (act->lex_env == act->var_env) { /* common case, already closed, so skip */ DUK_DD(DUK_DDPRINT("lex_env and var_env are the same and lex_env " "already closed -> skip closing lex_env")); ; } else { DUK_DD(DUK_DDPRINT("closing lex_env record %p -> %!O", (void *) act->lex_env, (duk_heaphdr *) act->lex_env)); duk_js_close_environment_record(thr, act->lex_env, DUK_ACT_GET_FUNC(act), act->idx_bottom); act = thr->callstack + idx; /* avoid side effect issues */ } } #endif DUK_ASSERT((act->lex_env == NULL) || ((duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->lex_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL))); DUK_ASSERT((act->var_env == NULL) || ((duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_CALLEE(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_VARMAP(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_THREAD(thr)) == NULL) && (duk_hobject_find_existing_entry_tval_ptr(thr->heap, act->var_env, DUK_HTHREAD_STRING_INT_REGBASE(thr)) == NULL))); 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 * * Note: careful manipulation of refcounts. The top is * not updated yet, so all the activations are reachable * for mark-and-sweep (which may be triggered by decref). * However, the pointers are NULL so this is not an issue. */ #ifdef DUK_USE_REFERENCE_COUNTING tmp = act->var_env; #endif act->var_env = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); act = thr->callstack + idx; /* avoid side effect issues */ #endif #ifdef DUK_USE_REFERENCE_COUNTING tmp = act->lex_env; #endif act->lex_env = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); act = thr->callstack + idx; /* avoid side effect issues */ #endif /* Note: this may cause a corner case situation where a finalizer * may see a currently reachable activation whose 'func' is NULL. */ #ifdef DUK_USE_REFERENCE_COUNTING tmp = DUK_ACT_GET_FUNC(act); #endif act->func = NULL; #ifdef DUK_USE_REFERENCE_COUNTING DUK_HOBJECT_DECREF_ALLOWNULL(thr, tmp); act = thr->callstack + idx; /* avoid side effect issues */ DUK_UNREF(act); #endif } thr->callstack_top = new_top; /* * We could clear the book-keeping variables for the topmost activation, * but don't do so now. */ #if 0 if (thr->callstack_top > 0) { duk_activation *act = thr->callstack + thr->callstack_top - 1; act->idx_retval = 0; } #endif /* Note: any entries above the callstack top are garbage and not zeroed. * Also topmost activation idx_retval is garbage (not zeroed), and must * be ignored. */ }