From 7543fc6d1e6766b46c5cbe9f82bcbb8b0622d964 Mon Sep 17 00:00:00 2001 From: ferrieux 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 + + 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 * 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