DUK_EXTERNAL void duk_debugger_cooperate(duk_context *ctx) { duk_hthread *thr; duk_bool_t processed_messages; thr = (duk_hthread *) ctx; DUK_ASSERT(ctx != NULL); DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); if (!DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) { return; } if (thr->callstack_top > 0 || thr->heap->dbg_processing) { /* Calling duk_debugger_cooperate() while Duktape is being * called into is not supported. This is not a 100% check * but prevents any damage in most cases. */ return; } thr->heap->dbg_processing = 1; processed_messages = duk_debug_process_messages(thr, 1 /*no_block*/); thr->heap->dbg_processing = 0; DUK_UNREF(processed_messages); }
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); }
/* Log frontend shared helper, magic value indicates log level. Provides * frontend functions: trace(), debug(), info(), warn(), error(), fatal(). * This needs to have small footprint, reasonable performance, minimal * memory churn, etc. */ DUK_INTERNAL duk_ret_t duk_bi_logger_prototype_log_shared(duk_context *ctx) { duk_hthread *thr = (duk_hthread *) ctx; duk_double_t now; duk_small_int_t entry_lev = duk_get_current_magic(ctx); duk_small_int_t logger_lev; duk_int_t nargs; duk_int_t i; duk_size_t tot_len; const duk_uint8_t *arg_str; duk_size_t arg_len; duk_uint8_t *buf, *p; const duk_uint8_t *q; duk_uint8_t date_buf[DUK_BI_DATE_ISO8601_BUFSIZE]; duk_size_t date_len; duk_small_int_t rc; DUK_ASSERT(entry_lev >= 0 && entry_lev <= 5); /* XXX: sanitize to printable (and maybe ASCII) */ /* XXX: better multiline */ /* * Logger arguments are: * * magic: log level (0-5) * this: logger * stack: plain log args * * We want to minimize memory churn so a two-pass approach * is used: first pass formats arguments and computes final * string length, second pass copies strings either into a * pre-allocated and reused buffer (short messages) or into a * newly allocated fixed buffer. If the backend function plays * nice, it won't coerce the buffer to a string (and thus * intern it). */ nargs = duk_get_top(ctx); /* [ arg1 ... argN this ] */ /* * Log level check */ duk_push_this(ctx); duk_get_prop_stridx(ctx, -1, DUK_STRIDX_LC_L); logger_lev = (duk_small_int_t) duk_get_int(ctx, -1); if (entry_lev < logger_lev) { return 0; } /* log level could be popped but that's not necessary */ now = duk_bi_date_get_now(ctx); duk_bi_date_format_timeval(now, date_buf); date_len = DUK_STRLEN((const char *) date_buf); duk_get_prop_stridx(ctx, -2, DUK_STRIDX_LC_N); duk_to_string(ctx, -1); DUK_ASSERT(duk_is_string(ctx, -1)); /* [ arg1 ... argN this loggerLevel loggerName ] */ /* * Pass 1 */ /* Line format: <time> <entryLev> <loggerName>: <msg> */ tot_len = 0; tot_len += 3 + /* separators: space, space, colon */ 3 + /* level string */ date_len + /* time */ duk_get_length(ctx, -1); /* loggerName */ for (i = 0; i < nargs; i++) { /* When formatting an argument to a string, errors may happen from multiple * causes. In general we want to catch obvious errors like a toLogString() * throwing an error, but we don't currently try to catch every possible * error. In particular, internal errors (like out of memory or stack) are * not caught. Also, we expect Error toString() to not throw an error. */ if (duk_is_object(ctx, i)) { /* duk_pcall_prop() may itself throw an error, but we're content * in catching the obvious errors (like toLogString() throwing an * error). */ duk_push_hstring_stridx(ctx, DUK_STRIDX_FMT); duk_dup(ctx, i); /* [ arg1 ... argN this loggerLevel loggerName 'fmt' arg ] */ /* call: this.fmt(arg) */ rc = duk_pcall_prop(ctx, -5 /*obj_index*/, 1 /*nargs*/); if (rc) { /* Keep the error as the result (coercing it might fail below, * but we don't catch that now). */ ; } duk_replace(ctx, i); } (void) duk_to_lstring(ctx, i, &arg_len); tot_len++; /* sep (even before first one) */ tot_len += arg_len; } /* * Pass 2 */ if (tot_len <= DUK_BI_LOGGER_SHORT_MSG_LIMIT) { duk_hbuffer_dynamic *h_buf; DUK_DDD(DUK_DDDPRINT("reuse existing small log message buffer, tot_len %ld", (long) tot_len)); /* We can assert for all buffer properties because user code * never has access to heap->log_buffer. */ DUK_ASSERT(thr != NULL); DUK_ASSERT(thr->heap != NULL); h_buf = thr->heap->log_buffer; DUK_ASSERT(h_buf != NULL); DUK_ASSERT(DUK_HBUFFER_HAS_DYNAMIC((duk_hbuffer *) h_buf)); DUK_ASSERT(DUK_HBUFFER_DYNAMIC_GET_ALLOC_SIZE(h_buf) == DUK_BI_LOGGER_SHORT_MSG_LIMIT); /* Set buffer 'visible size' to actual message length and * push it to the stack. */ DUK_HBUFFER_SET_SIZE((duk_hbuffer *) h_buf, tot_len); duk_push_hbuffer(ctx, (duk_hbuffer *) h_buf); buf = (duk_uint8_t *) DUK_HBUFFER_DYNAMIC_GET_DATA_PTR(thr->heap, h_buf); } else { DUK_DDD(DUK_DDDPRINT("use a one-off large log message buffer, tot_len %ld", (long) tot_len)); buf = (duk_uint8_t *) duk_push_fixed_buffer(ctx, tot_len); } DUK_ASSERT(buf != NULL); p = buf; DUK_MEMCPY((void *) p, (void *) date_buf, date_len); p += date_len; *p++ = (duk_uint8_t) DUK_ASC_SPACE; q = duk__log_level_strings + (entry_lev * 3); DUK_MEMCPY((void *) p, (void *) q, (duk_size_t) 3); p += 3; *p++ = (duk_uint8_t) DUK_ASC_SPACE; arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, -2, &arg_len); DUK_MEMCPY((void *) p, (const void *) arg_str, arg_len); p += arg_len; *p++ = (duk_uint8_t) DUK_ASC_COLON; for (i = 0; i < nargs; i++) { *p++ = (duk_uint8_t) DUK_ASC_SPACE; arg_str = (const duk_uint8_t *) duk_get_lstring(ctx, i, &arg_len); DUK_ASSERT(arg_str != NULL); DUK_MEMCPY((void *) p, (const void *) arg_str, arg_len); p += arg_len; } DUK_ASSERT(buf + tot_len == p); /* [ arg1 ... argN this loggerLevel loggerName buffer ] */ #if defined(DUK_USE_DEBUGGER_SUPPORT) && defined(DUK_USE_DEBUGGER_FWD_LOGGING) /* Do debugger forwarding before raw() because the raw() function * doesn't get the log level right now. */ if (DUK_HEAP_IS_DEBUGGER_ATTACHED(thr->heap)) { const char *log_buf; duk_size_t sz_buf; log_buf = (const char *) duk_get_buffer(ctx, -1, &sz_buf); DUK_ASSERT(log_buf != NULL); duk_debug_write_notify(thr, DUK_DBG_CMD_LOG); duk_debug_write_int(thr, (duk_int32_t) entry_lev); duk_debug_write_string(thr, (const char *) log_buf, sz_buf); duk_debug_write_eom(thr); } #endif /* Call this.raw(msg); look up through the instance allows user to override * the raw() function in the instance or in the prototype for maximum * flexibility. */ duk_push_hstring_stridx(ctx, DUK_STRIDX_RAW); duk_dup(ctx, -2); /* [ arg1 ... argN this loggerLevel loggerName buffer 'raw' buffer ] */ duk_call_prop(ctx, -6, 1); /* this.raw(buffer) */ return 0; }