Zsh Mailing List Archive
Messages sorted by: Reverse Date, Date, Thread, Author

Re: Getting source file and line number of a function.



On Mon, 28 Jul 2008 08:46:43 -0400
"Rocky Bernstein" <rocky.bernstein@xxxxxxxxx> wrote:
> Patch should be added. It's not the only way to do this and it changes
> the functrace (which could easily be addressed, so there might be some
> discussion.

I've finally got something that's at least consistent.  Whether it does
what is needed is another matter, but I suspect it's at least going in
the right direction.  It's a little different from Rocky's original.

Instead of hijacking $functrace, I've added another array,
$funcfiletrace.  I've made their behaviours' parallel one another,
i.e. they're always the same length.

What was really doing my brain in was working out what was supposed to
be relative to what, given that I wanted to keep $functrace doing what
it always did unless that really proved to be senseless (and I don't
think it did).  Eventually, I realised $functrace doesn't give you the
current context, it gives you the calling context (i.e. if you want the
current $LINENO or function name you get it some other way---the may be
more work to do here about the name part).  So I've made $funcfiletrace
do the same, except that it's the context where the function was
originally defined, not the point of the current call.  Example below.

In more detail, this modified version checks for a NULL scriptfilename,
sets and also restores it in some more places (in particular autoload
files), frees the filename from the Shfunc structure, tries to ensure
that that is NULL if there isn't a name (that meant tracking down some
more places where Shfunc's are created), handles NULL filenames in
parameter.c, and fixes up an additional oddity with autoload (we don't
know on the first execution of the function where the file is, so we
need to fix up funcstack when it's already in use).

I haven't done anything about the sourced script tracing yet.  It's
probably a good idea; we should obviously make sure it's consistent.

There could be more stuff I've missed.  For example, I haven't looked at
whether ksh-style autoloads need something different.  It's perfectly
possible we'll need additional variables in zsh/parameter.  It would be
nice to have some way of telling what sort of context we were in
(function defined in line or autloaded, sourced file, script).  For now
I'm happy if this looks like it's going the right way.

Anyway, here's an example script showing this in action:


## start
print Started functrace.zsh
zmodload zsh/parameter

print $LINENO; print $functrace; print $funcfiletrace

fn() {
    print Inside function $0
    print $LINENO; print $functrace; print $funcfiletrace
}

fn

fpath=(. $fpath)

echo 'print Inside $0
  print $LINENO; print $functrace; print $funcfiletrace
' >autofn

autoload autofn

autofn
autofn
## end


Output:


## start
Started functrace.zsh
4


Inside function fn
2
../functrace.zsh:11
../functrace.zsh:6
Inside autofn
2
../functrace.zsh:21
./autofn:0
Inside autofn
2
../functrace.zsh:22
./autofn:0
## end


Notes:
- (As I said above) only when we're inside a function do $functrace and
  $funcfiletrace come alive, and then they give the calling context.

- $functrace says where the function got called.  $funcfiletrace says where
  the function got defined.

- The line number 0 for the autoloaded function is not an error.  The
  file is autofn; this is a zsh autoload, so the line that started
  the definition was not in the file at all.  In other words, this
  is telling you the function was autoloaded from the entire contents of
  that file.  It's a relative path here because it got picked up from
  "." in fpath; that's an unusual case in practice, so although it's
  true that the file name could be ambiguous I haven't felt like
  sanitizing the path.

- The two autofn's are there to check the problem I noted above with
  the use of funcstack the first time.


Index: Src/exec.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/exec.c,v
retrieving revision 1.138
diff -u -r1.138 exec.c
--- Src/exec.c	7 Aug 2008 16:25:16 -0000	1.138
+++ Src/exec.c	9 Aug 2008 18:10:14 -0000
@@ -191,7 +191,7 @@
 parse_string(char *s)
 {
     Eprog p;
-    int oldlineno = lineno;
+    zlong oldlineno = lineno;
 
     lexsave();
     inpush(s, INP_LINENO, NULL);
@@ -1016,7 +1016,8 @@
     Wordcode next;
     wordcode code;
     int ret, cj, csp, ltype;
-    int old_pline_level, old_list_pipe, oldlineno;
+    int old_pline_level, old_list_pipe;
+    zlong oldlineno;
     /*
      * ERREXIT only forces the shell to exit if the last command in a &&
      * or || fails.  This is the case even if an earlier command is a
@@ -3961,6 +3962,8 @@
 	shf = (Shfunc) zalloc(sizeof(*shf));
 	shf->funcdef = prog;
 	shf->node.flags = 0;
+	shf->filename = ztrdup(scriptfilename);
+	shf->lineno = lineno;
 
 	if (!names) {
 	    /*
@@ -4059,15 +4062,24 @@
 execautofn(Estate state, UNUSED(int do_exec))
 {
     Shfunc shf;
-    char *oldscriptname;
+    char *oldscriptname, *oldscriptfilename;
 
     if (!(shf = loadautofn(state->prog->shf, 1, 0)))
 	return 1;
 
+    /*
+     * Probably we didn't know the filename where this function was
+     * defined yet.
+     */
+    if (funcstack && !funcstack->filename)
+	funcstack->filename = dupstring(shf->filename);
+
     oldscriptname = scriptname;
-    scriptname = dupstring(shf->node.nam);
+    oldscriptfilename = scriptfilename;
+    scriptname = scriptfilename = dupstring(shf->node.nam);
     execode(shf->funcdef, 1, 0);
     scriptname = oldscriptname;
+    scriptfilename = oldscriptfilename;
 
     return lastval;
 }
@@ -4078,11 +4090,12 @@
 {
     int noalias = noaliases, ksh = 1;
     Eprog prog;
+    char *fname;
 
     pushheap();
 
     noaliases = (shf->node.flags & PM_UNALIASED);
-    prog = getfpfunc(shf->node.nam, &ksh);
+    prog = getfpfunc(shf->node.nam, &ksh, &fname);
     noaliases = noalias;
 
     if (ksh == 1) {
@@ -4112,6 +4125,7 @@
 	    else
 		shf->funcdef = dupeprog(prog, 0);
 	    shf->node.flags &= ~PM_UNDEFINED;
+	    shf->filename = fname;
 	} else {
 	    VARARR(char, n, strlen(shf->node.nam) + 1);
 	    strcpy(n, shf->node.nam);
@@ -4123,6 +4137,7 @@
 		zwarn("%s: function not defined by file", n);
 		locallevel++;
 		popheap();
+		zsfree(fname);
 		return NULL;
 	    }
 	}
@@ -4133,6 +4148,7 @@
 	else
 	    shf->funcdef = dupeprog(stripkshdef(prog, shf->node.nam), 0);
 	shf->node.flags &= ~PM_UNDEFINED;
+	shf->filename = fname;
     }
     popheap();
 
@@ -4172,6 +4188,7 @@
 #ifdef MAX_FUNCTION_DEPTH
     static int funcdepth;
 #endif
+    Shfunc shf;
 
     pushheap();
 
@@ -4243,6 +4260,15 @@
     fstack.prev = funcstack;
     funcstack = &fstack;
 
+    if ((shf = (Shfunc) shfunctab->getnode(shfunctab, name))) {
+	fstack.flineno = shf->lineno;
+	fstack.filename = dupstring(shf->filename);
+    } else {
+	fstack.flineno = 0;
+	fstack.filename = dupstring(fstack.caller);
+    }
+    
+    
     if (prog->flags & EF_RUN) {
 	Shfunc shf;
 
@@ -4362,7 +4388,7 @@
 
 /**/
 Eprog
-getfpfunc(char *s, int *ksh)
+getfpfunc(char *s, int *ksh, char **fname)
 {
     char **pp, buf[PATH_MAX];
     off_t len;
@@ -4397,6 +4423,9 @@
 		    r = parse_string(d);
 		    scriptname = oldscriptname;
 
+		    if (fname)
+			*fname = ztrdup(buf);
+
 		    zfree(d, len + 1);
 
 		    return r;
Index: Src/hashtable.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/hashtable.c,v
retrieving revision 1.27
diff -u -r1.27 hashtable.c
--- Src/hashtable.c	1 Nov 2007 10:59:40 -0000	1.27
+++ Src/hashtable.c	9 Aug 2008 18:10:14 -0000
@@ -852,6 +852,7 @@
     zsfree(shf->node.nam);
     if (shf->funcdef)
 	freeeprog(shf->funcdef);
+    zsfree(shf->filename);
     zfree(shf, sizeof(struct shfunc));
 }
 
Index: Src/init.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/init.c,v
retrieving revision 1.91
diff -u -r1.91 init.c
--- Src/init.c	7 Aug 2008 16:25:16 -0000	1.91
+++ Src/init.c	9 Aug 2008 18:10:14 -0000
@@ -268,7 +268,7 @@
 		/* -c command */
 		cmd = *argv;
 		opts[INTERACTIVE] &= 1;
-		scriptname = ztrdup("zsh");
+		scriptname = scriptfilename = ztrdup("zsh");
 	    } else if (**argv == 'o') {
 		if (!*++*argv)
 		    argv++;
@@ -325,6 +325,7 @@
 	    }
 	    opts[INTERACTIVE] &= 1;
 	    argzero = *argv;
+	    scriptfilename = argzero;
 	    argv++;
 	}
 	while (*argv)
@@ -1051,10 +1052,12 @@
 source(char *s)
 {
     Eprog prog;
-    int tempfd = -1, fd, cj, oldlineno;
+    int tempfd = -1, fd, cj;
+    zlong oldlineno;
     int oldshst, osubsh, oloops;
     FILE *obshin;
     char *old_scriptname = scriptname, *us;
+    char *old_scriptfilename = scriptfilename;
     unsigned char *ocs;
     int ocsp;
     int otrap_return = trap_return, otrap_state = trap_state;
@@ -1087,6 +1090,7 @@
     loops  = 0;
     dosetopt(SHINSTDIN, 0, 1);
     scriptname = s;
+    scriptfilename = s;
 
     /*
      * The special return behaviour of traps shouldn't
@@ -1096,6 +1100,17 @@
     trap_state = TRAP_STATE_INACTIVE;
 
     sourcelevel++;
+    /* { */
+    /*   struct funcstack fstack; */
+    /*   fstack.name = dupstring("source"); */
+    /*   fstack.caller = dupstring(scriptfilename); */
+    /*   fstack.flineno = oldlineno; */
+    /*   fstack.lineno = oldlineno; */
+    /*   fstack.filename = NULL; */
+    /*   fstack.prev = funcstack; */
+    /*   funcstack = &fstack; */
+    /* } */
+    
     if (prog) {
 	pushheap();
 	errflag = 0;
@@ -1103,6 +1118,7 @@
 	popheap();
     } else
 	loop(0, 0);		     /* loop through the file to be sourced  */
+    /* funcstack = funcstack->prev; */
     sourcelevel--;
 
     trap_state = otrap_state;
@@ -1126,6 +1142,7 @@
     if (!exit_pending)
 	retflag = 0;
     scriptname = old_scriptname;
+    scriptfilename = old_scriptfilename;
     free(cmdstack);
     cmdstack = ocs;
     cmdsp = ocsp;
Index: Src/parse.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/parse.c,v
retrieving revision 1.70
diff -u -r1.70 parse.c
--- Src/parse.c	1 Jul 2008 18:38:40 -0000	1.70
+++ Src/parse.c	9 Aug 2008 18:10:15 -0000
@@ -720,7 +720,8 @@
 static int
 par_pline(int *complex)
 {
-    int p, line = lineno;
+    int p;
+    zlong line = lineno;
 
     p = ecadd(0);
 
@@ -1414,8 +1415,9 @@
 static void
 par_funcdef(void)
 {
-    int oecused = ecused, oldlineno = lineno, num = 0, onp, p, c = 0;
+    int oecused = ecused, num = 0, onp, p, c = 0;
     int so, oecssub = ecssub;
+    zlong oldlineno = lineno;
 
     lineno = 0;
     nocorrect = 1;
@@ -1646,7 +1648,8 @@
 	    p += nrediradd;
 	    sr += nrediradd;
 	} else if (tok == INOUTPAR) {
-	    int oldlineno = lineno, onp, so, oecssub = ecssub;
+	    zlong oldlineno = lineno;
+	    int onp, so, oecssub = ecssub;
 
 	    *complex = c;
 	    lineno = 0;
@@ -2860,7 +2863,8 @@
 	    return 1;
 	}
 	noaliases = (shf->node.flags & PM_UNALIASED);
-	if (!(prog = getfpfunc(shf->node.nam, NULL)) || prog == &dummy_eprog) {
+	if (!(prog = getfpfunc(shf->node.nam, NULL, NULL)) ||
+	    prog == &dummy_eprog) {
 	    noaliases = ona;
 	    zwarnnam(nam, "can't load function: %s", shf->node.nam);
 	    return 1;
Index: Src/utils.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/utils.c,v
retrieving revision 1.198
diff -u -r1.198 utils.c
--- Src/utils.c	31 Jul 2008 08:44:21 -0000	1.198
+++ Src/utils.c	9 Aug 2008 18:10:17 -0000
@@ -33,7 +33,10 @@
 /* name of script being sourced */
 
 /**/
-mod_export char *scriptname;
+mod_export char *scriptname;     /* is sometimes a function name */
+
+/**/
+mod_export char *scriptfilename;
 
 #ifdef MULTIBYTE_SUPPORT
 struct widechar_array {
Index: Src/zsh.h
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/zsh.h,v
retrieving revision 1.140
diff -u -r1.140 zsh.h
--- Src/zsh.h	7 Aug 2008 16:25:16 -0000	1.140
+++ Src/zsh.h	9 Aug 2008 18:10:18 -0000
@@ -1061,6 +1061,8 @@
 
 struct shfunc {
     struct hashnode node;
+    char *filename;             /* Name of file located in */
+    int lineno;			/* line number in above file */
     Eprog funcdef;		/* function definition    */
 };
 
@@ -1079,8 +1081,10 @@
 struct funcstack {
     Funcstack prev;		/* previous in stack */
     char *name;			/* name of function called */
+    char *filename;		/* file function resides in */
     char *caller;		/* name of caller */
-    int lineno;			/* line number in file */
+    zlong flineno;		/* line number in file */
+    zlong lineno;		/* line offset from beginning of function */
 };
 
 /* node in list of function call wrappers */
Index: Src/Modules/parameter.c
===================================================================
RCS file: /cvsroot/zsh/zsh/Src/Modules/parameter.c,v
retrieving revision 1.45
diff -u -r1.45 parameter.c
--- Src/Modules/parameter.c	5 Sep 2007 16:16:17 -0000	1.45
+++ Src/Modules/parameter.c	9 Aug 2008 18:10:18 -0000
@@ -286,7 +286,7 @@
 	zsfree(val);
 	return;
     }
-    shf = (Shfunc) zalloc(sizeof(*shf));
+    shf = (Shfunc) zshcalloc(sizeof(*shf));
     shf->funcdef = dupeprog(prog, 0);
     shf->node.flags = dis;
 
@@ -529,7 +529,35 @@
 	char *colonpair;
 
 	colonpair = zhalloc(strlen(f->caller) + (f->lineno > 9999 ? 24 : 6));
-	sprintf(colonpair, "%s:%d", f->caller, f->lineno);
+	sprintf(colonpair, "%s:%ld", f->caller, (long)f->lineno);
+
+	*p = colonpair;
+    }
+    *p = NULL;
+
+    return ret;
+}
+
+/* Functions for the funcfiletrace special parameter. */
+
+/**/
+static char **
+funcfiletracegetfn(UNUSED(Param pm))
+{
+    Funcstack f;
+    int num;
+    char **ret, **p;
+
+    for (f = funcstack, num = 0; f; f = f->prev, num++);
+
+    ret = (char **) zhalloc((num + 1) * sizeof(char *));
+
+    for (f = funcstack, p = ret; f; f = f->prev, p++) {
+	char *colonpair;
+	char *fname = f->filename ? f->filename : "";
+
+	colonpair = zhalloc(strlen(fname) + (f->flineno > 9999 ? 24 : 6));
+	sprintf(colonpair, "%s:%ld", fname, (long)f->flineno);
 
 	*p = colonpair;
     }
@@ -1773,6 +1801,8 @@
 { funcstackgetfn, arrsetfn, stdunsetfn };
 static const struct gsu_array functrace_gsu =
 { functracegetfn, arrsetfn, stdunsetfn };
+static const struct gsu_array funcfiletrace_gsu =
+{ funcfiletracegetfn, arrsetfn, stdunsetfn };
 static const struct gsu_array reswords_gsu =
 { reswordsgetfn, arrsetfn, stdunsetfn };
 static const struct gsu_array disreswords_gsu =
@@ -1807,6 +1837,8 @@
 		 scanpmfunctions),
     SPECIALPMDEF("functrace", PM_ARRAY|PM_READONLY,
 	    &functrace_gsu, NULL, NULL),
+    SPECIALPMDEF("funcfiletrace", PM_ARRAY|PM_READONLY,
+	    &funcfiletrace_gsu, NULL, NULL),
     SPECIALPMDEF("galiases", 0,
 	    &pmgaliases_gsu, getpmgalias, scanpmgaliases),
     SPECIALPMDEF("history", PM_READONLY,
-- 
Peter Stephenson <p.w.stephenson@xxxxxxxxxxxx>
Web page now at http://homepage.ntlworld.com/p.w.stephenson/



Messages sorted by: Reverse Date, Date, Thread, Author