]> granicus.if.org Git - vim/commitdiff
patch 7.4.1102 v7.4.1102
authorBram Moolenaar <Bram@vim.org>
Sat, 16 Jan 2016 14:40:53 +0000 (15:40 +0100)
committerBram Moolenaar <Bram@vim.org>
Sat, 16 Jan 2016 14:40:53 +0000 (15:40 +0100)
Problem:    Debugger has no stack backtrace support.
Solution:   Add "backtrace", "frame", "up" and "down" commands. (Alberto
            Fanjul, closes #433)

runtime/doc/repeat.txt
src/eval.c
src/ex_cmds2.c
src/globals.h
src/testdir/Make_all.mak
src/testdir/test108.in [new file with mode: 0644]
src/testdir/test108.ok [new file with mode: 0644]
src/version.c

index 50431d352ad7397aa9899330a1cee684d0093247..b3132a9002e7a7d3dcc1f34989cab5cc79ce83f0 100644 (file)
@@ -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*
index e97ddc47812b69f5df17718b48227c630d58e85c..1e2a0419e6bb07666d63cb67890a37c9be7b407f 100644 (file)
@@ -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,15 +21757,41 @@ 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);
     return NULL;
 }
 
+/*
+ * 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.
index 05baa7e99eb5cd8d871d18ec2a6be42f48ca1040..676cf117b42af4404dfafffde23b879112c31d62 100644 (file)
@@ -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".
  */
index 0931466e94a9c4088a39b56b027ce0ec0bafc99c..d6abc67eda39aad168ac491c069cc65418d6b9be 100644 (file)
@@ -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
index 6af9b84b04eaeb4fc681ed0b968dab8548db87a9..33fc3451b71e56cc55f34929017f25119ea0b205 100644 (file)
@@ -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 (file)
index 0000000..b442cf7
--- /dev/null
@@ -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 (file)
index 0000000..6315edc
--- /dev/null
@@ -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
+
index 1abb93e0f9f178f58311196a07eaaa892bbe1770..947d3c8749be9e8210ff73de1f8277452bb7299a 100644 (file)
@@ -741,6 +741,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    1102,
 /**/
     1101,
 /**/