DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line) { #else DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) { #endif duk_context *ctx = (duk_context *) thr; duk_bool_t double_error = thr->heap->handling_error; #ifdef DUK_USE_VERBOSE_ERRORS DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld", (long) code, (const char *) msg, (const char *) filename, (long) line)); #else DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld", (long) code)); #endif DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); thr->heap->handling_error = 1; /* * Create and push an error object onto the top of stack. * If a "double error" occurs, use a fixed error instance * to avoid further trouble. */ /* XXX: if attempt to push beyond allocated valstack, this double fault * handling fails miserably. We should really write the double error * directly to thr->heap->lj.value1 and avoid valstack use entirely. */ if (double_error) { if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_D(DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance")); duk_push_hobject_bidx(ctx, DUK_BIDX_DOUBLE_ERROR); } else { DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance " "-> push the error code as a number")); duk_push_int(ctx, (duk_int_t) code); } } else { /* Error object is augmented at its creation here. */ duk_require_stack(ctx, 1); /* XXX: unnecessary '%s' formatting here, but cannot use * 'msg' as a format string directly. */ #ifdef DUK_USE_VERBOSE_ERRORS duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, filename, line, "%s", (const char *) msg); #else duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, NULL, 0, NULL); #endif } /* * Augment error (throw time), unless alloc/double error */ if (double_error || code == DUK_ERR_ALLOC_ERROR) { DUK_D(DUK_DPRINT("alloc or double error: skip throw augmenting to avoid further trouble")); } else { #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)", (duk_tval *) duk_get_tval(ctx, -1))); duk_err_augment_error_throw(thr); #endif } /* * Finally, longjmp */ thr->heap->handling_error = 0; duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)", (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2)); duk_err_longjmp(thr); DUK_UNREACHABLE(); }
DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code, const char *msg, const char *filename, duk_int_t line) { #else DUK_INTERNAL void duk_err_create_and_throw(duk_hthread *thr, duk_errcode_t code) { #endif duk_context *ctx = (duk_context *) thr; duk_bool_t double_error = thr->heap->handling_error; #ifdef DUK_USE_VERBOSE_ERRORS DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld, msg=%s, filename=%s, line=%ld", (long) code, (const char *) msg, (const char *) filename, (long) line)); #else DUK_DD(DUK_DDPRINT("duk_err_create_and_throw(): code=%ld", (long) code)); #endif DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); thr->heap->handling_error = 1; if (!double_error) { /* Allow headroom for calls during error handling (see GH-191). * We allow space for 10 additional recursions, with one extra * for, e.g. a print() call at the deepest level. */ DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX); thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11; } DUK_ASSERT(thr->callstack_max == DUK_CALLSTACK_DEFAULT_MAX + DUK_CALLSTACK_GROW_STEP + 11); /* just making sure */ /* Sync so that augmentation sees up-to-date activations, NULL * thr->ptr_curr_pc so that it's not used if side effects occur * in augmentation or longjmp handling. */ duk_hthread_sync_and_null_currpc(thr); /* * Create and push an error object onto the top of stack. * If a "double error" occurs, use a fixed error instance * to avoid further trouble. */ /* XXX: if attempt to push beyond allocated valstack, this double fault * handling fails miserably. We should really write the double error * directly to thr->heap->lj.value1 and avoid valstack use entirely. */ if (double_error) { if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_D(DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance")); duk_push_hobject_bidx(ctx, DUK_BIDX_DOUBLE_ERROR); } else { DUK_D(DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance " "-> push the error code as a number")); duk_push_int(ctx, (duk_int_t) code); } } else { /* Error object is augmented at its creation here. */ duk_require_stack(ctx, 1); /* XXX: unnecessary '%s' formatting here, but cannot use * 'msg' as a format string directly. */ #ifdef DUK_USE_VERBOSE_ERRORS duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, filename, line, "%s", (const char *) msg); #else duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, NULL, 0, NULL); #endif } /* * Augment error (throw time), unless alloc/double error */ if (double_error || code == DUK_ERR_ALLOC_ERROR) { DUK_D(DUK_DPRINT("alloc or double error: skip throw augmenting to avoid further trouble")); } else { #if defined(DUK_USE_AUGMENT_ERROR_THROW) DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT (before throw augment)", (duk_tval *) duk_get_tval(ctx, -1))); duk_err_augment_error_throw(thr); #endif } /* * Finally, longjmp */ duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); thr->callstack_max = DUK_CALLSTACK_DEFAULT_MAX; /* reset callstack limit */ thr->heap->handling_error = 0; DUK_DDD(DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT (after throw augment)", (duk_tval *) &thr->heap->lj.value1, (duk_tval *) &thr->heap->lj.value2)); duk_err_longjmp(thr); DUK_UNREACHABLE(); }
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_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 */ }
void duk_err_create_and_throw(duk_hthread *thr, duk_uint32_t code, const char *msg, const char *filename, int line) { #else void duk_err_create_and_throw(duk_hthread *thr, duk_uint32_t code) { #endif duk_context *ctx = (duk_context *) thr; int double_error = thr->heap->handling_error; #ifdef DUK_USE_VERBOSE_ERRORS DUK_DDPRINT("duk_err_create_and_throw(): code=%d, msg=%s, filename=%s, line=%d", code, msg ? msg : "null", filename ? filename : "null", line); #else DUK_DDPRINT("duk_err_create_and_throw(): code=%d", code); #endif DUK_ASSERT(thr != NULL); DUK_ASSERT(ctx != NULL); thr->heap->handling_error = 1; /* * Create and push an error object onto the top of stack. * If a "double error" occurs, use a fixed error instance * to avoid further trouble. */ /* FIXME: if attempt to push beyond allocated valstack, this double fault * handling fails miserably. We should really write the double error * directly to thr->heap->lj.value1 and avoid valstack use entirely. */ if (double_error) { if (thr->builtins[DUK_BIDX_DOUBLE_ERROR]) { DUK_DPRINT("double fault detected -> push built-in fixed 'double error' instance"); duk_push_hobject(ctx, thr->builtins[DUK_BIDX_DOUBLE_ERROR]); } else { DUK_DPRINT("double fault detected; there is no built-in fixed 'double error' instance " "-> push the error code as a number"); duk_push_int(ctx, code); } } else { /* Error object is augmented at its creation here. */ duk_require_stack(ctx, 1); /* FIXME: unnecessary '%s' formatting here */ #ifdef DUK_USE_VERBOSE_ERRORS duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, filename, line, "%s", msg); #else duk_push_error_object_raw(ctx, code | DUK_ERRCODE_FLAG_NOBLAME_FILELINE, NULL, 0, NULL); #endif } /* * Call errhandler (unless error is an alloc error) * * Note: must back up the current jmpbuf if it is the shared bytecode * executor one (handled internally by the helper). */ if (double_error || code == DUK_ERR_ALLOC_ERROR) { DUK_DPRINT("alloc or double error: skip calling errhandler to avoid further trouble"); } else { duk__call_errhandler(thr); } /* * Finally, longjmp */ thr->heap->handling_error = 0; duk_err_setup_heap_ljstate(thr, DUK_LJ_TYPE_THROW); DUK_DDDPRINT("THROW ERROR (INTERNAL): %!iT, %!iT", &thr->heap->lj.value1, &thr->heap->lj.value2); duk_err_longjmp(thr); DUK_UNREACHABLE(); }