JavaScript-Embedded

 view release on metacpan or  search on metacpan

lib/JavaScript/Embedded/C/lib/duktape.c  view on Meta::CPAN


	if (thr->callstack_top < 2) {
		DUK_DD(DUK_DDPRINT(
		    "resume state invalid: callstack should contain at least 2 entries (caller and Duktape.Thread.resume)"));
		goto state_error;
	}
	DUK_ASSERT(thr->callstack_curr != NULL);
	DUK_ASSERT(thr->callstack_curr->parent != NULL);
	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr) != NULL); /* us */
	DUK_ASSERT(DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->callstack_curr)));
	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->callstack_curr->parent) != NULL); /* caller */

	caller_func = DUK_ACT_GET_FUNC(thr->callstack_curr->parent);
	if (!DUK_HOBJECT_IS_COMPFUNC(caller_func)) {
		DUK_DD(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_DD(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_hobject *h_fun;

		DUK_ASSERT(thr_resume->state == DUK_HTHREAD_STATE_INACTIVE);

		/* The initial function must be an ECMAScript function (but
		 * can be bound).  We must make sure of that before we longjmp
		 * because an error in the RESUME handler call processing will
		 * not be handled very cleanly.
		 */
		if ((thr_resume->callstack_top != 0) || (thr_resume->valstack_top - thr_resume->valstack != 1)) {
			goto state_error;
		}

		duk_push_tval(thr, DUK_GET_TVAL_NEGIDX(thr_resume, -1));
		duk_resolve_nonbound_function(thr);
		h_fun = duk_require_hobject(thr, -1); /* reject lightfuncs on purpose */
		if (!DUK_HOBJECT_IS_CALLABLE(h_fun) || !DUK_HOBJECT_IS_COMPFUNC(h_fun)) {
			goto state_error;
		}
		duk_pop(thr);
	}

#if 0
	/* This check would prevent a heap destruction time finalizer from
	 * launching a coroutine, which would ensure that during finalization
	 * 'thr' would always equal heap_thread.  Normal runtime finalizers
	 * run with ms_running == 0, i.e. outside mark-and-sweep.  See GH-2030.
	 */
	if (thr->heap->ms_running) {
		DUK_D(DUK_DPRINT("refuse Duktape.Thread.resume() when ms_running != 0"));
		goto state_error;
	}
#endif

	/*
	 *  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(thr, 2); /* value (error) is at stack top */
		duk_err_augment_error_throw(thr); /* in resumer's context */
	}
#endif

#if defined(DUK_USE_DEBUG)
	if (is_error) {
		DUK_DDD(DUK_DDDPRINT("RESUME ERROR: thread=%!T, value=%!T",
		                     (duk_tval *) duk_get_tval(thr, 0),
		                     (duk_tval *) duk_get_tval(thr, 1)));
	} else if (thr_resume->state == DUK_HTHREAD_STATE_YIELDED) {
		DUK_DDD(DUK_DDDPRINT("RESUME NORMAL: thread=%!T, value=%!T",
		                     (duk_tval *) duk_get_tval(thr, 0),
		                     (duk_tval *) duk_get_tval(thr, 1)));
	} else {
		DUK_DDD(DUK_DDDPRINT("RESUME INITIAL: thread=%!T, value=%!T",
		                     (duk_tval *) duk_get_tval(thr, 0),
		                     (duk_tval *) duk_get_tval(thr, 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_UPDREF(thr, &thr->heap->lj.value2, &thr->valstack_bottom[0]); /* side effects */

	/* lj value1: value */
	DUK_ASSERT(thr->valstack_bottom + 1 < thr->valstack_top);
	DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, &thr->valstack_bottom[1]); /* side effects */
	DUK_TVAL_CHKFAST_INPLACE_SLOW(&thr->heap->lj.value1);

	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 */
	DUK_UNREACHABLE();
	/* Never here, fall through to error (from compiler point of view). */

state_error:
	DUK_DCERROR_TYPE_INVALID_STATE(thr);

lib/JavaScript/Embedded/C/lib/duktape.c  view on Meta::CPAN

	}
}

DUK_INTERNAL void duk_debug_clear_pause_state(duk_heap *heap) {
	heap->dbg_pause_flags = 0;
	heap->dbg_pause_act = NULL;
	heap->dbg_pause_startline = 0;
}

#else /* DUK_USE_DEBUGGER_SUPPORT */

/* No debugger support. */

#endif /* DUK_USE_DEBUGGER_SUPPORT */

/* automatic undefs */
#undef DUK__DBG_TPORT_ENTER
#undef DUK__DBG_TPORT_EXIT
#undef DUK__SET_CONN_BROKEN
#line 1 "duk_error_augment.c"
/*
 *  Augmenting errors at their creation site and their throw site.
 *
 *  When errors are created, traceback data is added by built-in code
 *  and a user error handler (if defined) can process or replace the
 *  error.  Similarly, when errors are thrown, a user error handler
 *  (if defined) can process or replace the error.
 *
 *  Augmentation and other processing at error creation time is nice
 *  because an error is only created once, but it may be thrown and
 *  rethrown multiple times.  User error handler registered for processing
 *  an error at its throw site must be careful to handle rethrowing in
 *  a useful manner.
 *
 *  Error augmentation may throw an internal error (e.g. alloc error).
 *
 *  ECMAScript allows throwing any values, so all values cannot be
 *  augmented.  Currently, the built-in augmentation at error creation
 *  only augments error values which are Error instances (= have the
 *  built-in Error.prototype in their prototype chain) and are also
 *  extensible.  User error handlers have no limitations in this respect.
 */

/* #include duk_internal.h -> already included */

/*
 *  Helper for calling a user error handler.
 *
 *  'thr' must be the currently active thread; the error handler is called
 *  in its context.  The valstack of 'thr' must have the error value on
 *  top, and will be replaced by another error value based on the return
 *  value of the error handler.
 *
 *  The helper calls duk_handle_call() recursively in protected mode.
 *  Before that call happens, no longjmps should happen; as a consequence,
 *  we must assume that the valstack contains enough temporary space for
 *  arguments and such.
 *
 *  While the error handler runs, any errors thrown will not trigger a
 *  recursive error handler call (this is implemented using a heap level
 *  flag which will "follow" through any coroutines resumed inside the
 *  error handler).  If the error handler is not callable or throws an
 *  error, the resulting error replaces the original error (for Duktape
 *  internal errors, duk_error_throw.c further substitutes this error with
 *  a DoubleError which is not ideal).  This would be easy to change and
 *  even signal to the caller.
 *
 *  The user error handler is stored in 'Duktape.errCreate' or
 *  'Duktape.errThrow' depending on whether we're augmenting the error at
 *  creation or throw time.  There are several alternatives to this approach,
 *  see doc/error-objects.rst for discussion.
 *
 *  Note: since further longjmp()s may occur while calling the error handler
 *  (for many reasons, e.g. a labeled 'break' inside the handler), the
 *  caller can make no assumptions on the thr->heap->lj state after the
 *  call (this affects especially duk_error_throw.c).  This is not an issue
 *  as long as the caller writes to the lj state only after the error handler
 *  finishes.
 */

#if defined(DUK_USE_ERRTHROW) || defined(DUK_USE_ERRCREATE)
DUK_LOCAL void duk__err_augment_user(duk_hthread *thr, duk_small_uint_t stridx_cb) {
	duk_tval *tv_hnd;
	duk_int_t rc;

	DUK_ASSERT(thr != NULL);
	DUK_ASSERT(thr->heap != NULL);
	DUK_ASSERT_STRIDX_VALID(stridx_cb);

	if (thr->heap->augmenting_error) {
		DUK_D(DUK_DPRINT("recursive call to error augmentation, ignore"));
		return;
	}

	/*
	 *  Check whether or not we have an error handler.
	 *
	 *  We must be careful of not triggering an error when looking up the
	 *  property.  For instance, if the property is a getter, we don't want
	 *  to call it, only plain values are allowed.  The value, if it exists,
	 *  is not checked.  If the value is not a function, a TypeError happens
	 *  when it is called and that error replaces the original one.
	 */

	DUK_ASSERT_VALSTACK_SPACE(thr, 4); /* 3 entries actually needed below */

	/* [ ... errval ] */

	if (thr->builtins[DUK_BIDX_DUKTAPE] == NULL) {
		/* When creating built-ins, some of the built-ins may not be set
		 * and we want to tolerate that when throwing errors.
		 */
		DUK_DD(DUK_DDPRINT("error occurred when DUK_BIDX_DUKTAPE is NULL, ignoring"));
		return;
	}
	tv_hnd = duk_hobject_find_entry_tval_ptr_stridx(thr->heap, thr->builtins[DUK_BIDX_DUKTAPE], stridx_cb);
	if (tv_hnd == NULL) {
		DUK_DD(DUK_DDPRINT("error handler does not exist or is not a plain value: %!T", (duk_tval *) tv_hnd));
		return;
	}
	DUK_DDD(DUK_DDDPRINT("error handler dump (callability not checked): %!T", (duk_tval *) tv_hnd));

lib/JavaScript/Embedded/C/lib/duktape.c  view on Meta::CPAN

		return DUK__RETHAND_RESTART;
	}

#if defined(DUK_USE_COROUTINE_SUPPORT)
	DUK_DD(DUK_DDPRINT("no calling activation, thread finishes (similar to yield)"));

	DUK_ASSERT(thr->resumer != NULL);
	DUK_ASSERT(thr->resumer->callstack_top >= 2); /* ECMAScript activation + Duktape.Thread.resume() activation */
	DUK_ASSERT(thr->resumer->callstack_curr != NULL);
	DUK_ASSERT(thr->resumer->callstack_curr->parent != NULL);
	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr) != NULL &&
	           DUK_HOBJECT_IS_NATFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr)) &&
	           ((duk_hnatfunc *) DUK_ACT_GET_FUNC(thr->resumer->callstack_curr))->func ==
	               duk_bi_thread_resume); /* Duktape.Thread.resume() */
	DUK_ASSERT(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr->parent) != NULL &&
	           DUK_HOBJECT_IS_COMPFUNC(DUK_ACT_GET_FUNC(thr->resumer->callstack_curr->parent))); /* an ECMAScript function */
	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_RUNNING);
	DUK_ASSERT(thr->resumer->state == DUK_HTHREAD_STATE_RESUMED);

	resumer = thr->resumer;

	/* Share yield longjmp handler.
	 *
	 * This sequence of steps is a bit fragile (see GH-1845):
	 * - We need the return value from 'thr' (resumed thread) value stack.
	 *   The termination unwinds its value stack, losing the value.
	 * - We need a refcounted reference for 'thr', which may only exist
	 *   in the caller value stack.  We can't unwind or reconfigure the
	 *   caller's value stack without potentially freeing 'thr'.
	 *
	 * Current approach is to capture the 'thr' return value and store
	 * a reference to 'thr' in the caller value stack temporarily.  This
	 * keeps 'thr' reachable until final yield/return handling which
	 * removes the references atomatically.
	 */

	DUK_ASSERT(thr->valstack_top - 1 >= thr->valstack_bottom);
	duk_hthread_activation_unwind_norz(resumer); /* May remove last reference to 'thr', but is NORZ. */
	duk_push_tval(resumer, thr->valstack_top - 1); /* Capture return value, side effect free. */
	duk_push_hthread(resumer, thr); /* Make 'thr' reachable again, before side effects. */

	duk_hthread_terminate(thr); /* Updates thread state, minimizes its allocations. */
	thr->resumer = NULL;
	DUK_HTHREAD_DECREF(thr, resumer);
	DUK_ASSERT(thr->state == DUK_HTHREAD_STATE_TERMINATED);

	resumer->state = DUK_HTHREAD_STATE_RUNNING;
	DUK_HEAP_SWITCH_THREAD(thr->heap, resumer);

	DUK_ASSERT(resumer->valstack_top - 2 >= resumer->valstack_bottom);
	duk__handle_yield(thr, resumer, resumer->valstack_top - 2);
	thr = NULL; /* 'thr' invalidated by call */

#if 0
	thr = resumer;  /* not needed */
#endif

	DUK_DD(DUK_DDPRINT("-> return not caught, thread terminated; handle like yield, restart execution in resumer"));
	return DUK__RETHAND_RESTART;
#else
	/* Without coroutine support this case should never happen. */
	DUK_ERROR_INTERNAL(thr);
	DUK_WO_NORETURN(return 0;);
#endif
}

/*
 *  Executor interrupt handling
 *
 *  The handler is called whenever the interrupt countdown reaches zero
 *  (or below).  The handler must perform whatever checks are activated,
 *  e.g. check for cumulative step count to impose an execution step
 *  limit or check for breakpoints or other debugger interaction.
 *
 *  When the actions are done, the handler must reinit the interrupt
 *  init and counter values.  The 'init' value must indicate how many
 *  bytecode instructions are executed before the next interrupt.  The
 *  counter must interface with the bytecode executor loop.  Concretely,
 *  the new init value is normally one higher than the new counter value.
 *  For instance, to execute exactly one bytecode instruction the init
 *  value is set to 1 and the counter to 0.  If an error is thrown by the
 *  interrupt handler, the counters are set to the same value (e.g. both
 *  to 0 to cause an interrupt when the next bytecode instruction is about
 *  to be executed after error handling).
 *
 *  Maintaining the init/counter value properly is important for accurate
 *  behavior.  For instance, executor step limit needs a cumulative step
 *  count which is simply computed as a sum of 'init' values.  This must
 *  work accurately even when single stepping.
 */

#if defined(DUK_USE_INTERRUPT_COUNTER)

#define DUK__INT_NOACTION 0 /* no specific action, resume normal execution */
#define DUK__INT_RESTART  1 /* must "goto restart_execution", e.g. breakpoints changed */

#if defined(DUK_USE_DEBUGGER_SUPPORT)
DUK_LOCAL void duk__interrupt_handle_debugger(duk_hthread *thr, duk_bool_t *out_immediate, duk_small_uint_t *out_interrupt_retval) {
	duk_activation *act;
	duk_breakpoint *bp;
	duk_breakpoint **bp_active;
	duk_uint_fast32_t line = 0;
	duk_bool_t process_messages;
	duk_bool_t processed_messages = 0;

	DUK_ASSERT(thr->heap->dbg_processing == 0); /* don't re-enter e.g. during Eval */

	act = thr->callstack_curr;
	DUK_ASSERT(act != NULL);

	/* It might seem that replacing 'thr->heap' with just 'heap' below
	 * might be a good idea, but it increases code size slightly
	 * (probably due to unnecessary spilling) at least on x64.
	 */

	/*
	 *  Single opcode step check
	 */

	if (thr->heap->dbg_pause_flags & DUK_PAUSE_FLAG_ONE_OPCODE_ACTIVE) {
		DUK_D(DUK_DPRINT("PAUSE TRIGGERED by one opcode step"));

lib/JavaScript/Embedded/C/lib/duktape.c  view on Meta::CPAN

		if (duk_hobject_enumerator_next(thr, 0 /*get_value*/)) {
			/* [ ... enum ] -> [ ... next_key ] */
			DUK_DDD(DUK_DDDPRINT("enum active, next key is %!T, skip jump slot ", (duk_tval *) duk_get_tval(thr, -1)));
			pc_skip = 1;
		} else {
			/* [ ... enum ] -> [ ... ] */
			DUK_DDD(DUK_DDDPRINT("enum finished, execute jump slot"));
			DUK_ASSERT(DUK_TVAL_IS_UNDEFINED(thr->valstack_top)); /* valstack policy */
			thr->valstack_top++;
		}
		duk_replace(thr, (duk_idx_t) b);
	} else {
		/* 'null' enumerator case -> behave as with an empty enumerator */
		DUK_ASSERT(duk_is_null(thr, (duk_idx_t) c));
		DUK_DDD(DUK_DDDPRINT("enum is null, execute jump slot"));
	}

	return pc_skip;
}

/*
 *  Call handling helpers.
 */

DUK_LOCAL duk_bool_t duk__executor_handle_call(duk_hthread *thr, duk_idx_t idx, duk_idx_t nargs, duk_small_uint_t call_flags) {
	duk_bool_t rc;

	duk_set_top_unsafe(thr, (duk_idx_t) (idx + nargs + 2)); /* [ ... func this arg1 ... argN ] */

	/* Attempt an Ecma-to-Ecma call setup.  If the call
	 * target is (directly or indirectly) Reflect.construct(),
	 * the call may change into a constructor call on the fly.
	 */
	rc = (duk_bool_t) duk_handle_call_unprotected(thr, idx, call_flags);
	if (rc != 0) {
		/* Ecma-to-ecma call possible, may or may not
		 * be a tail call.  Avoid C recursion by
		 * reusing current executor instance.
		 */
		DUK_DDD(DUK_DDDPRINT("ecma-to-ecma call setup possible, restart execution"));
		/* curr_pc synced by duk_handle_call_unprotected() */
		DUK_ASSERT(thr->ptr_curr_pc == NULL);
		return rc;
	} else {
		/* Call was handled inline. */
	}
	DUK_ASSERT(thr->ptr_curr_pc != NULL);
	return rc;
}

/*
 *  ECMAScript bytecode executor.
 *
 *  Resume execution for the current thread from its current activation.
 *  Returns when execution would return from the entry level activation,
 *  leaving a single return value on top of the stack.  Function calls
 *  and thread resumptions are handled internally.  If an error occurs,
 *  a longjmp() with type DUK_LJ_TYPE_THROW is called on the entry level
 *  setjmp() jmpbuf.
 *
 *  ECMAScript function calls and coroutine resumptions are handled
 *  internally (by the outer executor function) without recursive C calls.
 *  Other function calls are handled using duk_handle_call(), increasing
 *  C recursion depth.
 *
 *  Abrupt completions (= long control tranfers) are handled either
 *  directly by reconfiguring relevant stacks and restarting execution,
 *  or via a longjmp.  Longjmp-free handling is preferable for performance
 *  (especially Emscripten performance), and is used for: break, continue,
 *  and return.
 *
 *  For more detailed notes, see doc/execution.rst.
 *
 *  Also see doc/code-issues.rst for discussion of setjmp(), longjmp(),
 *  and volatile.
 */

/* Presence of 'fun' is config based, there's a marginal performance
 * difference and the best option is architecture dependent.
 */
#if defined(DUK_USE_EXEC_FUN_LOCAL)
#define DUK__FUN() fun
#else
#define DUK__FUN() ((duk_hcompfunc *) DUK_ACT_GET_FUNC((thr)->callstack_curr))
#endif

/* Strict flag. */
#define DUK__STRICT() ((duk_small_uint_t) DUK_HOBJECT_HAS_STRICT((duk_hobject *) DUK__FUN()))

/* Reg/const access macros: these are very footprint and performance sensitive
 * so modify with care.  Arguments are sometimes evaluated multiple times which
 * is not ideal.
 */
#define DUK__REG(x)    (*(thr->valstack_bottom + (x)))
#define DUK__REGP(x)   (thr->valstack_bottom + (x))
#define DUK__CONST(x)  (*(consts + (x)))
#define DUK__CONSTP(x) (consts + (x))

/* Reg/const access macros which take the 32-bit instruction and avoid an
 * explicit field decoding step by using shifts and masks.  These must be
 * kept in sync with duk_js_bytecode.h.  The shift/mask values are chosen
 * so that 'ins' can be shifted and masked and used as a -byte- offset
 * instead of a duk_tval offset which needs further shifting (which is an
 * issue on some, but not all, CPUs).
 */
#define DUK__RCBIT_B DUK_BC_REGCONST_B
#define DUK__RCBIT_C DUK_BC_REGCONST_C
#if defined(DUK_USE_EXEC_REGCONST_OPTIMIZE)
#if defined(DUK_USE_PACKED_TVAL)
#define DUK__TVAL_SHIFT 3 /* sizeof(duk_tval) == 8 */
#else
#define DUK__TVAL_SHIFT 4 /* sizeof(duk_tval) == 16; not always the case so also asserted for */
#endif
#define DUK__SHIFT_A         (DUK_BC_SHIFT_A - DUK__TVAL_SHIFT)
#define DUK__SHIFT_B         (DUK_BC_SHIFT_B - DUK__TVAL_SHIFT)
#define DUK__SHIFT_C         (DUK_BC_SHIFT_C - DUK__TVAL_SHIFT)
#define DUK__SHIFT_BC        (DUK_BC_SHIFT_BC - DUK__TVAL_SHIFT)
#define DUK__MASK_A          (DUK_BC_UNSHIFTED_MASK_A << DUK__TVAL_SHIFT)
#define DUK__MASK_B          (DUK_BC_UNSHIFTED_MASK_B << DUK__TVAL_SHIFT)
#define DUK__MASK_C          (DUK_BC_UNSHIFTED_MASK_C << DUK__TVAL_SHIFT)
#define DUK__MASK_BC         (DUK_BC_UNSHIFTED_MASK_BC << DUK__TVAL_SHIFT)



( run in 0.903 second using v1.01-cache-2.11-cpan-3d66aa2751a )