From 7543fc6d1e6766b46c5cbe9f82bcbb8b0622d964 Mon Sep 17 00:00:00 2001
From: ferrieux <ferrieux@noemail.net>
Date: Mon, 5 Apr 2010 19:44:43 +0000
Subject: TIP #348 IMPLEMENTATION  -  Substituted error stack

FossilOrigin-Name: 43fe3048397d3a082c8a3c34f0cd6875f4350dd3
---
 ChangeLog           | 19 ++++++++++++
 doc/catch.n         | 48 ++++++++++++++++++++-----------
 doc/info.n          | 12 +++++++-
 doc/return.n        | 18 +++++++++++-
 generic/tclBasic.c  | 15 +++++++++-
 generic/tclCmdIL.c  | 57 +++++++++++++++++++++++++++++++++++-
 generic/tclInt.h    |  6 +++-
 generic/tclNamesp.c | 43 ++++++++++++++++++++++++++-
 generic/tclResult.c | 83 +++++++++++++++++++++++++++++++++++++++++++++++++++--
 tests/cmdMZ.test    | 12 +++++---
 tests/error.test    | 24 +++++++++++++++-
 tests/execute.test  |  6 ++--
 tests/info.test     | 10 +++----
 tests/init.test     |  4 +--
 tests/result.test   |  8 ++++++
 15 files changed, 325 insertions(+), 40 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index 3f2019e..952da2a 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,22 @@
+2010-04-05  Alexandre Ferrieux  <ferrieux@users.sourceforge.net>
+
+	TIP #348 IMPLEMENTATION  -  Substituted error stack
+
+	* generic/tclBasic.c
+	* generic/tclCmdIL.c
+	* generic/tclInt.h
+	* generic/tclNamesp.c
+	* generic/tclResult.c
+	* doc/catch.n
+	* doc/info.n
+	* doc/return.n
+	* tests/cmdMZ.test
+	* tests/error.test
+	* tests/execute.test
+	* tests/info.test
+	* tests/init.test
+	* tests/result.test
+
 2010-04-05  Donal K. Fellows  <dkf@users.sf.net>
 
 	* unix/tcl.m4 (SC_ENABLE_THREADS): Flip the default for whether to
diff --git a/doc/catch.n b/doc/catch.n
index a21fabd..1efb082 100644
--- a/doc/catch.n
+++ b/doc/catch.n
@@ -6,7 +6,7 @@
 '\" See the file "license.terms" for information on usage and redistribution
 '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 '\" 
-'\" RCS: @(#) $Id: catch.n,v 1.23 2010/01/13 12:08:30 dkf Exp $
+'\" RCS: @(#) $Id: catch.n,v 1.24 2010/04/05 19:44:45 ferrieux Exp $
 '\" 
 .so man.macros
 .TH catch n "8.5" Tcl "Tcl Built-In Commands"
@@ -54,22 +54,36 @@ Only when the return code is \fBTCL_RETURN\fR will the values of
 the \fB\-level\fR and \fB\-code\fR entries be something else, as
 further described in the documentation for the \fBreturn\fR command.
 .PP
-When the return code from evaluation of \fIscript\fR is \fBTCL_ERROR\fR,
-three additional entries are defined in the dictionary of return options
-stored in \fIoptionsVarName\fR: \fB\-errorinfo\fR, \fB\-errorcode\fR, 
-and \fB\-errorline\fR.  The value of the \fB\-errorinfo\fR entry
-is a formatted stack trace containing more information about
-the context in which the error happened.  The formatted stack
-trace is meant to be read by a person.  The value of
-the \fB\-errorcode\fR entry is additional information about the
-error stored as a list.  The \fB\-errorcode\fR value is meant to
-be further processed by programs, and may not be particularly
-readable by people.  The value of the \fB\-errorline\fR entry
-is an integer indicating which line of \fIscript\fR was being
-evaluated when the error occurred.  The values of the \fB\-errorinfo\fR
-and \fB\-errorcode\fR entries of the most recent error are also
-available as values of the global variables \fB::errorInfo\fR
-and \fB::errorCode\fR respectively.
+When the return code from evaluation of \fIscript\fR is
+\fBTCL_ERROR\fR, four additional entries are defined in the dictionary
+of return options stored in \fIoptionsVarName\fR: \fB\-errorinfo\fR,
+\fB\-errorcode\fR, \fB\-errorline\fR, and \fB\-errorstack\fR.  The
+value of the \fB\-errorinfo\fR entry is a formatted stack trace
+containing more information about the context in which the error
+happened.  The formatted stack trace is meant to be read by a person.
+The value of the \fB\-errorcode\fR entry is additional information
+about the error stored as a list.  The \fB\-errorcode\fR value is
+meant to be further processed by programs, and may not be particularly
+readable by people.  The value of the \fB\-errorline\fR entry is an
+integer indicating which line of \fIscript\fR was being evaluated when
+the error occurred.  The value of the \fB\-errorstack\fR entry is an
+even-sized list made of token-parameter pairs accumulated while
+unwinding the stack. The token may be "CALL", in which case the
+parameter is a list made of the proc name and arguments at the
+corresponding level; or it may be "UP", in which case the parameter is
+the relative [uplevel] of the previous CALL. The salient differences
+wrt -errorinfo are that (1) it is a machine-readable form amenable to
+[foreach {tok prm} ...], (2) it contains the true (substituted) values
+passed to the functions, instead of the static text of the calling
+sites, and (3) it is coarser-grained, with only one element per stack
+frame (like procs; no separate elements for [foreach] constructs for
+example).
+
+The values of the \fB\-errorinfo\fR and \fB\-errorcode\fR entries of
+the most recent error are also available as values of the global
+variables \fB::errorInfo\fR and \fB::errorCode\fR respectively. The
+value of the \fB\-errorstack\fR entry surfaces as \fBinfo
+errorstack\fR.
 .PP
 Tcl packages may provide commands that set other entries in the
 dictionary of return options, and the \fBreturn\fR command may be
diff --git a/doc/info.n b/doc/info.n
index 246b83f..3076274 100644
--- a/doc/info.n
+++ b/doc/info.n
@@ -8,7 +8,7 @@
 '\" See the file "license.terms" for information on usage and redistribution
 '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 '\" 
-'\" RCS: @(#) $Id: info.n,v 1.36 2010/03/24 13:21:11 dkf Exp $
+'\" RCS: @(#) $Id: info.n,v 1.37 2010/04/05 19:44:45 ferrieux Exp $
 '\" 
 .so man.macros
 .TH info n 8.4 Tcl "Tcl Built-In Commands"
@@ -94,6 +94,16 @@ does not have a default value then the command returns \fB0\fR.
 Otherwise it returns \fB1\fR and places the default value of \fIarg\fR
 into variable \fIvarname\fR.
 .TP
+\fBinfo errorstack \fR?\fIinterp\fR?
+.
+Returns a list of lists made of the function names and arguments at
+each level from the call stack of the last error in the given
+\fIinterp\fR, or in the current one if not specified.  This
+information is also present in the -errorstack entry of the options
+dictionary returned by 3-arg \fBcatch\fR; \fBinfo errorstack\fR is a
+convenient way of retrieving it for uncaught errors at toplevel in an
+interactive tclsh.
+.TP
 \fBinfo exists \fIvarName\fR
 .
 Returns \fB1\fR if the variable named \fIvarName\fR exists in the
diff --git a/doc/return.n b/doc/return.n
index 4f77135..8d0d96c 100644
--- a/doc/return.n
+++ b/doc/return.n
@@ -6,7 +6,7 @@
 '\" See the file "license.terms" for information on usage and redistribution
 '\" of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 '\" 
-'\" RCS: @(#) $Id: return.n,v 1.25 2010/01/20 13:42:17 dkf Exp $
+'\" RCS: @(#) $Id: return.n,v 1.26 2010/04/05 19:44:45 ferrieux Exp $
 '\" 
 .so man.macros
 .TH return n 8.5 Tcl "Tcl Built-In Commands"
@@ -138,6 +138,22 @@ the value of \fB\-errorinfo\fR in a return options dictionary captured
 by the \fBcatch\fR command (or from the copy of that information
 stored in the global variable \fBerrorInfo\fR).
 .TP
+\fB\-errorstack \fIlist\fR
+.
+The \fB\-errorstack\fR option receives special treatment only when the value
+of the \fB\-code\fR option is \fBTCL_ERROR\fR.  Then \fIlist\fR is the initial
+error stack, recording actual argument values passed to each proc level.  The error stack will
+also be reachable through [info errorstack].
+If no \fB\-errorstack\fR option is provided to \fBreturn\fR when
+the \fB\-code error\fR option is provided, Tcl will provide its own
+initial error stack in the entry for \fB\-errorstack\fR.  Tcl's
+initial error stack will include only the call to the procedure, and
+stack unwinding will append information about higher stack levels, but
+there will be no information about the context of the error within
+the procedure.  Typically the \fIlist\fR value is supplied from
+the value of \fB\-errorstack\fR in a return options dictionary captured
+by the \fBcatch\fR command (or from the copy of that information from [info errorstack]).
+.TP
 \fB\-level \fIlevel\fR
 .
 The \fB\-level\fR and \fB\-code\fR options work together to set the return
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index 148baa4..ca2b045 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -16,7 +16,7 @@
  * See the file "license.terms" for information on usage and redistribution of
  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tclBasic.c,v 1.449 2010/03/19 11:54:06 dkf Exp $
+ * RCS: @(#) $Id: tclBasic.c,v 1.450 2010/04/05 19:44:45 ferrieux Exp $
  */
 
 #include "tclInt.h"
@@ -529,6 +529,13 @@ Tcl_CreateInterp(void)
     iPtr->errorInfo = NULL;
     TclNewLiteralStringObj(iPtr->eiVar, "::errorInfo");
     Tcl_IncrRefCount(iPtr->eiVar);
+    iPtr->errorStack = Tcl_NewListObj(0, NULL);
+    Tcl_IncrRefCount(iPtr->errorStack);
+    iPtr->resetErrorStack = 1;
+    TclNewLiteralStringObj(iPtr->upLiteral,"UP");
+    Tcl_IncrRefCount(iPtr->upLiteral);
+    TclNewLiteralStringObj(iPtr->callLiteral,"CALL");
+    Tcl_IncrRefCount(iPtr->callLiteral);
     iPtr->errorCode = NULL;
     TclNewLiteralStringObj(iPtr->ecVar, "::errorCode");
     Tcl_IncrRefCount(iPtr->ecVar);
@@ -1467,6 +1474,10 @@ DeleteInterpProc(
 	Tcl_DecrRefCount(iPtr->errorInfo);
 	iPtr->errorInfo = NULL;
     }
+    Tcl_DecrRefCount(iPtr->errorStack);
+    iPtr->errorStack = NULL;
+    Tcl_DecrRefCount(iPtr->upLiteral);
+    Tcl_DecrRefCount(iPtr->callLiteral);
     if (iPtr->returnOpts) {
 	Tcl_DecrRefCount(iPtr->returnOpts);
     }
@@ -8943,5 +8954,7 @@ TclInfoCoroutineCmd(
  * mode: c
  * c-basic-offset: 4
  * fill-column: 78
+ * tab-width: 8
+ * indent-tabs-mode: nil
  * End:
  */
diff --git a/generic/tclCmdIL.c b/generic/tclCmdIL.c
index d063014..bdc6d2e 100644
--- a/generic/tclCmdIL.c
+++ b/generic/tclCmdIL.c
@@ -16,7 +16,7 @@
  * See the file "license.terms" for information on usage and redistribution of
  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tclCmdIL.c,v 1.180 2010/03/05 14:34:03 dkf Exp $
+ * RCS: @(#) $Id: tclCmdIL.c,v 1.181 2010/04/05 19:44:45 ferrieux Exp $
  */
 
 #include "tclInt.h"
@@ -118,6 +118,9 @@ static int		InfoCompleteCmd(ClientData dummy, Tcl_Interp *interp,
 			    int objc, Tcl_Obj *const objv[]);
 static int		InfoDefaultCmd(ClientData dummy, Tcl_Interp *interp,
 			    int objc, Tcl_Obj *const objv[]);
+/* TIP #348 - New 'info' subcommand 'errorstack' */
+static int		InfoErrorStackCmd(ClientData dummy, Tcl_Interp *interp,
+			    int objc, Tcl_Obj *const objv[]);
 /* TIP #280 - New 'info' subcommand 'frame' */
 static int		InfoFrameCmd(ClientData dummy, Tcl_Interp *interp,
 			    int objc, Tcl_Obj *const objv[]);
@@ -164,6 +167,7 @@ static const EnsembleImplMap defaultInfoMap[] = {
     {"complete",	   InfoCompleteCmd,	    NULL, NULL, NULL},
     {"coroutine",	   TclInfoCoroutineCmd,     NULL, NULL, NULL},
     {"default",		   InfoDefaultCmd,	    NULL, NULL, NULL},
+    {"errorstack",	   InfoErrorStackCmd,	    NULL, NULL, NULL},
     {"exists",		   TclInfoExistsCmd,	    TclCompileInfoExistsCmd, NULL, NULL},
     {"frame",		   InfoFrameCmd,	    NULL, NULL, NULL},
     {"functions",	   InfoFunctionsCmd,	    NULL, NULL, NULL},
@@ -1022,6 +1026,55 @@ InfoDefaultCmd(
 /*
  *----------------------------------------------------------------------
  *
+ * InfoErrorStackCmd --
+ *
+ *	Called to implement the "info errorstack" command that returns information
+ *	about the last error's call stack. Handles the following syntax:
+ *
+ *	    info errorstack ?interp?
+ *
+ * Results:
+ *	Returns TCL_OK if successful and TCL_ERROR if there is an error.
+ *
+ * Side effects:
+ *	Returns a result in the interpreter's result object. If there is an
+ *	error, the result is an error message.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+InfoErrorStackCmd(
+    ClientData dummy,		/* Not used. */
+    Tcl_Interp *interp,		/* Current interpreter. */
+    int objc,			/* Number of arguments. */
+    Tcl_Obj *const objv[])	/* Argument objects. */
+{
+    Tcl_Interp *target;
+    Interp *iPtr;
+
+    if ((objc != 1) && (objc != 2)) {
+	Tcl_WrongNumArgs(interp, 1, objv, "?interp?");
+	return TCL_ERROR;
+    }
+    
+    target = interp;
+    if (objc == 2) {
+        target = Tcl_GetSlave(interp, Tcl_GetString(objv[1]));
+        if (target == NULL) {
+            return TCL_ERROR;
+        }
+    }
+
+    iPtr = (Interp *) target;
+    Tcl_SetObjResult(interp, iPtr->errorStack);
+    
+    return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
  * TclInfoExistsCmd --
  *
  *	Called to implement the "info exists" command that determines whether
@@ -4401,5 +4454,7 @@ SelectObjFromSublist(
  * mode: c
  * c-basic-offset: 4
  * fill-column: 78
+ * tab-width: 8
+ * indent-tabs-mode: nil
  * End:
  */
diff --git a/generic/tclInt.h b/generic/tclInt.h
index 6c70fd2..047a823 100644
--- a/generic/tclInt.h
+++ b/generic/tclInt.h
@@ -15,7 +15,7 @@
  * See the file "license.terms" for information on usage and redistribution of
  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tclInt.h,v 1.467 2010/04/02 21:21:06 kennykb Exp $
+ * RCS: @(#) $Id: tclInt.h,v 1.468 2010/04/05 19:44:45 ferrieux Exp $
  */
 
 #ifndef _TCLINT
@@ -1984,6 +1984,10 @@ typedef struct Interp {
     Tcl_Obj *eiVar;		/* cached ref to ::errorInfo variable. */
     Tcl_Obj *errorCode;		/* errorCode value (now as a Tcl_Obj). */
     Tcl_Obj *ecVar;		/* cached ref to ::errorInfo variable. */
+    Tcl_Obj *errorStack;	/* [info errorstack] value (as a Tcl_Obj). */
+    Tcl_Obj *upLiteral;		/* "UP" literal for [info errorstack] */
+    Tcl_Obj *callLiteral;	/* "CALL" literal for [info errorstack] */
+    int resetErrorStack;        /* controls cleaning up of ::errorStack */
     int returnLevel;		/* [return -level] parameter. */
 
     /*
diff --git a/generic/tclNamesp.c b/generic/tclNamesp.c
index e32e0ba..41032d1 100644
--- a/generic/tclNamesp.c
+++ b/generic/tclNamesp.c
@@ -22,7 +22,7 @@
  * See the file "license.terms" for information on usage and redistribution of
  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tclNamesp.c,v 1.204 2010/03/05 14:34:04 dkf Exp $
+ * RCS: @(#) $Id: tclNamesp.c,v 1.205 2010/04/05 19:44:45 ferrieux Exp $
  */
 
 #include "tclInt.h"
@@ -4932,6 +4932,45 @@ Tcl_LogCommandInfo(
 		    TCL_GLOBAL_ONLY);
 	}
     }
+
+    /*
+     * TIP #348
+     */
+
+    if (Tcl_IsShared(iPtr->errorStack)) {
+        Tcl_Obj *newObj;
+            
+        newObj = Tcl_DuplicateObj(iPtr->errorStack);
+        Tcl_DecrRefCount(iPtr->errorStack);
+        Tcl_IncrRefCount(newObj);
+        iPtr->errorStack = newObj;
+    }
+    if (iPtr->resetErrorStack) {
+	int len;
+
+        iPtr->resetErrorStack = 0;
+	Tcl_ListObjLength(interp, iPtr->errorStack, &len);
+        /* reset while keeping the list intrep as much as possible */
+        Tcl_ListObjReplace(interp, iPtr->errorStack, 0, len, 0, NULL);
+    } 
+
+    if (iPtr->varFramePtr != iPtr->framePtr) {
+        /* uplevel case, [lappend errorstack UP $relativelevel] */
+        struct CallFrame *frame;
+        int n;
+
+        for (n=0, frame=iPtr->framePtr;
+		(frame && (frame != iPtr->varFramePtr));
+		n++, frame=frame->callerVarPtr);
+        Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->upLiteral);
+        Tcl_ListObjAppendElement(NULL, iPtr->errorStack, Tcl_NewIntObj(n));
+    } else if (iPtr->framePtr != iPtr->rootFramePtr) {
+        /* normal case, [lappend errorstack CALL [info level 0]] */
+        Tcl_ListObjAppendElement(NULL, iPtr->errorStack, iPtr->callLiteral);
+        Tcl_ListObjAppendElement(NULL, iPtr->errorStack,
+                                 Tcl_NewListObj(iPtr->varFramePtr->objc,
+                                                iPtr->varFramePtr->objv));
+    }
 }
 
 /*
@@ -4939,5 +4978,7 @@ Tcl_LogCommandInfo(
  * mode: c
  * c-basic-offset: 4
  * fill-column: 78
+ * tab-width: 8
+ * indent-tabs-mode: nil
  * End:
  */
diff --git a/generic/tclResult.c b/generic/tclResult.c
index 1fcdfba..07b50db 100644
--- a/generic/tclResult.c
+++ b/generic/tclResult.c
@@ -8,7 +8,7 @@
  * See the file "license.terms" for information on usage and redistribution of
  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
  *
- * RCS: @(#) $Id: tclResult.c,v 1.60 2010/03/30 13:17:18 nijtmans Exp $
+ * RCS: @(#) $Id: tclResult.c,v 1.61 2010/04/05 19:44:45 ferrieux Exp $
  */
 
 #include "tclInt.h"
@@ -19,7 +19,7 @@
 
 enum returnKeys {
     KEY_CODE,	KEY_ERRORCODE,	KEY_ERRORINFO,	KEY_ERRORLINE,
-    KEY_LEVEL,	KEY_OPTIONS,	KEY_LAST
+    KEY_LEVEL,	KEY_OPTIONS,	KEY_ERRORSTACK,	KEY_LAST
 };
 
 /*
@@ -46,6 +46,8 @@ typedef struct InterpState {
     Tcl_Obj *errorCode;
     Tcl_Obj *returnOpts;
     Tcl_Obj *objResult;
+    Tcl_Obj *errorStack;
+    int resetErrorStack;
 } InterpState;
 
 /*
@@ -82,6 +84,8 @@ Tcl_SaveInterpState(
     statePtr->returnLevel = iPtr->returnLevel;
     statePtr->returnCode = iPtr->returnCode;
     statePtr->errorInfo = iPtr->errorInfo;
+    statePtr->errorStack = iPtr->errorStack;
+    statePtr->resetErrorStack = iPtr->resetErrorStack;
     if (statePtr->errorInfo) {
 	Tcl_IncrRefCount(statePtr->errorInfo);
     }
@@ -93,6 +97,9 @@ Tcl_SaveInterpState(
     if (statePtr->returnOpts) {
 	Tcl_IncrRefCount(statePtr->returnOpts);
     }
+    if (statePtr->errorStack) {
+	Tcl_IncrRefCount(statePtr->errorStack);
+    }
     statePtr->objResult = Tcl_GetObjResult(interp);
     Tcl_IncrRefCount(statePtr->objResult);
     return (Tcl_InterpState) statePtr;
@@ -130,6 +137,7 @@ Tcl_RestoreInterpState(
 
     iPtr->returnLevel = statePtr->returnLevel;
     iPtr->returnCode = statePtr->returnCode;
+    iPtr->resetErrorStack = statePtr->resetErrorStack;
     if (iPtr->errorInfo) {
 	Tcl_DecrRefCount(iPtr->errorInfo);
     }
@@ -144,6 +152,13 @@ Tcl_RestoreInterpState(
     if (iPtr->errorCode) {
 	Tcl_IncrRefCount(iPtr->errorCode);
     }
+    if (iPtr->errorStack) {
+	Tcl_DecrRefCount(iPtr->errorStack);
+    }
+    iPtr->errorStack = statePtr->errorStack;
+    if (iPtr->errorStack) {
+	Tcl_IncrRefCount(iPtr->errorStack);
+    }
     if (iPtr->returnOpts) {
 	Tcl_DecrRefCount(iPtr->returnOpts);
     }
@@ -188,6 +203,9 @@ Tcl_DiscardInterpState(
     if (statePtr->returnOpts) {
 	Tcl_DecrRefCount(statePtr->returnOpts);
     }
+    if (statePtr->errorStack) {
+	Tcl_DecrRefCount(statePtr->errorStack);
+    }
     Tcl_DecrRefCount(statePtr->objResult);
     ckfree((char *) statePtr);
 }
@@ -924,6 +942,7 @@ Tcl_ResetResult(
 	Tcl_DecrRefCount(iPtr->errorInfo);
 	iPtr->errorInfo = NULL;
     }
+    iPtr->resetErrorStack = 1;
     iPtr->returnLevel = 1;
     iPtr->returnCode = TCL_OK;
     if (iPtr->returnOpts) {
@@ -1161,6 +1180,7 @@ GetKeys(void)
 	TclNewLiteralStringObj(keys[KEY_ERRORCODE], "-errorcode");
 	TclNewLiteralStringObj(keys[KEY_ERRORINFO], "-errorinfo");
 	TclNewLiteralStringObj(keys[KEY_ERRORLINE], "-errorline");
+	TclNewLiteralStringObj(keys[KEY_ERRORSTACK],"-errorstack");
 	TclNewLiteralStringObj(keys[KEY_LEVEL],	    "-level");
 	TclNewLiteralStringObj(keys[KEY_OPTIONS],   "-options");
 
@@ -1266,6 +1286,31 @@ TclProcessReturn(
 		iPtr->flags |= ERR_ALREADY_LOGGED;
 	    }
 	}
+	Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORSTACK], &valuePtr);
+	if (valuePtr != NULL) {
+            int len, valueObjc;
+            Tcl_Obj **valueObjv;
+
+            if (Tcl_IsShared(iPtr->errorStack)) {
+                Tcl_Obj *newObj;
+                
+                newObj = Tcl_DuplicateObj(iPtr->errorStack);
+                Tcl_DecrRefCount(iPtr->errorStack);
+                Tcl_IncrRefCount(newObj);
+                iPtr->errorStack = newObj;
+            }
+            /*
+             * List extraction done after duplication to avoid moving the rug
+             * if someone does [return -errorstack [info errorstack]]
+             */
+            if (Tcl_ListObjGetElements(interp, valuePtr, &valueObjc, &valueObjv) == TCL_ERROR) {
+                return TCL_ERROR;
+            }
+            iPtr->resetErrorStack = 0;
+            Tcl_ListObjLength(interp, iPtr->errorStack, &len);
+            /* reset while keeping the list intrep as much as possible */
+            Tcl_ListObjReplace(interp, iPtr->errorStack, 0, len, valueObjc, valueObjv);
+ 	}
 	Tcl_DictObjGet(NULL, iPtr->returnOpts, keys[KEY_ERRORCODE], &valuePtr);
 	if (valuePtr != NULL) {
 	    Tcl_SetObjErrorCode(interp, valuePtr);
@@ -1429,6 +1474,37 @@ TclMergeReturnOptions(
     }
 
     /*
+     * Check for bogus -errorstack value.
+     */
+
+    Tcl_DictObjGet(NULL, returnOpts, keys[KEY_ERRORSTACK], &valuePtr);
+    if (valuePtr != NULL) {
+	int length;
+
+	if (TCL_ERROR == Tcl_ListObjLength(NULL, valuePtr, &length )) {
+	    /*
+	     * Value is not a list, which is illegal for -errorstack.
+	     */
+	    Tcl_ResetResult(interp);
+	    Tcl_AppendResult(interp, "bad -errorstack value: "
+			     "expected a list but got \"",
+			     TclGetString(valuePtr), "\"", NULL);
+	    Tcl_SetErrorCode(interp, "TCL", "RESULT", "NONLIST_ERRORSTACK", NULL);
+	    goto error;
+	}
+        if (length % 2) {
+            /*
+             * Errorstack must always be an even-sized list
+             */
+            Tcl_ResetResult(interp);
+	    Tcl_AppendResult(interp, "forbidden odd-sized list for -errorstack: \"",
+			     TclGetString(valuePtr), "\"", NULL);
+	    Tcl_SetErrorCode(interp, "TCL", "RESULT", "ODDSIZEDLIST_ERRORSTACK", NULL);
+	    goto error;
+        }
+    }
+
+    /*
      * Convert [return -code return -level X] to [return -code ok -level X+1]
      */
 
@@ -1505,6 +1581,7 @@ Tcl_GetReturnOptions(
 
     if (result == TCL_ERROR) {
 	Tcl_AddObjErrorInfo(interp, "", -1);
+        Tcl_DictObjPut(NULL, options, keys[KEY_ERRORSTACK], iPtr->errorStack);
     }
     if (iPtr->errorCode) {
 	Tcl_DictObjPut(NULL, options, keys[KEY_ERRORCODE], iPtr->errorCode);
@@ -1636,5 +1713,7 @@ Tcl_TransferResult(
  * mode: c
  * c-basic-offset: 4
  * fill-column: 78
+ * tab-width: 8
+ * indent-tabs-mode: nil
  * End:
  */
diff --git a/tests/cmdMZ.test b/tests/cmdMZ.test
index 0a86e42..c7f6e44 100644
--- a/tests/cmdMZ.test
+++ b/tests/cmdMZ.test
@@ -11,7 +11,7 @@
 # See the file "license.terms" for information on usage and redistribution of
 # this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #
-# RCS: @(#) $Id: cmdMZ.test,v 1.29 2010/03/31 10:29:22 nijtmans Exp $
+# RCS: @(#) $Id: cmdMZ.test,v 1.30 2010/04/05 19:44:45 ferrieux Exp $
 
 if {[catch {package require tcltest 2.1}]} {
     puts stderr "Skipping tests in [info script]. tcltest 2.1 required."
@@ -149,11 +149,11 @@ test cmdMZ-return-2.8 {return option handling} -body {
 test cmdMZ-return-2.9 {return option handling} -body {
     return -level 0 -code 10
 } -returnCodes 10 -result {}
-test cmdMZ-return-2.10 {return option handling} {
+test cmdMZ-return-2.10 {return option handling} -body {
     list [catch {return -level 0 -code error} -> foo] [dictSort $foo]
-} {1 {-code 1 -errorcode NONE -errorinfo {
+} -match glob -result {1 {-code 1 -errorcode NONE -errorinfo {
     while executing
-"return -level 0 -code error"} -errorline 1 -level 0}}
+"return -level 0 -code error"} -errorline 1 -errorstack * -level 0}}
 test cmdMZ-return-2.11 {return option handling} {
     list [catch {return -level 0 -code break} -> foo] [dictSort $foo]
 } {3 {-code 3 -level 0}}
@@ -193,6 +193,9 @@ test cmdMZ-return-2.17 {return opton handling} -setup {
 } -cleanup {
     rename p {}
 } -result {1 c {a b}}
+test cmdMZ-return-2.18 {return option handling} {
+    list [catch {return -code error -errorstack [list CALL a CALL b] yo} -> foo] [dictSort $foo] [info errorstack]
+} {2 {-code 1 -errorcode NONE -errorstack {CALL a CALL b} -level 1} {CALL a CALL b}}
 
 # Check that the result of a [return -options $opts $result] is
 # indistinguishable from that of the originally caught script, no matter what
@@ -211,6 +214,7 @@ foreach {testid script} {
     cmdMZ-return-3.10 {return -code error -errorinfo foo}
     cmdMZ-return-3.11 {return -code error -errorinfo foo -errorcode bar}
     cmdMZ-return-3.12 {return -code error -errorinfo foo -errorcode bar -errorline 10}
+    cmdMZ-return-3.12.1 {return -code error -errorinfo foo -errorcode bar -errorline 10 -errorstack baz}
     cmdMZ-return-3.13 {return -options {x y z 2}}
     cmdMZ-return-3.14 {return -level 3 -code break sdf}
 } {
diff --git a/tests/error.test b/tests/error.test
index 623595c..ef09bc5 100644
--- a/tests/error.test
+++ b/tests/error.test
@@ -11,7 +11,7 @@
 # See the file "license.terms" for information on usage and redistribution of
 # this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #
-# RCS: @(#) $Id: error.test,v 1.29 2010/03/31 10:29:22 nijtmans Exp $
+# RCS: @(#) $Id: error.test,v 1.30 2010/04/05 19:44:45 ferrieux Exp $
 
 if {[lsearch [namespace children] ::tcltest] == -1} {
     package require tcltest 2
@@ -169,6 +169,19 @@ test error-4.5 {errorInfo and errorCode variables} {
     list [catch {error msg1 msg2 {}} msg] $msg $::errorInfo $::errorCode
 } {1 msg1 msg2 {}}
 
+test error-4.6 {errorstack via info } -body {
+    proc f x {g $x$x}
+    proc g x {error G:$x}
+    catch {f 12}
+    info errorstack
+} -match glob -result {CALL {g 1212} CALL {f 12} UP 1}
+test error-4.7 {errorstack via options dict } -body {
+    proc f x {g $x$x}
+    proc g x {error G:$x}
+    catch {f 12} m d
+    dict get $d -errorstack
+} -match glob -result {CALL {g 1212} CALL {f 12} UP 1}
+
 # Errors in error command itself
 
 test error-5.1 {errors in error command} {
@@ -223,6 +236,15 @@ test error-6.9 {catch must reset error state} {
     catch foo
     list $::errorCode
 } {NONE}
+test error-6.10 {catch must reset errorstack} -body {
+	proc f x {g $x$x}
+	proc g x {error G:$x}
+	catch {f 12}
+	set e1 [info errorstack]
+	catch {f 13}
+	set e2 [info errorstack]
+	list $e1 $e2
+} -match glob -result {{CALL {g 1212} CALL {f 12} UP 1} {CALL {g 1313} CALL {f 13} UP 1}}
 
 test error-7.1 {Bug 1397843} -body {
     variable cmds
diff --git a/tests/execute.test b/tests/execute.test
index 87f835e..ce21040 100644
--- a/tests/execute.test
+++ b/tests/execute.test
@@ -14,7 +14,7 @@
 # See the file "license.terms" for information on usage and redistribution
 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #
-# RCS: @(#) $Id: execute.test,v 1.34 2009/11/16 18:00:11 dgp Exp $
+# RCS: @(#) $Id: execute.test,v 1.35 2010/04/05 19:44:45 ferrieux Exp $
 
 if {[lsearch [namespace children] ::tcltest] == -1} {
     package require tcltest 2
@@ -956,11 +956,11 @@ test execute-8.5 {Bug 2038069} -setup {
     demo
 } -cleanup {
     rename demo {}
-} -result {-code 1 -level 0 -errorcode NONE -errorinfo {FOO
+} -match glob -result {-code 1 -level 0 -errorstack * -errorcode NONE -errorinfo {FOO
     while executing
 "error FOO"
     invoked from within
-"catch [list error FOO] m o"} -errorline 2}
+"catch \[list error FOO\] m o"} -errorline 2}
 
 test execute-9.1 {Interp result resetting [Bug 1522803]} {
     set c 0
diff --git a/tests/info.test b/tests/info.test
index 28fee2c..b25f4a6 100644
--- a/tests/info.test
+++ b/tests/info.test
@@ -13,7 +13,7 @@
 # See the file "license.terms" for information on usage and redistribution
 # of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #
-# RCS: @(#) $Id: info.test,v 1.75 2010/02/10 23:24:25 dkf Exp $
+# RCS: @(#) $Id: info.test,v 1.76 2010/04/05 19:44:45 ferrieux Exp $
 
 if {{::tcltest} ni [namespace children]} {
     package require tcltest 2
@@ -676,16 +676,16 @@ test info-21.1 {miscellaneous error conditions} -returnCodes error -body {
 } -result {wrong # args: should be "info subcommand ?arg ...?"}
 test info-21.2 {miscellaneous error conditions} -returnCodes error -body {
     info gorp
-} -result {unknown or ambiguous subcommand "gorp": must be args, body, class, cmdcount, commands, complete, coroutine, default, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
+} -result {unknown or ambiguous subcommand "gorp": must be args, body, class, cmdcount, commands, complete, coroutine, default, errorstack, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
 test info-21.3 {miscellaneous error conditions} -returnCodes error -body {
     info c
-} -result {unknown or ambiguous subcommand "c": must be args, body, class, cmdcount, commands, complete, coroutine, default, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
+} -result {unknown or ambiguous subcommand "c": must be args, body, class, cmdcount, commands, complete, coroutine, default, errorstack, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
 test info-21.4 {miscellaneous error conditions} -returnCodes error -body {
     info l
-} -result {unknown or ambiguous subcommand "l": must be args, body, class, cmdcount, commands, complete, coroutine, default, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
+} -result {unknown or ambiguous subcommand "l": must be args, body, class, cmdcount, commands, complete, coroutine, default, errorstack, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
 test info-21.5 {miscellaneous error conditions} -returnCodes error -body {
     info s
-} -result {unknown or ambiguous subcommand "s": must be args, body, class, cmdcount, commands, complete, coroutine, default, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
+} -result {unknown or ambiguous subcommand "s": must be args, body, class, cmdcount, commands, complete, coroutine, default, errorstack, exists, frame, functions, globals, hostname, level, library, loaded, locals, nameofexecutable, object, patchlevel, procs, script, sharedlibextension, tclversion, or vars}
 
 ##
 # ### ### ### ######### ######### #########
diff --git a/tests/init.test b/tests/init.test
index 0a49472..9c16ee3 100644
--- a/tests/init.test
+++ b/tests/init.test
@@ -10,7 +10,7 @@
 # See the file "license.terms" for information on usage and redistribution of
 # this file, and for a DISCLAIMER OF ALL WARRANTIES.
 #
-# RCS: @(#) $Id: init.test,v 1.21 2009/11/16 18:00:11 dgp Exp $
+# RCS: @(#) $Id: init.test,v 1.22 2010/04/05 19:44:45 ferrieux Exp $
 
 if {[lsearch [namespace children] ::tcltest] == -1} {
     package require tcltest 2
@@ -181,7 +181,7 @@ test init-5.0 {return options passed through ::unknown} -setup {
     list $code $foo $bar $code2 $foo2 $bar2
 } -cleanup {
     unset ::auto_index(::xxx)
-} -result {2 xxx {-errorcode NONE -code 1 -level 1} 2 xxx {-code 1 -level 1 -errorcode NONE}}
+} -match glob -result {2 xxx {-errorcode NONE -code 1 -level 1} 2 xxx {-code 1 -level 1 -errorcode NONE}}
 
 cleanupTests
 }	;#  End of [interp eval $testInterp]
diff --git a/tests/result.test b/tests/result.test
index b2db8ec..8bde7ef 100644
--- a/tests/result.test
+++ b/tests/result.test
@@ -135,6 +135,14 @@ test result-6.3 {Bug 2383005} {
      catch {return -code error -errorcode {{}a} eek} m
      set m
 } {bad -errorcode value: expected a list but got "{}a"}
+test result-6.4 {non-list -errorstack} {
+     catch {return -code error -errorstack {{}a} eek} m o
+     list $m [dict get $o -errorcode] [dict get $o -errorstack]
+} {{bad -errorstack value: expected a list but got "{}a"} {TCL RESULT NONLIST_ERRORSTACK} {UP 1}}
+test result-6.5 {odd-sized-list -errorstack} {
+     catch {return -code error -errorstack a eek} m o
+     list $m [dict get $o -errorcode] [dict get $o -errorstack]
+} {{forbidden odd-sized list for -errorstack: "a"} {TCL RESULT ODDSIZEDLIST_ERRORSTACK} {UP 1}}
 # cleanup
 cleanupTests
 return
-- 
cgit v0.12

