/*
 *  Error helpers
 */

#include "duk_internal.h"

/*
 *  Helper to walk the thread chain and see if there is an active error
 *  catcher.  Protected calls or finally blocks aren't considered catching.
 */

#if defined(DUK_USE_DEBUGGER_SUPPORT) && \
    (defined(DUK_USE_DEBUGGER_THROW_NOTIFY) || defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT))
DUK_LOCAL duk_bool_t duk__have_active_catcher(duk_hthread *thr) {
	/*
	 * XXX: As noted above, a protected API call won't be counted as a
	 * catcher. This is usually convenient, e.g. in the case of a top-
	 * level duk_pcall(), but may not always be desirable. Perhaps add an
	 * argument to treat them as catchers?
	 */

	duk_size_t i;

	DUK_ASSERT(thr != NULL);

	while (thr != NULL) {
		for (i = 0; i < thr->catchstack_top; i++) {
			duk_catcher *cat = thr->catchstack + i;
			if (DUK_CAT_HAS_CATCH_ENABLED(cat)) {
				return 1;  /* all we need to know */
			}
		}
		thr = thr->resumer;
	}
	return 0;
}
#endif  /* DUK_USE_DEBUGGER_SUPPORT && (DUK_USE_DEBUGGER_THROW_NOTIFY || DUK_USE_DEBUGGER_PAUSE_UNCAUGHT) */

/*
 *  Get prototype object for an integer error code.
 */

DUK_INTERNAL duk_hobject *duk_error_prototype_from_code(duk_hthread *thr, duk_errcode_t code) {
	switch (code) {
	case DUK_ERR_EVAL_ERROR:
		return thr->builtins[DUK_BIDX_EVAL_ERROR_PROTOTYPE];
	case DUK_ERR_RANGE_ERROR:
		return thr->builtins[DUK_BIDX_RANGE_ERROR_PROTOTYPE];
	case DUK_ERR_REFERENCE_ERROR:
		return thr->builtins[DUK_BIDX_REFERENCE_ERROR_PROTOTYPE];
	case DUK_ERR_SYNTAX_ERROR:
		return thr->builtins[DUK_BIDX_SYNTAX_ERROR_PROTOTYPE];
	case DUK_ERR_TYPE_ERROR:
		return thr->builtins[DUK_BIDX_TYPE_ERROR_PROTOTYPE];
	case DUK_ERR_URI_ERROR:
		return thr->builtins[DUK_BIDX_URI_ERROR_PROTOTYPE];
	case DUK_ERR_ERROR:
	default:
		return thr->builtins[DUK_BIDX_ERROR_PROTOTYPE];
	}
}

/*
 *  Exposed helper for setting up heap longjmp state.
 */

DUK_INTERNAL void duk_err_setup_heap_ljstate(duk_hthread *thr, duk_small_int_t lj_type) {
#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.  Errors are not intercepted when debug
	 * message loop is active (e.g. for Eval).
	 */

	/* XXX: Allow customizing the pause and notify behavior at runtime
	 * using debugger runtime flags.  For now the behavior is fixed using
	 * config options.
	 */
#if defined(DUK_USE_DEBUGGER_THROW_NOTIFY) || defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT)
	if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap) &&
	    !thr->heap->dbg_processing &&
	    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);

#if defined(DUK_USE_DEBUGGER_THROW_NOTIFY)
		/* Report it to the debug client */
		duk_debug_send_throw(thr, fatal);
#endif

#if defined(DUK_USE_DEBUGGER_PAUSE_UNCAUGHT)
		if (fatal) {
			DUK_D(DUK_DPRINT("throw will be fatal, halt before longjmp"));
			duk_debug_halt_execution(thr, 1 /*use_prev_pc*/);
		}
#endif
	}

 skip_throw_intercept:
#endif  /* DUK_USE_DEBUGGER_THROW_NOTIFY || DUK_USE_DEBUGGER_PAUSE_UNCAUGHT */
#endif  /* DUK_USE_DEBUGGER_SUPPORT */

	thr->heap->lj.type = lj_type;

	DUK_ASSERT(thr->valstack_top > thr->valstack);
	DUK_TVAL_SET_TVAL_UPDREF(thr, &thr->heap->lj.value1, thr->valstack_top - 1);  /* side effects */

	duk_pop((duk_context *) thr);
}
