Browse Source

Implement callstack level for debugger commands

GetLocals, GetVar, PutVar, and Eval will now accept an optional negative
callstack offset specifying the function activation to operate on. This
offset has the same semantics as the argument of Duktape.act(): -1 is
the topmost activation, -2 is its caller, etc.
pull/412/head
Bruce Pascoe 9 years ago
parent
commit
426c6bc88f
  1. 1
      src/duk_api_debug.c
  2. 25
      src/duk_bi_global.c
  3. 105
      src/duk_debugger.c
  4. 4
      src/duk_heap.h

1
src/duk_api_debug.c

@ -66,6 +66,7 @@ DUK_EXTERNAL void duk_debugger_attach(duk_context *ctx,
heap->dbg_write_flush_cb = write_flush_cb;
heap->dbg_detached_cb = detached_cb;
heap->dbg_udata = udata;
heap->dbg_have_next_byte = 0;
/* Start in paused state. */
heap->dbg_processing = 0;

25
src/duk_bi_global.c

@ -429,6 +429,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
duk_hobject *outer_var_env;
duk_bool_t this_to_global = 1;
duk_small_uint_t comp_flags;
duk_int_t level = -2;
DUK_ASSERT_TOP(ctx, 1);
DUK_ASSERT(thr->callstack_top >= 1); /* at least this function exists */
@ -448,15 +449,27 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
return 1; /* return arg as-is */
}
#if defined(DUK_USE_DEBUGGER_SUPPORT)
/* XXX: level is used only by the debugger and should never be present
* for eval() called from Ecmascript code. is there a way to assert for
* this?
*/
level = -2;
if (duk_get_top(ctx) >= 2 && duk_is_number(ctx, 1)) {
level = duk_get_int(ctx, 1);
}
DUK_ASSERT(level <= -2);
#endif
/* [ source ] */
comp_flags = DUK_JS_COMPILE_FLAG_EVAL;
act_eval = thr->callstack + thr->callstack_top - 1; /* this function */
if (thr->callstack_top >= 2) {
if (thr->callstack_top >= (duk_size_t) -level) {
/* Have a calling activation, check for direct eval (otherwise
* assume indirect eval.
*/
act_caller = thr->callstack + thr->callstack_top - 2; /* caller */
act_caller = thr->callstack + thr->callstack_top + level; /* caller */
if ((act_caller->flags & DUK_ACT_FLAG_STRICT) &&
(act_eval->flags & DUK_ACT_FLAG_DIRECT_EVAL)) {
/* Only direct eval inherits strictness from calling code
@ -486,14 +499,14 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
act = thr->callstack + thr->callstack_top - 1; /* this function */
if (act->flags & DUK_ACT_FLAG_DIRECT_EVAL) {
DUK_ASSERT(thr->callstack_top >= 2);
act = thr->callstack + thr->callstack_top - 2; /* caller */
act = thr->callstack + thr->callstack_top + level; /* caller */
if (act->lex_env == NULL) {
DUK_ASSERT(act->var_env == NULL);
DUK_DDD(DUK_DDDPRINT("delayed environment initialization"));
/* this may have side effects, so re-lookup act */
duk_js_init_activation_environment_records_delayed(thr, act);
act = thr->callstack + thr->callstack_top - 2;
act = thr->callstack + thr->callstack_top + level;
}
DUK_ASSERT(act->lex_env != NULL);
DUK_ASSERT(act->var_env != NULL);
@ -508,7 +521,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
"var_env and lex_env to a fresh env, "
"this_binding to caller's this_binding"));
act = thr->callstack + thr->callstack_top - 2; /* caller */
act = thr->callstack + thr->callstack_top + level; /* caller */
act_lex_env = act->lex_env;
act = NULL; /* invalidated */
@ -559,7 +572,7 @@ DUK_INTERNAL duk_ret_t duk_bi_global_object_eval(duk_context *ctx) {
} else {
duk_tval *tv;
DUK_ASSERT(thr->callstack_top >= 2);
act = thr->callstack + thr->callstack_top - 2; /* caller */
act = thr->callstack + thr->callstack_top + level; /* caller */
tv = thr->valstack + act->idx_bottom - 1; /* this is just beneath bottom */
DUK_ASSERT(tv >= thr->valstack);
duk_push_tval(ctx, tv);

105
src/duk_debugger.c

@ -49,6 +49,7 @@ DUK_INTERNAL void duk_debug_do_detach(duk_heap *heap) {
heap->dbg_step_thread = NULL;
heap->dbg_step_csindex = 0;
heap->dbg_step_startline = 0;
heap->dbg_have_next_byte = 0;
/* Ensure there are no stale active breakpoint pointers.
* Breakpoint list is currently kept - we could empty it
@ -149,6 +150,21 @@ DUK_INTERNAL void duk_debug_skip_byte(duk_hthread *thr) {
* Debug connection read primitives
*/
/* Peek ahead in the stream one byte. */
DUK_INTERNAL uint8_t duk_debug_peek_byte(duk_hthread *thr) {
/* It is important not to call this if the last byte read was an EOM.
* Reading ahead in this scenario would cause unnecessary blocking if
* another message is not available.
*/
duk_uint8_t x;
x = duk_debug_read_byte(thr);
thr->heap->dbg_have_next_byte = 1;
thr->heap->dbg_next_byte = x;
return x;
}
/* Read fully. */
DUK_INTERNAL void duk_debug_read_bytes(duk_hthread *thr, duk_uint8_t *data, duk_size_t length) {
duk_heap *heap;
@ -165,7 +181,12 @@ DUK_INTERNAL void duk_debug_read_bytes(duk_hthread *thr, duk_uint8_t *data, duk_
goto fail;
}
/* NOTE: length may be zero */
p = data;
if (length >= 1 && heap->dbg_have_next_byte) {
heap->dbg_have_next_byte = 0;
*p++ = heap->dbg_next_byte;
}
for (;;) {
left = (duk_size_t) ((data + length) - p);
if (left == 0) {
@ -204,6 +225,11 @@ DUK_INTERNAL duk_uint8_t duk_debug_read_byte(duk_hthread *thr) {
return 0;
}
if (heap->dbg_have_next_byte) {
heap->dbg_have_next_byte = 0;
return heap->dbg_next_byte;
}
x = 0; /* just in case callback is broken and won't write 'x' */
DUK_ASSERT(heap->dbg_read_cb != NULL);
got = heap->dbg_read_cb(heap->dbg_udata, (char *) (&x), 1);
@ -1113,16 +1139,27 @@ DUK_LOCAL void duk__debug_handle_get_var(duk_hthread *thr, duk_heap *heap) {
duk_context *ctx = (duk_context *) thr;
duk_hstring *str;
duk_bool_t rc;
duk_int32_t level;
DUK_UNREF(heap);
DUK_D(DUK_DPRINT("debug command GetVar"));
str = duk_debug_read_hstring(thr); /* push to stack */
DUK_ASSERT(str != NULL);
if (duk_debug_peek_byte(thr) != 0x00) {
level = duk_debug_read_int(thr); /* optional callstack level */
if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) {
DUK_D(DUK_DPRINT("invalid callstack level for GetVar"));
duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level");
return;
}
} else {
level = -1;
}
if (thr->callstack_top > 0) {
rc = duk_js_getvar_activation(thr,
thr->callstack + thr->callstack_top - 1,
thr->callstack + thr->callstack_top + level,
str,
0);
} else {
@ -1150,6 +1187,7 @@ DUK_LOCAL void duk__debug_handle_put_var(duk_hthread *thr, duk_heap *heap) {
duk_context *ctx = (duk_context *) thr;
duk_hstring *str;
duk_tval *tv;
duk_int32_t level;
DUK_UNREF(heap);
DUK_D(DUK_DPRINT("debug command PutVar"));
@ -1159,10 +1197,20 @@ DUK_LOCAL void duk__debug_handle_put_var(duk_hthread *thr, duk_heap *heap) {
duk_debug_read_tval(thr); /* push to stack */
tv = duk_get_tval(ctx, -1);
DUK_ASSERT(tv != NULL);
if (duk_debug_peek_byte(thr) != 0x00) {
level = duk_debug_read_int(thr); /* optional callstack level */
if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) {
DUK_D(DUK_DPRINT("invalid callstack level for PutVar"));
duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level");
return;
}
} else {
level = -1;
}
if (thr->callstack_top > 0) {
duk_js_putvar_activation(thr,
thr->callstack + thr->callstack_top - 1,
thr->callstack + thr->callstack_top + level,
str,
tv,
0);
@ -1229,15 +1277,28 @@ DUK_LOCAL void duk__debug_handle_get_call_stack(duk_hthread *thr, duk_heap *heap
DUK_LOCAL void duk__debug_handle_get_locals(duk_hthread *thr, duk_heap *heap) {
duk_context *ctx = (duk_context *) thr;
duk_activation *curr_act;
duk_int32_t level;
duk_hstring *varname;
DUK_UNREF(heap);
duk_debug_write_reply(thr);
if (thr->callstack_top == 0) {
goto callstack_empty;
if (duk_debug_peek_byte(thr) != 0x00) {
level = duk_debug_read_int(thr); /* optional callstack level */
if (level >= 0 || -level > (duk_int32_t) thr->callstack_top) {
DUK_D(DUK_DPRINT("invalid callstack level for GetLocals"));
duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level");
return;
}
duk_debug_write_reply(thr);
} else {
duk_debug_write_reply(thr);
if (thr->callstack_top == 0) {
goto callstack_empty;
}
level = -1;
}
curr_act = thr->callstack + thr->callstack_top - 1;
curr_act = thr->callstack + thr->callstack_top + level;
/* XXX: several nice-to-have improvements here:
* - Use direct reads avoiding value stack operations
@ -1272,28 +1333,44 @@ DUK_LOCAL void duk__debug_handle_eval(duk_hthread *thr, duk_heap *heap) {
duk_small_uint_t call_flags;
duk_int_t call_ret;
duk_small_int_t eval_err;
duk_int32_t level;
DUK_UNREF(heap);
DUK_D(DUK_DPRINT("debug command Eval"));
/* The eval code must be executed within the current (topmost)
* activation. For now, use global object eval() function, with
* the eval considered a 'direct call to eval'.
/* The eval code must be executed within the specified activation.
* For now, use global object eval() function, with the eval considered
* a 'direct call to eval'.
*/
duk_push_c_function(ctx, duk_bi_global_object_eval, 1 /*nargs*/);
/* nargs == 2 so we can pass a callstack level to eval(). */
duk_push_c_function(ctx, duk_bi_global_object_eval, 2 /*nargs*/);
duk_push_undefined(ctx); /* 'this' binding shouldn't matter here */
(void) duk_debug_read_hstring(thr);
if (duk_debug_peek_byte(thr) != 0x00) {
level = duk_debug_read_int(thr); /* optional callstack level */
if (level >= 0 || -level > (duk_int32_t)thr->callstack_top) {
DUK_D(DUK_DPRINT("invalid callstack level for Eval"));
duk_debug_write_error_eom(thr, DUK_DBG_ERR_NOTFOUND, "invalid callstack level");
return;
}
}
else {
level = -1;
}
DUK_ASSERT(level < 0 && -level <= (duk_int32_t) thr->callstack_top);
duk_push_int(ctx, level - 1); /* compensate for eval() call */
/* [ ... eval "eval" eval_input ] */
/* [ ... eval "eval" eval_input level ] */
call_flags = DUK_CALL_FLAG_PROTECTED;
if (thr->callstack_top >= 1) {
if (thr->callstack_top >= (duk_size_t) -level) {
duk_activation *act;
duk_hobject *fun;
act = thr->callstack + thr->callstack_top - 1;
act = thr->callstack + thr->callstack_top + level;
fun = DUK_ACT_GET_FUNC(act);
if (fun != NULL && DUK_HOBJECT_IS_COMPILEDFUNCTION(fun)) {
/* Direct eval requires that there's a current
@ -1305,7 +1382,7 @@ DUK_LOCAL void duk__debug_handle_eval(duk_hthread *thr, duk_heap *heap) {
}
}
call_ret = duk_handle_call(thr, 1 /*num_stack_args*/, call_flags);
call_ret = duk_handle_call(thr, 2 /*num_stack_args*/, call_flags);
if (call_ret == DUK_EXEC_SUCCESS) {
eval_err = 0;

4
src/duk_heap.h

@ -447,6 +447,10 @@ struct duk_heap {
duk_uint32_t dbg_exec_counter; /* cumulative opcode execution count (overflows are OK) */
duk_uint32_t dbg_last_counter; /* value of dbg_exec_counter when we last did a Date-based check */
duk_double_t dbg_last_time; /* time when status/peek was last done (Date-based rate limit) */
/* Used to support single-byte stream lookahead. */
duk_bool_t dbg_have_next_byte;
duk_uint8_t dbg_next_byte;
#endif
/* string intern table (weak refs) */

Loading…
Cancel
Save