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);
}
Exemple #2
0
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);
}
Exemple #3
0
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.
	 */
}
Exemple #4
0
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 */
}
Exemple #5
0
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.
	 */
}