DUK_INTERNAL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type) { duk_tval tv_tmp; #if defined(DUK_USE_DEBUGGER_SUPPORT) /* If something is thrown with the debugger attached and nobody will * catch it, execution is paused before the longjmp, turning over * control to the debug client. This allows local state to be examined * before the stack is unwound. */ /* TODO: Allow customizing the pause and notify behavior */ if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap) && lj_type == DUK_LJ_TYPE_THROW) { duk_context *ctx = (duk_context *) thr; duk_bool_t fatal; duk_hobject *h_obj; /* Don't intercept a DoubleError, we may have caused the initial double * fault and attempting to intercept it will cause us to be called * recursively and exhaust the C stack. */ h_obj = duk_get_hobject(ctx, -1); if (h_obj == thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_D(DUK_DPRINT("built-in DoubleError instance thrown, not intercepting")); goto skip_throw_intercept; } DUK_D(DUK_DPRINT("throw with debugger attached, report to client")); fatal = !duk__have_active_catcher(thr); /* Report it to the debug client */ duk_debug_send_throw(thr, fatal); if (fatal) { DUK_D(DUK_DPRINT("throw will be fatal, halt before longjmp")); duk_debug_halt_execution(thr, 1 /*use_prev_pc*/); } } skip_throw_intercept: #endif thr->heap->lj.type = lj_type; DUK_ASSERT(thr->valstack_top > thr->valstack); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, thr->valstack_top - 1); DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); DUK_TVAL_DECREF(thr, &tv_tmp); duk_pop((duk_context *) thr); }
void duk_err_setup_heap_ljstate(duk_hthread *thr, int lj_type) { duk_tval tv_tmp; thr->heap->lj.type = lj_type; DUK_ASSERT(thr->valstack_top > thr->valstack); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, thr->valstack_top - 1); DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); DUK_TVAL_DECREF(thr, &tv_tmp); duk_pop((duk_context *) thr); }
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_ret_t duk_bi_thread_resume(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_hthread *thr_resume; duk_tval tv_tmp; duk_tval *tv; duk_hobject *func; duk_small_int_t is_error; DUK_DDDPRINT("Duktape.Thread.resume(): thread=%!T, value=%!T, is_error=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1), duk_get_tval(ctx, 2)); DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); thr_resume = duk_require_hthread(ctx, 0); is_error = (duk_small_int_t) duk_to_boolean(ctx, 2); duk_set_top(ctx, 2); /* [ thread value ] */ /* * Thread state and calling context checks */ if (thr->callstack_top < 2) { DUK_DDPRINT("resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"); goto state_error; } DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func)); DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) { DUK_DDPRINT("resume state invalid: caller must be Ecmascript code"); goto state_error; } /* Note: there is no requirement that: 'thr->callstack_preventcount == 1' * like for yield. */ if (thr_resume->state != DUK_HTHREAD_STATE_INACTIVE && thr_resume->state != DUK_HTHREAD_STATE_YIELDED) { DUK_DDPRINT("resume state invalid: target thread must be INACTIVE or YIELDED"); goto state_error; } DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE || thr_resume->state == DUK_HTHREAD_STATE_YIELDED); /* Further state-dependent pre-checks */ if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { /* no pre-checks now, assume a previous yield() has left things in * tip-top shape (longjmp handler will assert for these). */ } else { DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE); if ((thr_resume->callstack_top != 0) || (thr_resume->valstack_top - thr_resume->valstack != 1)) { goto state_invalid_initial; } tv = &thr_resume->valstack_top[-1]; DUK_ASSERT(tv >= thr_resume->valstack && tv < thr_resume->valstack_top); if (!DUK_TVAL_IS_OBJECT(tv)) { goto state_invalid_initial; } func = DUK_TVAL_GET_OBJECT(tv); DUK_ASSERT(func != NULL); if (!DUK_HOBJECT_IS_COMPILEDFUNCTION(func)) { /* Note: cannot be a bound function either right now, * this would be easy to relax though. */ goto state_invalid_initial; } } /* * The error object has been augmented with a traceback and other * info from its creation point -- usually another thread. The * error handler is called here right before throwing, but it also * runs in the resumer's thread. It might be nice to get a traceback * from the resumee but this is not the case now. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) if (is_error) { DUK_ASSERT_TOP(ctx, 2); /* value (error) is at stack top */ duk_err_augment_error_throw(thr); /* in resumer's context */ } #endif #ifdef DUK_USE_DEBUG /* debug logging */ if (is_error) { DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) { DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } else { DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); } #endif thr->heap->lj.type = DUK_LJ_TYPE_RESUME; /* lj value2: thread */ DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value2); DUK_TVAL_SET_TVAL(&thr->heap->lj.value2, &thr->valstack_bottom[0]); DUK_TVAL_INCREF(thr, &thr->heap->lj.value2); DUK_TVAL_DECREF(thr, &tv_tmp); /* lj value1: value */ DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[1]); DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); DUK_TVAL_DECREF(thr, &tv_tmp); thr->heap->lj.iserror = is_error; DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ duk_err_longjmp(thr); /* execution resumes in bytecode executor */ return 0; /* never here */ state_invalid_initial: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid initial thread state/stack"); return 0; /* never here */ state_error: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for resume"); return 0; /* never here */ }
duk_ret_t duk_bi_thread_yield(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_tval tv_tmp; duk_small_int_t is_error; DUK_DDDPRINT("Duktape.Thread.yield(): value=%!T, is_error=%!T", duk_get_tval(ctx, 0), duk_get_tval(ctx, 1)); DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING); DUK_ASSERT(thr->heap->curr_thread == thr); is_error = (duk_small_int_t) duk_to_boolean(ctx, 1); duk_set_top(ctx, 1); /* [ value ] */ /* * Thread state and calling context checks */ if (!thr->resumer) { DUK_DDPRINT("yield state invalid: current thread must have a resumer"); goto state_error; } DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED); if (thr->callstack_top < 2) { DUK_DDPRINT("yield state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.yield)"); goto state_error; } DUK_ASSERT((thr->callstack + thr->callstack_top - 1)->func != NULL); /* us */ DUK_ASSERT(DUK_HOBJECT_IS_NATIVEFUNCTION((thr->callstack + thr->callstack_top - 1)->func)); DUK_ASSERT((thr->callstack + thr->callstack_top - 2)->func != NULL); /* caller */ if (!DUK_HOBJECT_IS_COMPILEDFUNCTION((thr->callstack + thr->callstack_top - 2)->func)) { DUK_DDPRINT("yield state invalid: caller must be Ecmascript code"); goto state_error; } DUK_ASSERT(thr->callstack_preventcount >= 1); /* should never be zero, because we (Duktape.Thread.yield) are on the stack */ if (thr->callstack_preventcount != 1) { /* Note: the only yield-preventing call is Duktape.Thread.yield(), hence check for 1, not 0 */ DUK_DDPRINT("yield state invalid: there must be no yield-preventing calls in current thread callstack (preventcount is %d)", (int) thr->callstack_preventcount); goto state_error; } /* * The error object has been augmented with a traceback and other * info from its creation point -- usually the current thread. * The error handler, however, is called right before throwing * and runs in the yielder's thread. */ #if defined(DUK_USE_AUGMENT_ERROR_THROW) if (is_error) { DUK_ASSERT_TOP(ctx, 1); /* value (error) is at stack top */ duk_err_augment_error_throw(thr); /* in yielder's context */ } #endif #ifdef DUK_USE_DEBUG if (is_error) { DUK_DDDPRINT("YIELD ERROR: value=%!T", duk_get_tval(ctx, 0)); } else { DUK_DDDPRINT("YIELD NORMAL: value=%!T", duk_get_tval(ctx, 0)); } #endif /* * Process yield * * After longjmp(), processing continues in bytecode executor longjmp * handler, which will e.g. update thr->resumer to NULL. */ thr->heap->lj.type = DUK_LJ_TYPE_YIELD; /* lj value1: value */ DUK_ASSERT(thr->valstack_bottom < thr->valstack_top); DUK_TVAL_SET_TVAL(&tv_tmp, &thr->heap->lj.value1); DUK_TVAL_SET_TVAL(&thr->heap->lj.value1, &thr->valstack_bottom[0]); DUK_TVAL_INCREF(thr, &thr->heap->lj.value1); DUK_TVAL_DECREF(thr, &tv_tmp); thr->heap->lj.iserror = is_error; DUK_ASSERT(thr->heap->lj.jmpbuf_ptr != NULL); /* call is from executor, so we know we have a jmpbuf */ duk_err_longjmp(thr); /* execution resumes in bytecode executor */ return 0; /* never here */ state_error: DUK_ERROR(thr, DUK_ERR_TYPE_ERROR, "invalid state for yield"); return 0; /* never here */ }
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. */ }