summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBram Moolenaar <Bram@vim.org>2016-01-16 15:40:53 +0100
committerBram Moolenaar <Bram@vim.org>2016-01-16 15:40:53 +0100
commitf1f60f859cdbb2638b3662ccf7b1d179865fe7dc (patch)
tree739551087fb1b9948568366dc5d6187861dbcaf5
parente39b3d9fb4e4006684c33847d1ef6a0d742699dd (diff)
downloadvim-git-f1f60f859cdbb2638b3662ccf7b1d179865fe7dc.tar.gz
patch 7.4.1102v7.4.1102
Problem: Debugger has no stack backtrace support. Solution: Add "backtrace", "frame", "up" and "down" commands. (Alberto Fanjul, closes #433)
-rw-r--r--runtime/doc/repeat.txt32
-rw-r--r--src/eval.c33
-rw-r--r--src/ex_cmds2.c150
-rw-r--r--src/globals.h1
-rw-r--r--src/testdir/Make_all.mak1
-rw-r--r--src/testdir/test108.in87
-rw-r--r--src/testdir/test108.ok84
-rw-r--r--src/version.c2
8 files changed, 382 insertions, 8 deletions
diff --git a/runtime/doc/repeat.txt b/runtime/doc/repeat.txt
index 50431d352..b3132a900 100644
--- a/runtime/doc/repeat.txt
+++ b/runtime/doc/repeat.txt
@@ -1,4 +1,4 @@
-*repeat.txt* For Vim version 7.4. Last change: 2015 Apr 13
+*repeat.txt* For Vim version 7.4. Last change: 2016 Jan 16
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -483,16 +483,44 @@ Additionally, these commands can be used:
finish Finish the current script or user function and come
back to debug mode for the command after the one that
sourced or called it.
+ *>bt*
+ *>backtrace*
+ *>where*
+ backtrace Show the call stacktrace for current debugging session.
+ bt
+ where
+ *>frame*
+ frame N Goes to N bactrace level. + and - signs make movement
+ relative. E.g., ":frame +3" goes three frames up.
+ *>up*
+ up Goes one level up from call stacktrace.
+ *>down*
+ down Goes one level down from call stacktrace.
About the additional commands in debug mode:
- There is no command-line completion for them, you get the completion for the
normal Ex commands only.
-- You can shorten them, up to a single character: "c", "n", "s" and "f".
+- You can shorten them, up to a single character, unless more then one command
+ starts with the same letter. "f" stands for "finish", use "fr" for "frame".
- Hitting <CR> will repeat the previous one. When doing another command, this
is reset (because it's not clear what you want to repeat).
- When you want to use the Ex command with the same name, prepend a colon:
":cont", ":next", ":finish" (or shorter).
+The backtrace shows the hierarchy of function calls, e.g.:
+ >bt ~
+ 3 function One[3] ~
+ 2 Two[3] ~
+ ->1 Three[3] ~
+ 0 Four ~
+ line 1: let four = 4 ~
+
+The "->" points to the current frame. Use "up", "down" and "frame N" to
+select another frame.
+
+In the current frame you can evaluate the local function variables. There is
+no way to see the command at the current line yet.
+
DEFINING BREAKPOINTS
*:breaka* *:breakadd*
diff --git a/src/eval.c b/src/eval.c
index e97ddc478..1e2a0419e 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -812,6 +812,7 @@ static char_u *get_tv_string_buf_chk __ARGS((typval_T *varp, char_u *buf));
static dictitem_T *find_var __ARGS((char_u *name, hashtab_T **htp, int no_autoload));
static dictitem_T *find_var_in_ht __ARGS((hashtab_T *ht, int htname, char_u *varname, int no_autoload));
static hashtab_T *find_var_ht __ARGS((char_u *name, char_u **varname));
+static funccall_T *get_funccal __ARGS((void));
static void vars_clear_ext __ARGS((hashtab_T *ht, int free_val));
static void delete_var __ARGS((hashtab_T *ht, hashitem_T *hi));
static void list_one_var __ARGS((dictitem_T *v, char_u *prefix, int *first));
@@ -21735,7 +21736,7 @@ find_var_ht(name, varname)
if (current_funccal == NULL)
return &globvarht; /* global variable */
- return &current_funccal->l_vars.dv_hashtab; /* l: variable */
+ return &get_funccal()->l_vars.dv_hashtab; /* l: variable */
}
*varname = name + 2;
if (*name == 'g') /* global variable */
@@ -21756,9 +21757,9 @@ find_var_ht(name, varname)
if (*name == 'v') /* v: variable */
return &vimvarht;
if (*name == 'a' && current_funccal != NULL) /* function argument */
- return &current_funccal->l_avars.dv_hashtab;
+ return &get_funccal()->l_avars.dv_hashtab;
if (*name == 'l' && current_funccal != NULL) /* local function variable */
- return &current_funccal->l_vars.dv_hashtab;
+ return &get_funccal()->l_vars.dv_hashtab;
if (*name == 's' /* script variable */
&& current_SID > 0 && current_SID <= ga_scripts.ga_len)
return &SCRIPT_VARS(current_SID);
@@ -21766,6 +21767,32 @@ find_var_ht(name, varname)
}
/*
+ * Get function call environment based on bactrace debug level
+ */
+ static funccall_T *
+get_funccal()
+{
+ int i;
+ funccall_T *funccal;
+ funccall_T *temp_funccal;
+
+ funccal = current_funccal;
+ if (debug_backtrace_level > 0)
+ {
+ for (i = 0; i < debug_backtrace_level; i++)
+ {
+ temp_funccal = funccal->caller;
+ if (temp_funccal)
+ funccal = temp_funccal;
+ else
+ /* backtrace level overflow. reset to max */
+ debug_backtrace_level = i;
+ }
+ }
+ return funccal;
+}
+
+/*
* Get the string value of a (global/local) variable.
* Note: see get_tv_string() for how long the pointer remains valid.
* Returns NULL when it doesn't exist.
diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c
index 05baa7e99..676cf117b 100644
--- a/src/ex_cmds2.c
+++ b/src/ex_cmds2.c
@@ -68,6 +68,10 @@ typedef struct sn_prl_S
#if defined(FEAT_EVAL) || defined(PROTO)
static int debug_greedy = FALSE; /* batch mode debugging: don't save
and restore typeahead. */
+static int get_maxbacktrace_level(void);
+static void do_setdebugtracelevel(char_u *arg);
+static void do_checkbacktracelevel(void);
+static void do_showbacktrace(char_u *cmd);
/*
* do_debug(): Debug mode.
@@ -101,6 +105,10 @@ do_debug(cmd)
#define CMD_FINISH 4
#define CMD_QUIT 5
#define CMD_INTERRUPT 6
+#define CMD_BACKTRACE 7
+#define CMD_FRAME 8
+#define CMD_UP 9
+#define CMD_DOWN 10
#ifdef ALWAYS_USE_GUI
/* Can't do this when there is no terminal for input/output. */
@@ -178,6 +186,7 @@ do_debug(cmd)
# endif
cmdline_row = msg_row;
+ msg_starthere();
if (cmdline != NULL)
{
/* If this is a debug command, set "last_cmd".
@@ -197,8 +206,18 @@ do_debug(cmd)
case 's': last_cmd = CMD_STEP;
tail = "tep";
break;
- case 'f': last_cmd = CMD_FINISH;
- tail = "inish";
+ case 'f':
+ last_cmd = 0;
+ if (p[1] == 'r')
+ {
+ last_cmd = CMD_FRAME;
+ tail = "rame";
+ }
+ else
+ {
+ last_cmd = CMD_FINISH;
+ tail = "inish";
+ }
break;
case 'q': last_cmd = CMD_QUIT;
tail = "uit";
@@ -206,6 +225,21 @@ do_debug(cmd)
case 'i': last_cmd = CMD_INTERRUPT;
tail = "nterrupt";
break;
+ case 'b': last_cmd = CMD_BACKTRACE;
+ if (p[1] == 't')
+ tail = "t";
+ else
+ tail = "acktrace";
+ break;
+ case 'w': last_cmd = CMD_BACKTRACE;
+ tail = "here";
+ break;
+ case 'u': last_cmd = CMD_UP;
+ tail = "p";
+ break;
+ case 'd': last_cmd = CMD_DOWN;
+ tail = "own";
+ break;
default: last_cmd = 0;
}
if (last_cmd != 0)
@@ -217,7 +251,7 @@ do_debug(cmd)
++p;
++tail;
}
- if (ASCII_ISALPHA(*p))
+ if (ASCII_ISALPHA(*p) && last_cmd != CMD_FRAME)
last_cmd = 0;
}
}
@@ -250,7 +284,31 @@ do_debug(cmd)
/* Do not repeat ">interrupt" cmd, continue stepping. */
last_cmd = CMD_STEP;
break;
+ case CMD_BACKTRACE:
+ do_showbacktrace(cmd);
+ continue;
+ case CMD_FRAME:
+ if (*p == NUL)
+ {
+ do_showbacktrace(cmd);
+ }
+ else
+ {
+ p = skipwhite(p);
+ do_setdebugtracelevel(p);
+ }
+ continue;
+ case CMD_UP:
+ debug_backtrace_level++;
+ do_checkbacktracelevel();
+ continue;
+ case CMD_DOWN:
+ debug_backtrace_level--;
+ do_checkbacktracelevel();
+ continue;
}
+ /* Going out reset backtrace_level */
+ debug_backtrace_level = 0;
break;
}
@@ -285,6 +343,92 @@ do_debug(cmd)
debug_did_msg = TRUE;
}
+ static int
+get_maxbacktrace_level(void)
+{
+ char *p, *q;
+ int maxbacktrace = 1;
+
+ maxbacktrace = 0;
+ if (sourcing_name != NULL)
+ {
+ p = (char *)sourcing_name;
+ while ((q = strstr(p, "..")) != NULL)
+ {
+ p = q + 2;
+ maxbacktrace++;
+ }
+ }
+ return maxbacktrace;
+}
+
+ static void
+do_setdebugtracelevel(char_u *arg)
+{
+ int level;
+
+ level = atoi((char *)arg);
+ if (*arg == '+' || level < 0)
+ debug_backtrace_level += level;
+ else
+ debug_backtrace_level = level;
+
+ do_checkbacktracelevel();
+}
+
+ static void
+do_checkbacktracelevel(void)
+{
+ if (debug_backtrace_level < 0)
+ {
+ debug_backtrace_level = 0;
+ MSG(_("frame is zero"));
+ }
+ else
+ {
+ int max = get_maxbacktrace_level();
+
+ if (debug_backtrace_level > max)
+ {
+ debug_backtrace_level = max;
+ smsg((char_u *)_("frame at highest level: %d"), max);
+ }
+ }
+}
+
+ static void
+do_showbacktrace(char_u *cmd)
+{
+ char *cur;
+ char *next;
+ int i = 0;
+ int max = get_maxbacktrace_level();
+
+ if (sourcing_name != NULL)
+ {
+ cur = (char *)sourcing_name;
+ while (!got_int)
+ {
+ next = strstr(cur, "..");
+ if (next != NULL)
+ *next = NUL;
+ if (i == max - debug_backtrace_level)
+ smsg((char_u *)"->%d %s", max - i, cur);
+ else
+ smsg((char_u *)" %d %s", max - i, cur);
+ ++i;
+ if (next == NULL)
+ break;
+ *next = '.';
+ cur = next + 2;
+ }
+ }
+ if (sourcing_lnum != 0)
+ smsg((char_u *)_("line %ld: %s"), (long)sourcing_lnum, cmd);
+ else
+ smsg((char_u *)_("cmd: %s"), cmd);
+}
+
/*
* ":debug".
*/
diff --git a/src/globals.h b/src/globals.h
index 0931466e9..d6abc67ed 100644
--- a/src/globals.h
+++ b/src/globals.h
@@ -231,6 +231,7 @@ EXTERN int ex_nesting_level INIT(= 0); /* nesting level */
EXTERN int debug_break_level INIT(= -1); /* break below this level */
EXTERN int debug_did_msg INIT(= FALSE); /* did "debug mode" message */
EXTERN int debug_tick INIT(= 0); /* breakpoint change count */
+EXTERN int debug_backtrace_level INIT(= 0); /* breakpoint backtrace level */
# ifdef FEAT_PROFILE
EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */
# endif
diff --git a/src/testdir/Make_all.mak b/src/testdir/Make_all.mak
index 6af9b84b0..33fc3451b 100644
--- a/src/testdir/Make_all.mak
+++ b/src/testdir/Make_all.mak
@@ -89,6 +89,7 @@ SCRIPTS_ALL = \
test105.out \
test106.out \
test107.out \
+ test108.out \
test_argument_0count.out \
test_argument_count.out \
test_autocmd_option.out \
diff --git a/src/testdir/test108.in b/src/testdir/test108.in
new file mode 100644
index 000000000..b442cf7fc
--- /dev/null
+++ b/src/testdir/test108.in
@@ -0,0 +1,87 @@
+Tests for backtrace debug commands. vim: set ft=vim :
+
+STARTTEST
+:so small.vim
+:function! Foo()
+: let var1 = 1
+: let var2 = Bar(var1) + 9
+: return var2
+:endfunction
+:
+:function! Bar(var)
+: let var1 = 2 + a:var
+: let var2 = Bazz(var1) + 4
+: return var2
+:endfunction
+:
+:function! Bazz(var)
+: let var1 = 3 + a:var
+: let var3 = "another var"
+: return var1
+:endfunction
+:new
+:debuggreedy
+:redir => out
+:debug echo Foo()
+step
+step
+step
+step
+step
+step
+echo "- show backtrace:\n"
+backtrace
+echo "\nshow variables on different levels:\n"
+echo var1
+up
+back
+echo var1
+u
+bt
+echo var1
+echo "\n- undefined vars:\n"
+step
+frame 2
+echo "undefined var3 on former level:"
+echo var3
+fr 0
+echo "here var3 is defined with \"another var\":"
+echo var3
+step
+step
+step
+up
+echo "\nundefined var2 on former level"
+echo var2
+down
+echo "here var2 is defined with 10:"
+echo var2
+echo "\n- backtrace movements:\n"
+b
+echo "\nnext command cannot go down, we are on bottom\n"
+down
+up
+echo "\nnext command cannot go up, we are on top\n"
+up
+b
+echo "fil is not frame or finish, it is file"
+fil
+echo "\n- relative backtrace movement\n"
+fr -1
+frame
+fra +1
+fram
+echo "\n- go beyond limits does not crash\n"
+fr 100
+fra
+frame -40
+fram
+echo "\n- final result 19:"
+cont
+:0debuggreedy
+:redir END
+:$put =out
+:w! test.out
+:qa!
+ENDTEST
+
diff --git a/src/testdir/test108.ok b/src/testdir/test108.ok
new file mode 100644
index 000000000..6315edcc2
--- /dev/null
+++ b/src/testdir/test108.ok
@@ -0,0 +1,84 @@
+
+
+
+- show backtrace:
+
+ 2 function Foo[2]
+ 1 Bar[2]
+->0 Bazz
+line 2: let var3 = "another var"
+
+show variables on different levels:
+
+6
+ 2 function Foo[2]
+->1 Bar[2]
+ 0 Bazz
+line 2: let var3 = "another var"
+3
+->2 function Foo[2]
+ 1 Bar[2]
+ 0 Bazz
+line 2: let var3 = "another var"
+1
+
+- undefined vars:
+
+undefined var3 on former level:
+Error detected while processing function Foo[2]..Bar[2]..Bazz:
+line 3:
+E121: Undefined variable: var3
+E15: Invalid expression: var3
+here var3 is defined with "another var":
+another var
+
+undefined var2 on former level
+Error detected while processing function Foo[2]..Bar:
+line 3:
+E121: Undefined variable: var2
+E15: Invalid expression: var2
+here var2 is defined with 10:
+10
+
+- backtrace movements:
+
+ 1 function Foo[2]
+->0 Bar
+line 3: End of function
+
+next command cannot go down, we are on bottom
+
+frame is zero
+
+next command cannot go up, we are on top
+
+frame at highest level: 1
+->1 function Foo[2]
+ 0 Bar
+line 3: End of function
+fil is not frame or finish, it is file
+"[No Name]" --No lines in buffer--
+
+- relative backtrace movement
+
+ 1 function Foo[2]
+->0 Bar
+line 3: End of function
+->1 function Foo[2]
+ 0 Bar
+line 3: End of function
+
+- go beyond limits does not crash
+
+frame at highest level: 1
+->1 function Foo[2]
+ 0 Bar
+line 3: End of function
+frame is zero
+ 1 function Foo[2]
+->0 Bar
+line 3: End of function
+
+- final result 19:
+19
+
diff --git a/src/version.c b/src/version.c
index 1abb93e0f..947d3c874 100644
--- a/src/version.c
+++ b/src/version.c
@@ -742,6 +742,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
+ 1102,
+/**/
1101,
/**/
1100,