diff options
Diffstat (limited to 'generic/tclBasic.c')
-rw-r--r-- | generic/tclBasic.c | 3371 |
1 files changed, 1954 insertions, 1417 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c index 30631a5..2a334c4 100644 --- a/generic/tclBasic.c +++ b/generic/tclBasic.c @@ -15,22 +15,21 @@ * * 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.361 2008/08/23 01:48:25 msofer Exp $ */ #include "tclInt.h" #include "tclOOInt.h" #include "tclCompile.h" -#include <float.h> -#include <limits.h> -#include <math.h> #include "tommath.h" +#include <math.h> #if NRE_ENABLE_ASSERTS #include <assert.h> #endif +#define INTERP_STACK_INITIAL_SIZE 2000 +#define CORO_STACK_INITIAL_SIZE 200 + /* * Determine whether we're using IEEE floating point */ @@ -54,69 +53,91 @@ typedef struct OldMathFuncData { } OldMathFuncData; /* - * Static functions in this file: + * This is the script cancellation struct and hash table. The hash table is + * used to keep track of the information necessary to process script + * cancellation requests, including the original interp, asynchronous handler + * tokens (created by Tcl_AsyncCreate), and the clientData and flags arguments + * passed to Tcl_CancelEval on a per-interp basis. The cancelLock mutex is + * used for protecting calls to Tcl_CancelEval as well as protecting access to + * the hash table below. */ -static char * CallCommandTraces(Interp *iPtr, Command *cmdPtr, - const char *oldName, const char *newName, int flags); -static int CancelEvalProc(ClientData clientData, - Tcl_Interp *interp, int code); -static int CheckDoubleResult(Tcl_Interp *interp, double dResult); -static void DeleteInterpProc(Tcl_Interp *interp); -static void DeleteOpCmdClientData(ClientData clientData); -static Tcl_Obj *GetCommandSource(Interp *iPtr, int objc, - Tcl_Obj *const objv[], int lookup); -static void ProcessUnexpectedResult(Tcl_Interp *interp, int returnCode); -static int OldMathFuncProc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static void OldMathFuncDeleteProc(ClientData clientData); -static int ExprAbsFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprBinaryFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprBoolFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprCeilFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprDoubleFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprEntierFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprFloorFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprIntFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprIsqrtFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprRandFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprRoundFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprSqrtFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprSrandFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprUnaryFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static int ExprWideFunc(ClientData clientData, Tcl_Interp *interp, - int argc, Tcl_Obj *const *objv); -static void MathFuncWrongNumArgs(Tcl_Interp *interp, int expected, - int actual, Tcl_Obj *const *objv); -#ifdef USE_DTRACE -static int DTraceObjCmd(ClientData dummy, Tcl_Interp *interp, int objc, - Tcl_Obj *const objv[]); -static int DTraceCmdReturn(ClientData data[], Tcl_Interp *interp, - int result); -#else -#define DTraceCmdReturn NULL -#endif +typedef struct { + Tcl_Interp *interp; /* Interp this struct belongs to. */ + Tcl_AsyncHandler async; /* Async handler token for script + * cancellation. */ + char *result; /* The script cancellation result or NULL for + * a default result. */ + int length; /* Length of the above error message. */ + ClientData clientData; /* Ignored */ + int flags; /* Additional flags */ +} CancelInfo; +static Tcl_HashTable cancelTable; +static int cancelTableInitialized = 0; /* 0 means not yet initialized. */ +TCL_DECLARE_MUTEX(cancelLock) -MODULE_SCOPE const TclStubs * const tclConstStubsPtr; +/* + * Declarations for managing contexts for non-recursive coroutines. Contexts + * are used to save the evaluation state between NR calls to each coro. + */ +#define SAVE_CONTEXT(context) \ + (context).framePtr = iPtr->framePtr; \ + (context).varFramePtr = iPtr->varFramePtr; \ + (context).cmdFramePtr = iPtr->cmdFramePtr; \ + (context).lineLABCPtr = iPtr->lineLABCPtr + +#define RESTORE_CONTEXT(context) \ + iPtr->framePtr = (context).framePtr; \ + iPtr->varFramePtr = (context).varFramePtr; \ + iPtr->cmdFramePtr = (context).cmdFramePtr; \ + iPtr->lineLABCPtr = (context).lineLABCPtr + /* - * Tcl_EvalObjv helpers + * Static functions in this file: */ +static char * CallCommandTraces(Interp *iPtr, Command *cmdPtr, + const char *oldName, const char *newName, + int flags); +static int CancelEvalProc(ClientData clientData, + Tcl_Interp *interp, int code); +static int CheckDoubleResult(Tcl_Interp *interp, double dResult); +static void DeleteCoroutine(ClientData clientData); +static void DeleteInterpProc(Tcl_Interp *interp); +static void DeleteOpCmdClientData(ClientData clientData); +#ifdef USE_DTRACE +static Tcl_ObjCmdProc DTraceObjCmd; +static Tcl_NRPostProc DTraceCmdReturn; +#else +# define DTraceCmdReturn NULL +#endif /* USE_DTRACE */ +static Tcl_ObjCmdProc ExprAbsFunc; +static Tcl_ObjCmdProc ExprBinaryFunc; +static Tcl_ObjCmdProc ExprBoolFunc; +static Tcl_ObjCmdProc ExprCeilFunc; +static Tcl_ObjCmdProc ExprDoubleFunc; +static Tcl_ObjCmdProc ExprEntierFunc; +static Tcl_ObjCmdProc ExprFloorFunc; +static Tcl_ObjCmdProc ExprIntFunc; +static Tcl_ObjCmdProc ExprIsqrtFunc; +static Tcl_ObjCmdProc ExprRandFunc; +static Tcl_ObjCmdProc ExprRoundFunc; +static Tcl_ObjCmdProc ExprSqrtFunc; +static Tcl_ObjCmdProc ExprSrandFunc; +static Tcl_ObjCmdProc ExprUnaryFunc; +static Tcl_ObjCmdProc ExprWideFunc; +static void MathFuncWrongNumArgs(Tcl_Interp *interp, int expected, + int actual, Tcl_Obj *const *objv); +static Tcl_NRPostProc NRCoroutineCallerCallback; +static Tcl_NRPostProc NRCoroutineExitCallback; +static int NRCommand(ClientData data[], Tcl_Interp *interp, int result); + +static Tcl_ObjCmdProc OldMathFuncProc; +static void OldMathFuncDeleteProc(ClientData clientData); +static void ProcessUnexpectedResult(Tcl_Interp *interp, + int returnCode); +static int RewindCoroutine(CoroutineData *corPtr, int result); static void TEOV_SwitchVarFrame(Tcl_Interp *interp); static void TEOV_PushExceptionHandlers(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], int flags); @@ -125,21 +146,36 @@ static inline Command * TEOV_LookupCmdFromObj(Tcl_Interp *interp, static int TEOV_NotFound(Tcl_Interp *interp, int objc, Tcl_Obj *const objv[], Namespace *lookupNsPtr); static int TEOV_RunEnterTraces(Tcl_Interp *interp, - Command **cmdPtrPtr, int objc, - Tcl_Obj *const objv[], Namespace *lookupNsPtr); + Command **cmdPtrPtr, Tcl_Obj *commandPtr, int objc, + Tcl_Obj *const objv[]); +static Tcl_NRPostProc RewindCoroutineCallback; +static Tcl_NRPostProc TailcallCleanup; +static Tcl_NRPostProc TEOEx_ByteCodeCallback; +static Tcl_NRPostProc TEOEx_ListCallback; +static Tcl_NRPostProc TEOV_Error; +static Tcl_NRPostProc TEOV_Exception; +static Tcl_NRPostProc TEOV_NotFoundCallback; static Tcl_NRPostProc TEOV_RestoreVarFrame; static Tcl_NRPostProc TEOV_RunLeaveTraces; -static Tcl_NRPostProc TEOV_Exception; -static Tcl_NRPostProc TEOV_Error; -static Tcl_NRPostProc TEOEx_ListCallback; -static Tcl_NRPostProc TEOEx_ByteCodeCallback; +static Tcl_NRPostProc EvalObjvCore; +static Tcl_NRPostProc Dispatch; + +static Tcl_ObjCmdProc NRCoroInjectObjCmd; +static Tcl_NRPostProc NRPostInvoke; -static Tcl_NRPostProc NRCommand; -static Tcl_NRPostProc NRRunObjProc; +MODULE_SCOPE const TclStubs tclStubs; + +/* + * Magical counts for the number of arguments accepted by a coroutine command + * after particular kinds of [yield]. + */ -static Tcl_NRPostProc AtProcExitCleanup; -static Tcl_NRPostProc NRAtProcExitEval; +#define CORO_ACTIVATE_YIELD PTR2INT(NULL) +#define CORO_ACTIVATE_YIELDM PTR2INT(NULL)+1 +#define COROUTINE_ARGUMENTS_SINGLE_OPTIONAL (-1) +#define COROUTINE_ARGUMENTS_ARBITRARY (-2) + /* * The following structure define the commands in the Tcl core. */ @@ -149,11 +185,16 @@ typedef struct { Tcl_ObjCmdProc *objProc; /* Object-based function for command. */ CompileProc *compileProc; /* Function called to compile command. */ Tcl_ObjCmdProc *nreProc; /* NR-based function for command */ - int isSafe; /* If non-zero, command will be present in - * safe interpreter. Otherwise it will be - * hidden. */ + int flags; /* Various flag bits, as defined below. */ } CmdInfo; +#define CMD_IS_SAFE 1 /* Whether this command is part of the set of + * commands present by default in a safe + * interpreter. */ +/* CMD_COMPILES_EXPANDED - Whether the compiler for this command can handle + * expansion for itself rather than needing the generic layer to take care of + * it for it. Defined in tclInt.h. */ + /* * The built-in commands, and the functions that implement them: */ @@ -163,91 +204,95 @@ static const CmdInfo builtInCmds[] = { * Commands in the generic core. */ - {"append", Tcl_AppendObjCmd, TclCompileAppendCmd, NULL, 1}, - {"apply", Tcl_ApplyObjCmd, NULL, TclNRApplyObjCmd, 1}, - {"array", Tcl_ArrayObjCmd, NULL, NULL, 1}, - {"break", Tcl_BreakObjCmd, TclCompileBreakCmd, NULL, 1}, + {"append", Tcl_AppendObjCmd, TclCompileAppendCmd, NULL, CMD_IS_SAFE}, + {"apply", Tcl_ApplyObjCmd, NULL, TclNRApplyObjCmd, CMD_IS_SAFE}, + {"break", Tcl_BreakObjCmd, TclCompileBreakCmd, NULL, CMD_IS_SAFE}, #ifndef EXCLUDE_OBSOLETE_COMMANDS - {"case", Tcl_CaseObjCmd, NULL, NULL, 1}, + {"case", Tcl_CaseObjCmd, NULL, NULL, CMD_IS_SAFE}, #endif - {"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, 1}, - {"concat", Tcl_ConcatObjCmd, NULL, NULL, 1}, - {"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, 1}, - {"error", Tcl_ErrorObjCmd, NULL, NULL, 1}, - {"eval", Tcl_EvalObjCmd, NULL, NULL, 1}, - {"expr", Tcl_ExprObjCmd, TclCompileExprCmd, NULL, 1}, - {"for", Tcl_ForObjCmd, TclCompileForCmd, TclNRForObjCmd, 1}, - {"foreach", Tcl_ForeachObjCmd, TclCompileForeachCmd, NULL, 1}, - {"format", Tcl_FormatObjCmd, NULL, NULL, 1}, - {"global", Tcl_GlobalObjCmd, TclCompileGlobalCmd, NULL, 1}, - {"if", Tcl_IfObjCmd, TclCompileIfCmd, TclNRIfObjCmd, 1}, - {"incr", Tcl_IncrObjCmd, TclCompileIncrCmd, NULL, 1}, - {"join", Tcl_JoinObjCmd, NULL, NULL, 1}, - {"lappend", Tcl_LappendObjCmd, TclCompileLappendCmd, NULL, 1}, - {"lassign", Tcl_LassignObjCmd, TclCompileLassignCmd, NULL, 1}, - {"lindex", Tcl_LindexObjCmd, TclCompileLindexCmd, NULL, 1}, - {"linsert", Tcl_LinsertObjCmd, NULL, NULL, 1}, - {"list", Tcl_ListObjCmd, TclCompileListCmd, NULL, 1}, - {"llength", Tcl_LlengthObjCmd, TclCompileLlengthCmd, NULL, 1}, - {"lrange", Tcl_LrangeObjCmd, NULL, NULL, 1}, - {"lrepeat", Tcl_LrepeatObjCmd, NULL, NULL, 1}, - {"lreplace", Tcl_LreplaceObjCmd, NULL, NULL, 1}, - {"lreverse", Tcl_LreverseObjCmd, NULL, NULL, 1}, - {"lsearch", Tcl_LsearchObjCmd, NULL, NULL, 1}, - {"lset", Tcl_LsetObjCmd, TclCompileLsetCmd, NULL, 1}, - {"lsort", Tcl_LsortObjCmd, NULL, NULL, 1}, - {"namespace", Tcl_NamespaceObjCmd, TclCompileNamespaceCmd, TclNRNamespaceObjCmd, 1}, - {"package", Tcl_PackageObjCmd, NULL, NULL, 1}, - {"proc", Tcl_ProcObjCmd, NULL, NULL, 1}, - {"regexp", Tcl_RegexpObjCmd, TclCompileRegexpCmd, NULL, 1}, - {"regsub", Tcl_RegsubObjCmd, NULL, NULL, 1}, - {"rename", Tcl_RenameObjCmd, NULL, NULL, 1}, - {"return", Tcl_ReturnObjCmd, TclCompileReturnCmd, NULL, 1}, - {"scan", Tcl_ScanObjCmd, NULL, NULL, 1}, - {"set", Tcl_SetObjCmd, TclCompileSetCmd, NULL, 1}, - {"split", Tcl_SplitObjCmd, NULL, NULL, 1}, - {"subst", Tcl_SubstObjCmd, NULL, NULL, 1}, - {"switch", Tcl_SwitchObjCmd, TclCompileSwitchCmd, NULL, 1}, - {"trace", Tcl_TraceObjCmd, NULL, NULL, 1}, - {"unset", Tcl_UnsetObjCmd, NULL, NULL, 1}, - {"uplevel", Tcl_UplevelObjCmd, NULL, TclNRUplevelObjCmd, 1}, - {"upvar", Tcl_UpvarObjCmd, TclCompileUpvarCmd, NULL, 1}, - {"variable", Tcl_VariableObjCmd, TclCompileVariableCmd, NULL, 1}, - {"while", Tcl_WhileObjCmd, TclCompileWhileCmd, TclNRWhileObjCmd, 1}, + {"catch", Tcl_CatchObjCmd, TclCompileCatchCmd, TclNRCatchObjCmd, CMD_IS_SAFE}, + {"concat", Tcl_ConcatObjCmd, TclCompileConcatCmd, NULL, CMD_IS_SAFE}, + {"continue", Tcl_ContinueObjCmd, TclCompileContinueCmd, NULL, CMD_IS_SAFE}, + {"coroutine", NULL, NULL, TclNRCoroutineObjCmd, CMD_IS_SAFE}, + {"error", Tcl_ErrorObjCmd, TclCompileErrorCmd, NULL, CMD_IS_SAFE}, + {"eval", Tcl_EvalObjCmd, NULL, TclNREvalObjCmd, CMD_IS_SAFE}, + {"expr", Tcl_ExprObjCmd, TclCompileExprCmd, TclNRExprObjCmd, CMD_IS_SAFE}, + {"for", Tcl_ForObjCmd, TclCompileForCmd, TclNRForObjCmd, CMD_IS_SAFE}, + {"foreach", Tcl_ForeachObjCmd, TclCompileForeachCmd, TclNRForeachCmd, CMD_IS_SAFE}, + {"format", Tcl_FormatObjCmd, TclCompileFormatCmd, NULL, CMD_IS_SAFE}, + {"global", Tcl_GlobalObjCmd, TclCompileGlobalCmd, NULL, CMD_IS_SAFE}, + {"if", Tcl_IfObjCmd, TclCompileIfCmd, TclNRIfObjCmd, CMD_IS_SAFE}, + {"incr", Tcl_IncrObjCmd, TclCompileIncrCmd, NULL, CMD_IS_SAFE}, + {"join", Tcl_JoinObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"lappend", Tcl_LappendObjCmd, TclCompileLappendCmd, NULL, CMD_IS_SAFE}, + {"lassign", Tcl_LassignObjCmd, TclCompileLassignCmd, NULL, CMD_IS_SAFE}, + {"lindex", Tcl_LindexObjCmd, TclCompileLindexCmd, NULL, CMD_IS_SAFE}, + {"linsert", Tcl_LinsertObjCmd, TclCompileLinsertCmd, NULL, CMD_IS_SAFE}, + {"list", Tcl_ListObjCmd, TclCompileListCmd, NULL, CMD_IS_SAFE|CMD_COMPILES_EXPANDED}, + {"llength", Tcl_LlengthObjCmd, TclCompileLlengthCmd, NULL, CMD_IS_SAFE}, + {"lmap", Tcl_LmapObjCmd, TclCompileLmapCmd, TclNRLmapCmd, CMD_IS_SAFE}, + {"lrange", Tcl_LrangeObjCmd, TclCompileLrangeCmd, NULL, CMD_IS_SAFE}, + {"lrepeat", Tcl_LrepeatObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"lreplace", Tcl_LreplaceObjCmd, TclCompileLreplaceCmd, NULL, CMD_IS_SAFE}, + {"lreverse", Tcl_LreverseObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"lsearch", Tcl_LsearchObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"lset", Tcl_LsetObjCmd, TclCompileLsetCmd, NULL, CMD_IS_SAFE}, + {"lsort", Tcl_LsortObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"package", Tcl_PackageObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"proc", Tcl_ProcObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"regexp", Tcl_RegexpObjCmd, TclCompileRegexpCmd, NULL, CMD_IS_SAFE}, + {"regsub", Tcl_RegsubObjCmd, TclCompileRegsubCmd, NULL, CMD_IS_SAFE}, + {"rename", Tcl_RenameObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"return", Tcl_ReturnObjCmd, TclCompileReturnCmd, NULL, CMD_IS_SAFE}, + {"scan", Tcl_ScanObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"set", Tcl_SetObjCmd, TclCompileSetCmd, NULL, CMD_IS_SAFE}, + {"split", Tcl_SplitObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"subst", Tcl_SubstObjCmd, TclCompileSubstCmd, TclNRSubstObjCmd, CMD_IS_SAFE}, + {"switch", Tcl_SwitchObjCmd, TclCompileSwitchCmd, TclNRSwitchObjCmd, CMD_IS_SAFE}, + {"tailcall", NULL, TclCompileTailcallCmd, TclNRTailcallObjCmd, CMD_IS_SAFE}, + {"throw", Tcl_ThrowObjCmd, TclCompileThrowCmd, NULL, CMD_IS_SAFE}, + {"trace", Tcl_TraceObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"try", Tcl_TryObjCmd, TclCompileTryCmd, TclNRTryObjCmd, CMD_IS_SAFE}, + {"unset", Tcl_UnsetObjCmd, TclCompileUnsetCmd, NULL, CMD_IS_SAFE}, + {"uplevel", Tcl_UplevelObjCmd, NULL, TclNRUplevelObjCmd, CMD_IS_SAFE}, + {"upvar", Tcl_UpvarObjCmd, TclCompileUpvarCmd, NULL, CMD_IS_SAFE}, + {"variable", Tcl_VariableObjCmd, TclCompileVariableCmd, NULL, CMD_IS_SAFE}, + {"while", Tcl_WhileObjCmd, TclCompileWhileCmd, TclNRWhileObjCmd, CMD_IS_SAFE}, + {"yield", NULL, TclCompileYieldCmd, TclNRYieldObjCmd, CMD_IS_SAFE}, + {"yieldto", NULL, TclCompileYieldToCmd, TclNRYieldToObjCmd, CMD_IS_SAFE}, /* * Commands in the OS-interface. Note that many of these are unsafe. */ - {"after", Tcl_AfterObjCmd, NULL, NULL, 1}, + {"after", Tcl_AfterObjCmd, NULL, NULL, CMD_IS_SAFE}, {"cd", Tcl_CdObjCmd, NULL, NULL, 0}, - {"close", Tcl_CloseObjCmd, NULL, NULL, 1}, - {"eof", Tcl_EofObjCmd, NULL, NULL, 1}, + {"close", Tcl_CloseObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"eof", Tcl_EofObjCmd, NULL, NULL, CMD_IS_SAFE}, {"encoding", Tcl_EncodingObjCmd, NULL, NULL, 0}, {"exec", Tcl_ExecObjCmd, NULL, NULL, 0}, {"exit", Tcl_ExitObjCmd, NULL, NULL, 0}, - {"fblocked", Tcl_FblockedObjCmd, NULL, NULL, 1}, + {"fblocked", Tcl_FblockedObjCmd, NULL, NULL, CMD_IS_SAFE}, {"fconfigure", Tcl_FconfigureObjCmd, NULL, NULL, 0}, - {"fcopy", Tcl_FcopyObjCmd, NULL, NULL, 1}, - {"file", Tcl_FileObjCmd, NULL, NULL, 0}, - {"fileevent", Tcl_FileEventObjCmd, NULL, NULL, 1}, - {"flush", Tcl_FlushObjCmd, NULL, NULL, 1}, - {"gets", Tcl_GetsObjCmd, NULL, NULL, 1}, + {"fcopy", Tcl_FcopyObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"fileevent", Tcl_FileEventObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"flush", Tcl_FlushObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"gets", Tcl_GetsObjCmd, NULL, NULL, CMD_IS_SAFE}, {"glob", Tcl_GlobObjCmd, NULL, NULL, 0}, {"load", Tcl_LoadObjCmd, NULL, NULL, 0}, {"open", Tcl_OpenObjCmd, NULL, NULL, 0}, - {"pid", Tcl_PidObjCmd, NULL, NULL, 1}, - {"puts", Tcl_PutsObjCmd, NULL, NULL, 1}, + {"pid", Tcl_PidObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"puts", Tcl_PutsObjCmd, NULL, NULL, CMD_IS_SAFE}, {"pwd", Tcl_PwdObjCmd, NULL, NULL, 0}, - {"read", Tcl_ReadObjCmd, NULL, NULL, 1}, - {"seek", Tcl_SeekObjCmd, NULL, NULL, 1}, + {"read", Tcl_ReadObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"seek", Tcl_SeekObjCmd, NULL, NULL, CMD_IS_SAFE}, {"socket", Tcl_SocketObjCmd, NULL, NULL, 0}, - {"source", Tcl_SourceObjCmd, NULL, NULL, 0}, - {"tell", Tcl_TellObjCmd, NULL, NULL, 1}, - {"time", Tcl_TimeObjCmd, NULL, NULL, 1}, + {"source", Tcl_SourceObjCmd, NULL, TclNRSourceObjCmd, 0}, + {"tell", Tcl_TellObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"time", Tcl_TimeObjCmd, NULL, NULL, CMD_IS_SAFE}, {"unload", Tcl_UnloadObjCmd, NULL, NULL, 0}, - {"update", Tcl_UpdateObjCmd, NULL, NULL, 1}, - {"vwait", Tcl_VwaitObjCmd, NULL, NULL, 1}, + {"update", Tcl_UpdateObjCmd, NULL, NULL, CMD_IS_SAFE}, + {"vwait", Tcl_VwaitObjCmd, NULL, NULL, CMD_IS_SAFE}, {NULL, NULL, NULL, NULL, 0} }; @@ -361,30 +406,6 @@ static const OpCmdInfo mathOpCmds[] = { }; /* - * This is the script cancellation struct and hash table. The hash table is - * used to keep track of the information necessary to process script - * cancellation requests, including the original interp, asynchronous handler - * tokens (created by Tcl_AsyncCreate), and the clientData and flags arguments - * passed to Tcl_CancelEval on a per-interp basis. The cancelLock mutex is - * used for protecting calls to Tcl_CancelEval as well as protecting access to - * the hash table below. - */ - -typedef struct { - Tcl_Interp *interp; /* Interp this struct belongs to */ - Tcl_AsyncHandler async; /* Async handler token for script - * cancellation */ - char *result; /* The script cancellation result or - * NULL for a default result */ - int length; /* Length of the above error message */ - ClientData clientData; /* Ignored */ - int flags; /* Additional flags */ -} CancelInfo; -static Tcl_HashTable cancelTable; -static int cancelTableInitialized = 0; /* 0 means not yet initialized. */ -TCL_DECLARE_MUTEX(cancelLock) - -/* *---------------------------------------------------------------------- * * TclFinalizeEvaluation -- @@ -460,11 +481,23 @@ Tcl_CreateInterp(void) * the Tcl_CallFrame structure (or vice versa). */ - if (sizeof(Tcl_CallFrame) != sizeof(CallFrame)) { + if (sizeof(Tcl_CallFrame) < sizeof(CallFrame)) { /*NOTREACHED*/ - Tcl_Panic("Tcl_CallFrame and CallFrame are not the same size"); + Tcl_Panic("Tcl_CallFrame must not be smaller than CallFrame"); } +#if defined(_WIN32) && !defined(_WIN64) + if (sizeof(time_t) != 4) { + /*NOTREACHED*/ + Tcl_Panic("<time.h> is not compatible with MSVC"); + } + if ((TclOffset(Tcl_StatBuf,st_atime) != 32) + || (TclOffset(Tcl_StatBuf,st_ctime) != 40)) { + /*NOTREACHED*/ + Tcl_Panic("<sys/stat.h> is not compatible with MSVC"); + } +#endif + if (cancelTableInitialized == 0) { Tcl_MutexLock(&cancelLock); if (cancelTableInitialized == 0) { @@ -480,7 +513,7 @@ Tcl_CreateInterp(void) * object type table and other object management code. */ - iPtr = (Interp *) ckalloc(sizeof(Interp)); + iPtr = ckalloc(sizeof(Interp)); interp = (Tcl_Interp *) iPtr; iPtr->result = iPtr->resultSpace; @@ -493,25 +526,29 @@ Tcl_CreateInterp(void) iPtr->hiddenCmdTablePtr = NULL; iPtr->interpInfo = NULL; + TCL_CT_ASSERT(sizeof(iPtr->extra) <= sizeof(Tcl_HashTable)); + iPtr->extra.optimizer = TclOptimizeBytecode; + iPtr->numLevels = 0; iPtr->maxNestingDepth = MAX_NESTING_DEPTH; iPtr->framePtr = NULL; /* Initialise as soon as :: is available */ iPtr->varFramePtr = NULL; /* Initialise as soon as :: is available */ /* - * TIP #280 - Initialize the arrays used to extend the ByteCode and - * Proc structures. + * TIP #280 - Initialize the arrays used to extend the ByteCode and Proc + * structures. */ iPtr->cmdFramePtr = NULL; - iPtr->linePBodyPtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); - iPtr->lineBCPtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); - iPtr->lineLAPtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); - iPtr->lineLABCPtr = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); + iPtr->linePBodyPtr = ckalloc(sizeof(Tcl_HashTable)); + iPtr->lineBCPtr = ckalloc(sizeof(Tcl_HashTable)); + iPtr->lineLAPtr = ckalloc(sizeof(Tcl_HashTable)); + iPtr->lineLABCPtr = ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->linePBodyPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineBCPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineLAPtr, TCL_ONE_WORD_KEYS); Tcl_InitHashTable(iPtr->lineLABCPtr, TCL_ONE_WORD_KEYS); + iPtr->scriptCLLocPtr = NULL; iPtr->activeVarTracePtr = NULL; @@ -519,6 +556,17 @@ 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); + TclNewLiteralStringObj(iPtr->innerLiteral,"INNER"); + Tcl_IncrRefCount(iPtr->innerLiteral); + iPtr->innerContext = Tcl_NewListObj(0, NULL); + Tcl_IncrRefCount(iPtr->innerContext); iPtr->errorCode = NULL; TclNewLiteralStringObj(iPtr->ecVar, "::errorCode"); Tcl_IncrRefCount(iPtr->ecVar); @@ -543,7 +591,7 @@ Tcl_CreateInterp(void) } iPtr->cmdCount = 0; - TclInitLiteralTable(&(iPtr->literalTable)); + TclInitLiteralTable(&iPtr->literalTable); iPtr->compileEpoch = 0; iPtr->compiledProcPtr = NULL; iPtr->resolverPtr = NULL; @@ -562,6 +610,15 @@ Tcl_CreateInterp(void) iPtr->resultSpace[0] = 0; iPtr->threadId = Tcl_GetCurrentThread(); + /* TIP #378 */ +#ifdef TCL_INTERP_DEBUG_FRAME + iPtr->flags |= INTERP_DEBUG_FRAME; +#else + if (getenv("TCL_INTERP_DEBUG_FRAME") != NULL) { + iPtr->flags |= INTERP_DEBUG_FRAME; + } +#endif + /* * Initialise the tables for variable traces and searches *before* * creating the global ns - so that the trace on errorInfo can be @@ -584,7 +641,7 @@ Tcl_CreateInterp(void) */ /* This is needed to satisfy GCC 3.3's strict aliasing rules */ - framePtr = (CallFrame *) ckalloc(sizeof(CallFrame)); + framePtr = ckalloc(sizeof(CallFrame)); result = Tcl_PushCallFrame(interp, (Tcl_CallFrame *) framePtr, (Tcl_Namespace *) iPtr->globalNsPtr, /*isProcCallFrame*/ 0); if (result != TCL_OK) { @@ -603,7 +660,7 @@ Tcl_CreateInterp(void) * variable). */ - iPtr->execEnvPtr = TclCreateExecEnv(interp); + iPtr->execEnvPtr = TclCreateExecEnv(interp, INTERP_STACK_INITIAL_SIZE); /* * TIP #219, Tcl Channel Reflection API support. @@ -617,7 +674,7 @@ Tcl_CreateInterp(void) iPtr->asyncCancelMsg = Tcl_NewObj(); - cancelInfo = (CancelInfo *) ckalloc(sizeof(CancelInfo)); + cancelInfo = ckalloc(sizeof(CancelInfo)); cancelInfo->interp = interp; iPtr->asyncCancel = Tcl_AsyncCreate(CancelEvalProc, cancelInfo); @@ -626,7 +683,7 @@ Tcl_CreateInterp(void) cancelInfo->length = 0; Tcl_MutexLock(&cancelLock); - hPtr = Tcl_CreateHashEntry(&cancelTable, (char *) iPtr, &isNew); + hPtr = Tcl_CreateHashEntry(&cancelTable, iPtr, &isNew); Tcl_SetHashValue(hPtr, cancelInfo); Tcl_MutexUnlock(&cancelLock); @@ -636,7 +693,7 @@ Tcl_CreateInterp(void) */ #ifdef TCL_COMPILE_STATS - statsPtr = &(iPtr->stats); + statsPtr = &iPtr->stats; statsPtr->numExecutions = 0; statsPtr->numCompilations = 0; statsPtr->numByteCodesFreed = 0; @@ -667,7 +724,7 @@ Tcl_CreateInterp(void) * Initialise the stub table pointer. */ - iPtr->stubTable = tclConstStubsPtr; + iPtr->stubTable = &tclStubs; /* * Initialize the ensemble error message rewriting support. @@ -695,7 +752,7 @@ Tcl_CreateInterp(void) #endif iPtr->pendingObjDataPtr = NULL; iPtr->asyncReadyPtr = TclGetAsyncReadyPtr(); - iPtr->atExitPtr = NULL; + iPtr->deferredCallbacks = NULL; /* * Create the core commands. Do it here, rather than calling @@ -708,16 +765,17 @@ Tcl_CreateInterp(void) * Tcl_CmdProc, set the Tcl_CmdProc to TclInvokeObjectCommand. */ - for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { + for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { if ((cmdInfoPtr->objProc == NULL) - && (cmdInfoPtr->compileProc == NULL)) { + && (cmdInfoPtr->compileProc == NULL) + && (cmdInfoPtr->nreProc == NULL)) { Tcl_Panic("builtin command with NULL object command proc and a NULL compile proc"); } hPtr = Tcl_CreateHashEntry(&iPtr->globalNsPtr->cmdTable, cmdInfoPtr->name, &isNew); if (isNew) { - cmdPtr = (Command *) ckalloc(sizeof(Command)); + cmdPtr = ckalloc(sizeof(Command)); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = iPtr->globalNsPtr; cmdPtr->refCount = 1; @@ -730,6 +788,9 @@ Tcl_CreateInterp(void) cmdPtr->deleteProc = NULL; cmdPtr->deleteData = NULL; cmdPtr->flags = 0; + if (cmdInfoPtr->flags & CMD_COMPILES_EXPANDED) { + cmdPtr->flags |= CMD_COMPILES_EXPANDED; + } cmdPtr->importRefPtr = NULL; cmdPtr->tracePtr = NULL; cmdPtr->nreProc = cmdInfoPtr->nreProc; @@ -738,16 +799,21 @@ Tcl_CreateInterp(void) } /* - * Create the "binary", "chan", "dict", "info" and "string" ensembles. - * Note that all these commands (and their subcommands that are not - * present in the global namespace) are wholly safe. + * Create the "array", "binary", "chan", "dict", "file", "info", + * "namespace" and "string" ensembles. Note that all these commands (and + * their subcommands that are not present in the global namespace) are + * wholly safe *except* for "file". */ + TclInitArrayCmd(interp); TclInitBinaryCmd(interp); TclInitChanCmd(interp); TclInitDictCmd(interp); + TclInitFileCmd(interp); TclInitInfoCmd(interp); + TclInitNamespaceCmd(interp); TclInitStringCmd(interp); + TclInitPrefixCmd(interp); /* * Register "clock" subcommands. These *do* go through @@ -770,28 +836,22 @@ Tcl_CreateInterp(void) TclDefaultBgErrorHandlerObjCmd, NULL, NULL); /* - * Create an unsupported command for debugging bytecode. + * Create unsupported commands for debugging bytecode and objects. */ Tcl_CreateObjCommand(interp, "::tcl::unsupported::disassemble", Tcl_DisassembleObjCmd, NULL, NULL); + Tcl_CreateObjCommand(interp, "::tcl::unsupported::representation", + Tcl_RepresentationCmd, NULL, NULL); - /* - * Create unsupported commands for tailcall, coroutine and yield - * Create unsupported commands for atProcExit and tailcall - */ - - Tcl_NRCreateCommand(interp, "::tcl::unsupported::atProcExit", - /*objProc*/ NULL, TclNRAtProcExitObjCmd, INT2PTR(TCL_NR_ATEXIT_TYPE), - NULL); - Tcl_NRCreateCommand(interp, "::tcl::unsupported::tailcall", - /*objProc*/ NULL, TclNRAtProcExitObjCmd, INT2PTR(TCL_NR_TAILCALL_TYPE), - NULL); + /* Adding the bytecode assembler command */ + cmdPtr = (Command *) Tcl_NRCreateCommand(interp, + "::tcl::unsupported::assemble", Tcl_AssembleObjCmd, + TclNRAssembleObjCmd, NULL, NULL); + cmdPtr->compileProc = &TclCompileAssembleCmd; - Tcl_NRCreateCommand(interp, "::tcl::unsupported::coroutine", - /*objProc*/ NULL, TclNRCoroutineObjCmd, NULL, NULL); - Tcl_NRCreateCommand(interp, "::tcl::unsupported::yield", - /*objProc*/ NULL, TclNRYieldObjCmd, NULL, NULL); + Tcl_NRCreateCommand(interp, "::tcl::unsupported::inject", NULL, + NRCoroInjectObjCmd, NULL, NULL); #ifdef USE_DTRACE /* @@ -809,8 +869,8 @@ Tcl_CreateInterp(void) if (mathfuncNSPtr == NULL) { Tcl_Panic("Can't create math function namespace"); } - strcpy(mathFuncName, "::tcl::mathfunc::"); #define MATH_FUNC_PREFIX_LEN 17 /* == strlen("::tcl::mathfunc::") */ + memcpy(mathFuncName, "::tcl::mathfunc::", MATH_FUNC_PREFIX_LEN); for (builtinFuncPtr = BuiltinFuncTable; builtinFuncPtr->name != NULL; builtinFuncPtr++) { strcpy(mathFuncName+MATH_FUNC_PREFIX_LEN, builtinFuncPtr->name); @@ -824,15 +884,14 @@ Tcl_CreateInterp(void) */ mathopNSPtr = Tcl_CreateNamespace(interp, "::tcl::mathop", NULL, NULL); -#define MATH_OP_PREFIX_LEN 15 /* == strlen("::tcl::mathop::") */ if (mathopNSPtr == NULL) { Tcl_Panic("can't create math operator namespace"); } Tcl_Export(interp, mathopNSPtr, "*", 1); - strcpy(mathFuncName, "::tcl::mathop::"); +#define MATH_OP_PREFIX_LEN 15 /* == strlen("::tcl::mathop::") */ + memcpy(mathFuncName, "::tcl::mathop::", MATH_OP_PREFIX_LEN); for (opcmdInfoPtr=mathOpCmds ; opcmdInfoPtr->name!=NULL ; opcmdInfoPtr++){ - TclOpCmdClientData *occdPtr = (TclOpCmdClientData *) - ckalloc(sizeof(TclOpCmdClientData)); + TclOpCmdClientData *occdPtr = ckalloc(sizeof(TclOpCmdClientData)); occdPtr->op = opcmdInfoPtr->name; occdPtr->i.numArgs = opcmdInfoPtr->i.numArgs; @@ -904,17 +963,27 @@ Tcl_CreateInterp(void) * TIP #268: Full patchlevel instead of just major.minor */ - Tcl_PkgProvideEx(interp, "Tcl", TCL_PATCH_LEVEL, - (ClientData) tclConstStubsPtr); + Tcl_PkgProvideEx(interp, "Tcl", TCL_PATCH_LEVEL, &tclStubs); if (TclTommath_Init(interp) != TCL_OK) { - Tcl_Panic(Tcl_GetString(Tcl_GetObjResult(interp))); + Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp))); } if (TclOOInit(interp) != TCL_OK) { - Tcl_Panic(Tcl_GetString(Tcl_GetObjResult(interp))); + Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp))); } + /* + * Only build in zlib support if we've successfully detected a library to + * compile and link against. + */ + +#ifdef HAVE_ZLIB + if (TclZlibInit(interp) != TCL_OK) { + Tcl_Panic("%s", Tcl_GetString(Tcl_GetObjResult(interp))); + } +#endif + TOP_CB(iPtr) = NULL; return interp; } @@ -925,7 +994,7 @@ DeleteOpCmdClientData( { TclOpCmdClientData *occdPtr = clientData; - ckfree((char *) occdPtr); + ckfree(occdPtr); } /* @@ -954,10 +1023,11 @@ TclHideUnsafeCommands( return TCL_ERROR; } for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) { - if (!cmdInfoPtr->isSafe) { + if (!(cmdInfoPtr->flags & CMD_IS_SAFE)) { Tcl_HideCommand(interp, cmdInfoPtr->name, cmdInfoPtr->name); } } + TclMakeFileCommandSafe(interp); /* Ugh! */ return TCL_OK; } @@ -995,14 +1065,14 @@ Tcl_CallWhenDeleted( Tcl_GetThreadData(&assocDataCounterKey, (int)sizeof(int)); int isNew; char buffer[32 + TCL_INTEGER_SPACE]; - AssocData *dPtr = (AssocData *) ckalloc(sizeof(AssocData)); + AssocData *dPtr = ckalloc(sizeof(AssocData)); Tcl_HashEntry *hPtr; sprintf(buffer, "Assoc Data Key #%d", *assocDataCounterPtr); (*assocDataCounterPtr)++; if (iPtr->assocData == NULL) { - iPtr->assocData = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); + iPtr->assocData = ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS); } hPtr = Tcl_CreateHashEntry(iPtr->assocData, buffer, &isNew); @@ -1051,7 +1121,7 @@ Tcl_DontCallWhenDeleted( hPtr = Tcl_NextHashEntry(&hSearch)) { dPtr = Tcl_GetHashValue(hPtr); if ((dPtr->proc == proc) && (dPtr->clientData == clientData)) { - ckfree((char *) dPtr); + ckfree(dPtr); Tcl_DeleteHashEntry(hPtr); return; } @@ -1091,14 +1161,14 @@ Tcl_SetAssocData( int isNew; if (iPtr->assocData == NULL) { - iPtr->assocData = (Tcl_HashTable *) ckalloc(sizeof(Tcl_HashTable)); + iPtr->assocData = ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(iPtr->assocData, TCL_STRING_KEYS); } hPtr = Tcl_CreateHashEntry(iPtr->assocData, name, &isNew); if (isNew == 0) { dPtr = Tcl_GetHashValue(hPtr); } else { - dPtr = (AssocData *) ckalloc(sizeof(AssocData)); + dPtr = ckalloc(sizeof(AssocData)); } dPtr->proc = proc; dPtr->clientData = clientData; @@ -1143,7 +1213,7 @@ Tcl_DeleteAssocData( if (dPtr->proc != NULL) { dPtr->proc(dPtr->clientData, interp); } - ckfree((char *) dPtr); + ckfree(dPtr); Tcl_DeleteHashEntry(hPtr); } @@ -1298,12 +1368,14 @@ DeleteInterpProc( Tcl_HashSearch search; Tcl_HashTable *hTablePtr; ResolverScheme *resPtr, *nextResPtr; + int i; /* - * Punt if there is an error in the Tcl_Release/Tcl_Preserve matchup. + * Punt if there is an error in the Tcl_Release/Tcl_Preserve matchup, + * unless we are exiting. */ - if (iPtr->numLevels > 0) { + if ((iPtr->numLevels > 0) && !TclInExit()) { Tcl_Panic("DeleteInterpProc called with active evals"); } @@ -1337,9 +1409,9 @@ DeleteInterpProc( if (cancelInfo != NULL) { if (cancelInfo->result != NULL) { - ckfree((char *) cancelInfo->result); + ckfree(cancelInfo->result); } - ckfree((char *) cancelInfo); + ckfree(cancelInfo); } Tcl_DeleteHashEntry(hPtr); @@ -1373,7 +1445,6 @@ DeleteInterpProc( * table, as it will be freed later in this function without further use. */ - TclCleanupLiteralTable(interp, &(iPtr->literalTable)); TclHandleFree(iPtr->handle); TclTeardownNamespace(iPtr->globalNsPtr); @@ -1386,7 +1457,7 @@ DeleteInterpProc( /* * Non-pernicious deletion. The deletion callbacks will not be allowed * to create any new hidden or non-hidden commands. - * Tcl_DeleteCommandFromToken() will remove the entry from the + * Tcl_DeleteCommandFromToken will remove the entry from the * hiddenCmdTablePtr. */ @@ -1395,7 +1466,7 @@ DeleteInterpProc( Tcl_DeleteCommandFromToken(interp, Tcl_GetHashValue(hPtr)); } Tcl_DeleteHashTable(hTablePtr); - ckfree((char *) hTablePtr); + ckfree(hTablePtr); } /* @@ -1416,10 +1487,10 @@ DeleteInterpProc( if (dPtr->proc != NULL) { dPtr->proc(dPtr->clientData, interp); } - ckfree((char *) dPtr); + ckfree(dPtr); } Tcl_DeleteHashTable(hTablePtr); - ckfree((char *) hTablePtr); + ckfree(hTablePtr); } /* @@ -1427,11 +1498,11 @@ DeleteInterpProc( * namespace. The order is important [Bug 1658572]. */ - if (iPtr->framePtr != iPtr->rootFramePtr) { + if ((iPtr->framePtr != iPtr->rootFramePtr) && !TclInExit()) { Tcl_Panic("DeleteInterpProc: popping rootCallFrame with other frames on top"); } Tcl_PopCallFrame(interp); - ckfree((char *) iPtr->rootFramePtr); + ckfree(iPtr->rootFramePtr); iPtr->rootFramePtr = NULL; Tcl_DeleteNamespace((Tcl_Namespace *) iPtr->globalNsPtr); @@ -1441,7 +1512,7 @@ DeleteInterpProc( */ Tcl_FreeResult(interp); - interp->result = NULL; + iPtr->result = NULL; Tcl_DecrRefCount(iPtr->objResultPtr); iPtr->objResultPtr = NULL; Tcl_DecrRefCount(iPtr->ecVar); @@ -1454,6 +1525,12 @@ DeleteInterpProc( Tcl_DecrRefCount(iPtr->errorInfo); iPtr->errorInfo = NULL; } + Tcl_DecrRefCount(iPtr->errorStack); + iPtr->errorStack = NULL; + Tcl_DecrRefCount(iPtr->upLiteral); + Tcl_DecrRefCount(iPtr->callLiteral); + Tcl_DecrRefCount(iPtr->innerLiteral); + Tcl_DecrRefCount(iPtr->innerContext); if (iPtr->returnOpts) { Tcl_DecrRefCount(iPtr->returnOpts); } @@ -1468,6 +1545,10 @@ DeleteInterpProc( if (iPtr->execEnvPtr != NULL) { TclDeleteExecEnv(iPtr->execEnvPtr); } + if (iPtr->scriptFile) { + Tcl_DecrRefCount(iPtr->scriptFile); + iPtr->scriptFile = NULL; + } Tcl_DecrRefCount(iPtr->emptyObjPtr); iPtr->emptyObjPtr = NULL; @@ -1475,7 +1556,7 @@ DeleteInterpProc( while (resPtr) { nextResPtr = resPtr->nextPtr; ckfree(resPtr->name); - ckfree((char *) resPtr); + ckfree(resPtr); resPtr = nextResPtr; } @@ -1484,103 +1565,101 @@ DeleteInterpProc( * interpreter. */ - TclDeleteLiteralTable(interp, &(iPtr->literalTable)); + TclDeleteLiteralTable(interp, &iPtr->literalTable); /* * TIP #280 - Release the arrays for ByteCode/Proc extension, and * contents. */ - { - Tcl_HashEntry *hPtr; - Tcl_HashSearch hSearch; - int i; - - for (hPtr = Tcl_FirstHashEntry(iPtr->linePBodyPtr, &hSearch); - hPtr != NULL; - hPtr = Tcl_NextHashEntry(&hSearch)) { - CmdFrame *cfPtr = Tcl_GetHashValue(hPtr); + for (hPtr = Tcl_FirstHashEntry(iPtr->linePBodyPtr, &search); + hPtr != NULL; + hPtr = Tcl_NextHashEntry(&search)) { + CmdFrame *cfPtr = Tcl_GetHashValue(hPtr); + Proc *procPtr = (Proc *) Tcl_GetHashKey(iPtr->linePBodyPtr, hPtr); + procPtr->iPtr = NULL; + if (cfPtr) { if (cfPtr->type == TCL_LOCATION_SOURCE) { Tcl_DecrRefCount(cfPtr->data.eval.path); } - ckfree((char *) cfPtr->line); - ckfree((char *) cfPtr); - Tcl_DeleteHashEntry(hPtr); + ckfree(cfPtr->line); + ckfree(cfPtr); } - Tcl_DeleteHashTable(iPtr->linePBodyPtr); - ckfree((char *) iPtr->linePBodyPtr); - iPtr->linePBodyPtr = NULL; + Tcl_DeleteHashEntry(hPtr); + } + Tcl_DeleteHashTable(iPtr->linePBodyPtr); + ckfree(iPtr->linePBodyPtr); + iPtr->linePBodyPtr = NULL; - /* - * See also tclCompile.c, TclCleanupByteCode - */ + /* + * See also tclCompile.c, TclCleanupByteCode + */ - for (hPtr = Tcl_FirstHashEntry(iPtr->lineBCPtr, &hSearch); - hPtr != NULL; - hPtr = Tcl_NextHashEntry(&hSearch)) { - ExtCmdLoc *eclPtr = Tcl_GetHashValue(hPtr); + for (hPtr = Tcl_FirstHashEntry(iPtr->lineBCPtr, &search); + hPtr != NULL; + hPtr = Tcl_NextHashEntry(&search)) { + ExtCmdLoc *eclPtr = Tcl_GetHashValue(hPtr); - if (eclPtr->type == TCL_LOCATION_SOURCE) { - Tcl_DecrRefCount(eclPtr->path); - } - for (i=0; i< eclPtr->nuloc; i++) { - ckfree((char *) eclPtr->loc[i].line); - } + if (eclPtr->type == TCL_LOCATION_SOURCE) { + Tcl_DecrRefCount(eclPtr->path); + } + for (i=0; i< eclPtr->nuloc; i++) { + ckfree(eclPtr->loc[i].line); + } - if (eclPtr->loc != NULL) { - ckfree((char *) eclPtr->loc); - } + if (eclPtr->loc != NULL) { + ckfree(eclPtr->loc); + } - if (eclPtr->eiloc != NULL) { - ckfree((char *) eclPtr->eiloc); - } + ckfree(eclPtr); + Tcl_DeleteHashEntry(hPtr); + } + Tcl_DeleteHashTable(iPtr->lineBCPtr); + ckfree(iPtr->lineBCPtr); + iPtr->lineBCPtr = NULL; - ckfree((char *) eclPtr); - Tcl_DeleteHashEntry(hPtr); - } - Tcl_DeleteHashTable(iPtr->lineBCPtr); - ckfree((char *) iPtr->lineBCPtr); - iPtr->lineBCPtr = NULL; + /* + * Location stack for uplevel/eval/... scripts which were passed through + * proc arguments. Actually we track all arguments as we do not and cannot + * know which arguments will be used as scripts and which will not. + */ + if (iPtr->lineLAPtr->numEntries && !TclInExit()) { /* - * Location stack for uplevel/eval/... scripts which were passed - * through proc arguments. Actually we track all arguments as we - * don't, cannot know which arguments will be used as scripts and - * which won't. + * When the interp goes away we have nothing on the stack, so there + * are no arguments, so this table has to be empty. */ - if (iPtr->lineLAPtr->numEntries) { - /* - * When the interp goes away we have nothing on the stack, so - * there are no arguments, so this table has to be empty. - */ + Tcl_Panic("Argument location tracking table not empty"); + } - Tcl_Panic("Argument location tracking table not empty"); - } + Tcl_DeleteHashTable(iPtr->lineLAPtr); + ckfree((char *) iPtr->lineLAPtr); + iPtr->lineLAPtr = NULL; - Tcl_DeleteHashTable(iPtr->lineLAPtr); - ckfree((char *) iPtr->lineLAPtr); - iPtr->lineLAPtr = NULL; + if (iPtr->lineLABCPtr->numEntries && !TclInExit()) { + /* + * When the interp goes away we have nothing on the stack, so there + * are no arguments, so this table has to be empty. + */ - if (iPtr->lineLABCPtr->numEntries) { - /* - * When the interp goes away we have nothing on the stack, so - * there are no arguments, so this table has to be empty. - */ + Tcl_Panic("Argument location tracking table not empty"); + } - Tcl_Panic("Argument location tracking table not empty"); - } + Tcl_DeleteHashTable(iPtr->lineLABCPtr); + ckfree(iPtr->lineLABCPtr); + iPtr->lineLABCPtr = NULL; - Tcl_DeleteHashTable(iPtr->lineLABCPtr); - ckfree((char *) iPtr->lineLABCPtr); - iPtr->lineLABCPtr = NULL; - } + /* + * Squelch the tables of traces on variables and searches over arrays in + * the in the interpreter. + */ Tcl_DeleteHashTable(&iPtr->varTraces); Tcl_DeleteHashTable(&iPtr->varSearches); - ckfree((char *) iPtr); + ckfree(iPtr); } /* @@ -1646,9 +1725,10 @@ Tcl_HideCommand( */ if (strstr(hiddenCmdToken, "::") != NULL) { - Tcl_AppendResult(interp, + Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot use namespace qualifiers in hidden command" - " token (rename)", NULL); + " token (rename)", -1)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "HIDDENTOKEN", NULL); return TCL_ERROR; } @@ -1670,8 +1750,10 @@ Tcl_HideCommand( */ if (cmdPtr->nsPtr != iPtr->globalNsPtr) { - Tcl_AppendResult(interp, "can only hide global namespace commands" - " (use rename then hide)", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can only hide global namespace commands (use rename then hide)", + -1)); + Tcl_SetErrorCode(interp, "TCL", "HIDE", "NON_GLOBAL", NULL); return TCL_ERROR; } @@ -1681,8 +1763,7 @@ Tcl_HideCommand( hiddenCmdTablePtr = iPtr->hiddenCmdTablePtr; if (hiddenCmdTablePtr == NULL) { - hiddenCmdTablePtr = (Tcl_HashTable *) - ckalloc((unsigned) sizeof(Tcl_HashTable)); + hiddenCmdTablePtr = ckalloc(sizeof(Tcl_HashTable)); Tcl_InitHashTable(hiddenCmdTablePtr, TCL_STRING_KEYS); iPtr->hiddenCmdTablePtr = hiddenCmdTablePtr; } @@ -1695,8 +1776,10 @@ Tcl_HideCommand( hPtr = Tcl_CreateHashEntry(hiddenCmdTablePtr, hiddenCmdToken, &isNew); if (!isNew) { - Tcl_AppendResult(interp, "hidden command named \"", hiddenCmdToken, - "\" already exists", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "hidden command named \"%s\" already exists", + hiddenCmdToken)); + Tcl_SetErrorCode(interp, "TCL", "HIDE", "ALREADY_HIDDEN", NULL); return TCL_ERROR; } @@ -1797,8 +1880,10 @@ Tcl_ExposeCommand( */ if (strstr(cmdName, "::") != NULL) { - Tcl_AppendResult(interp, "cannot expose to a namespace " - "(use expose to toplevel, then rename)", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "cannot expose to a namespace (use expose to toplevel, then rename)", + -1)); + Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "NON_GLOBAL", NULL); return TCL_ERROR; } @@ -1812,27 +1897,29 @@ Tcl_ExposeCommand( hPtr = Tcl_FindHashEntry(hiddenCmdTablePtr, hiddenCmdToken); } if (hPtr == NULL) { - Tcl_AppendResult(interp, "unknown hidden command \"", hiddenCmdToken, - "\"", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unknown hidden command \"%s\"", hiddenCmdToken)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN", + hiddenCmdToken, NULL); return TCL_ERROR; } cmdPtr = Tcl_GetHashValue(hPtr); /* * Check that we have a true global namespace command (enforced by - * Tcl_HideCommand() but let's double check. (If it was not, we would not + * Tcl_HideCommand but let's double check. (If it was not, we would not * really know how to handle it). */ if (cmdPtr->nsPtr != iPtr->globalNsPtr) { /* - * This case is theoritically impossible, we might rather Tcl_Panic() + * This case is theoritically impossible, we might rather Tcl_Panic * than 'nicely' erroring out ? */ - Tcl_AppendResult(interp, - "trying to expose a non global command name space command", - NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "trying to expose a non-global command namespace command", + -1)); return TCL_ERROR; } @@ -1849,12 +1936,24 @@ Tcl_ExposeCommand( hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, cmdName, &isNew); if (!isNew) { - Tcl_AppendResult(interp, "exposed command \"", cmdName, - "\" already exists", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "exposed command \"%s\" already exists", cmdName)); + Tcl_SetErrorCode(interp, "TCL", "EXPOSE", "COMMAND_EXISTS", NULL); return TCL_ERROR; } /* + * Command resolvers (per-interp, per-namespace) might have resolved to a + * command for the given namespace scope with this command not being + * registered with the namespace's command table. During BC compilation, + * the so-resolved command turns into a CmdName literal. Without + * invalidating a possible CmdName literal here explicitly, such literals + * keep being reused while pointing to overhauled commands. + */ + + TclInvalidateCmdLiteral(interp, cmdName, nsPtr); + + /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we need * the info will be soon enough. @@ -1986,10 +2085,19 @@ Tcl_CreateCommand( */ cmdPtr = Tcl_GetHashValue(hPtr); - oldRefPtr = cmdPtr->importRefPtr; - cmdPtr->importRefPtr = NULL; + cmdPtr->refCount++; + if (cmdPtr->importRefPtr) { + cmdPtr->flags |= CMD_REDEF_IN_PROGRESS; + } Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); + + if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) { + oldRefPtr = cmdPtr->importRefPtr; + cmdPtr->importRefPtr = NULL; + } + TclCleanupCommandMacro(cmdPtr); + hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); if (!isNew) { /* @@ -1998,10 +2106,22 @@ Tcl_CreateCommand( * stuck in an infinite loop). */ - ckfree(Tcl_GetHashValue(hPtr)); + ckfree(Tcl_GetHashValue(hPtr)); } } else { /* + * Command resolvers (per-interp, per-namespace) might have resolved + * to a command for the given namespace scope with this command not + * being registered with the namespace's command table. During BC + * compilation, the so-resolved command turns into a CmdName literal. + * Without invalidating a possible CmdName literal here explicitly, + * such literals keep being reused while pointing to overhauled + * commands. + */ + + TclInvalidateCmdLiteral(interp, tail, nsPtr); + + /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we * need the info will be soon enough. @@ -2010,7 +2130,7 @@ Tcl_CreateCommand( TclInvalidateNsCmdLookup(nsPtr); TclInvalidateNsPath(nsPtr); } - cmdPtr = (Command *) ckalloc(sizeof(Command)); + cmdPtr = ckalloc(sizeof(Command)); Tcl_SetHashValue(hPtr, cmdPtr); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = nsPtr; @@ -2066,12 +2186,9 @@ Tcl_CreateCommand( * future calls to Tcl_GetCommandName. * * Side effects: - * If no command named "cmdName" already exists for interp, one is - * created. Otherwise, if a command does exist, then if the object-based - * Tcl_ObjCmdProc is TclInvokeStringCommand, we assume Tcl_CreateCommand - * was called previously for the same command and just set its - * Tcl_ObjCmdProc to the argument "proc"; otherwise, we delete the old - * command. + * If a command named "cmdName" already exists for interp, it is + * first deleted. Then the new command is created from the arguments. + * [***] (See below for exception). * * In the future, during bytecode evaluation when "cmdName" is seen as * the name of a command by Tcl_EvalObj or Tcl_Eval, the object-based @@ -2138,17 +2255,22 @@ Tcl_CreateObjCommand( if (!isNew) { cmdPtr = Tcl_GetHashValue(hPtr); + /* Command already exists. */ + /* - * Command already exists. If its object-based Tcl_ObjCmdProc is - * TclInvokeStringCommand, we just set its Tcl_ObjCmdProc to the - * argument "proc". Otherwise, we delete the old command. + * [***] This is wrong. See Tcl Bug a16752c252. + * However, this buggy behavior is kept under particular + * circumstances to accommodate deployed binaries of the + * "tclcompiler" program. http://sourceforge.net/projects/tclpro/ + * that crash if the bug is fixed. */ - if (cmdPtr->objProc == TclInvokeStringCommand) { + if (cmdPtr->objProc == TclInvokeStringCommand + && cmdPtr->clientData == clientData + && cmdPtr->deleteData == clientData + && cmdPtr->deleteProc == deleteProc) { cmdPtr->objProc = proc; cmdPtr->objClientData = clientData; - cmdPtr->deleteProc = deleteProc; - cmdPtr->deleteData = clientData; return (Tcl_Command) cmdPtr; } @@ -2159,10 +2281,19 @@ Tcl_CreateObjCommand( * intact. */ - oldRefPtr = cmdPtr->importRefPtr; - cmdPtr->importRefPtr = NULL; + cmdPtr->refCount++; + if (cmdPtr->importRefPtr) { + cmdPtr->flags |= CMD_REDEF_IN_PROGRESS; + } Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); + + if (cmdPtr->flags & CMD_REDEF_IN_PROGRESS) { + oldRefPtr = cmdPtr->importRefPtr; + cmdPtr->importRefPtr = NULL; + } + TclCleanupCommandMacro(cmdPtr); + hPtr = Tcl_CreateHashEntry(&nsPtr->cmdTable, tail, &isNew); if (!isNew) { /* @@ -2171,10 +2302,22 @@ Tcl_CreateObjCommand( * stuck in an infinite loop). */ - ckfree(Tcl_GetHashValue(hPtr)); + ckfree(Tcl_GetHashValue(hPtr)); } } else { /* + * Command resolvers (per-interp, per-namespace) might have resolved + * to a command for the given namespace scope with this command not + * being registered with the namespace's command table. During BC + * compilation, the so-resolved command turns into a CmdName literal. + * Without invalidating a possible CmdName literal here explicitly, + * such literals keep being reused while pointing to overhauled + * commands. + */ + + TclInvalidateCmdLiteral(interp, tail, nsPtr); + + /* * The list of command exported from the namespace might have changed. * However, we do not need to recompute this just yet; next time we * need the info will be soon enough. @@ -2182,7 +2325,7 @@ Tcl_CreateObjCommand( TclInvalidateNsCmdLookup(nsPtr); } - cmdPtr = (Command *) ckalloc(sizeof(Command)); + cmdPtr = ckalloc(sizeof(Command)); Tcl_SetHashValue(hPtr, cmdPtr); cmdPtr->hPtr = hPtr; cmdPtr->nsPtr = nsPtr; @@ -2268,9 +2411,9 @@ TclInvokeStringCommand( * Invoke the command's string-based Tcl_CmdProc. */ - result = (*cmdPtr->proc)(cmdPtr->clientData, interp, objc, argv); + result = cmdPtr->proc(cmdPtr->clientData, interp, objc, argv); - TclStackFree(interp, (char **)argv); + TclStackFree(interp, (void *) argv); return result; } @@ -2289,8 +2432,8 @@ TclInvokeStringCommand( * A standard Tcl string result value. * * Side effects: - * Besides those side effects of the called Tcl_CmdProc, - * TclInvokeStringCommand allocates and frees storage. + * Besides those side effects of the called Tcl_ObjCmdProc, + * TclInvokeObjectCommand allocates and frees storage. * *---------------------------------------------------------------------- */ @@ -2319,7 +2462,12 @@ TclInvokeObjectCommand( * Invoke the command's object-based Tcl_ObjCmdProc. */ - result = (*cmdPtr->objProc)(cmdPtr->objClientData, interp, argc, objv); + if (cmdPtr->objProc != NULL) { + result = cmdPtr->objProc(cmdPtr->objClientData, interp, argc, objv); + } else { + result = Tcl_NRCallObjProc(interp, cmdPtr->nreProc, + cmdPtr->objClientData, argc, objv); + } /* * Move the interpreter's object result to the string result, then reset @@ -2389,9 +2537,11 @@ TclRenameCommand( cmd = Tcl_FindCommand(interp, oldName, NULL, /*flags*/ 0); cmdPtr = (Command *) cmd; if (cmdPtr == NULL) { - Tcl_AppendResult(interp, "can't ", + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't %s \"%s\": command doesn't exist", ((newName == NULL)||(*newName == '\0'))? "delete":"rename", - " \"", oldName, "\": command doesn't exist", NULL); + oldName)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", oldName, NULL); return TCL_ERROR; } cmdNsPtr = cmdPtr->nsPtr; @@ -2420,21 +2570,24 @@ TclRenameCommand( TCL_CREATE_NS_IF_UNKNOWN, &newNsPtr, &dummy1, &dummy2, &newTail); if ((newNsPtr == NULL) || (newTail == NULL)) { - Tcl_AppendResult(interp, "can't rename to \"", newName, - "\": bad command name", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't rename to \"%s\": bad command name", newName)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", NULL); result = TCL_ERROR; goto done; } if (Tcl_FindHashEntry(&newNsPtr->cmdTable, newTail) != NULL) { - Tcl_AppendResult(interp, "can't rename to \"", newName, - "\": command already exists", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't rename to \"%s\": command already exists", newName)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "RENAME", + "TARGET_EXISTS", NULL); result = TCL_ERROR; goto done; } /* * Warning: any changes done in the code here are likely to be needed in - * Tcl_HideCommand() code too (until the common parts are extracted out). + * Tcl_HideCommand code too (until the common parts are extracted out). * - dl */ @@ -2475,6 +2628,17 @@ TclRenameCommand( TclInvalidateNsCmdLookup(cmdPtr->nsPtr); /* + * Command resolvers (per-interp, per-namespace) might have resolved to a + * command for the given namespace scope with this command not being + * registered with the namespace's command table. During BC compilation, + * the so-resolved command turns into a CmdName literal. Without + * invalidating a possible CmdName literal here explicitly, such literals + * keep being reused while pointing to overhauled commands. + */ + + TclInvalidateCmdLiteral(interp, newTail, cmdPtr->nsPtr); + + /* * Script for rename traces can delete the command "oldName". Therefore * increment the reference count for cmdPtr so that it's Command structure * is freed only towards the end of this function by calling @@ -2489,7 +2653,7 @@ TclRenameCommand( Tcl_DStringInit(&newFullName); Tcl_DStringAppend(&newFullName, newNsPtr->fullName, -1); if (newNsPtr != iPtr->globalNsPtr) { - Tcl_DStringAppend(&newFullName, "::", 2); + TclDStringAppendLiteral(&newFullName, "::"); } Tcl_DStringAppend(&newFullName, newTail, -1); cmdPtr->refCount++; @@ -2593,7 +2757,7 @@ Tcl_SetCommandInfoFromToken( { Command *cmdPtr; /* Internal representation of the command */ - if (cmd == (Tcl_Command) NULL) { + if (cmd == NULL) { return 0; } @@ -2677,7 +2841,7 @@ Tcl_GetCommandInfoFromToken( { Command *cmdPtr; /* Internal representation of the command */ - if (cmd == (Tcl_Command) NULL) { + if (cmd == NULL) { return 0; } @@ -2822,7 +2986,7 @@ Tcl_DeleteCommand( */ cmd = Tcl_FindCommand(interp, cmdName, NULL, /*flags*/ 0); - if (cmd == (Tcl_Command) NULL) { + if (cmd == NULL) { return -1; } return Tcl_DeleteCommandFromToken(interp, cmd); @@ -2917,8 +3081,9 @@ Tcl_DeleteCommandFromToken( tracePtr = cmdPtr->tracePtr; while (tracePtr != NULL) { CommandTrace *nextPtr = tracePtr->nextPtr; + if ((--tracePtr->refCount) <= 0) { - ckfree((char *) tracePtr); + ckfree(tracePtr); } tracePtr = nextPtr; } @@ -2952,19 +3117,17 @@ Tcl_DeleteCommandFromToken( * created when a command was imported into a namespace, this client * data will be a pointer to a ImportedCmdData structure describing * the "real" command that this imported command refers to. - */ - - /* + * * If you are getting a crash during the call to deleteProc and * cmdPtr->deleteProc is a pointer to the function free(), the most * likely cause is that your extension allocated memory for the - * clientData argument to Tcl_CreateObjCommand() with the ckalloc() + * clientData argument to Tcl_CreateObjCommand with the ckalloc() * macro and you are now trying to deallocate this memory with free() * instead of ckfree(). You should pass a pointer to your own method * that calls ckfree(). */ - (*cmdPtr->deleteProc)(cmdPtr->deleteData); + cmdPtr->deleteProc(cmdPtr->deleteData); } /* @@ -2972,12 +3135,13 @@ Tcl_DeleteCommandFromToken( * commands were created that refer back to this command. Delete these * imported commands now. */ - - for (refPtr = cmdPtr->importRefPtr; refPtr != NULL; - refPtr = nextRefPtr) { - nextRefPtr = refPtr->nextPtr; - importCmd = (Tcl_Command) refPtr->importedCmdPtr; - Tcl_DeleteCommandFromToken(interp, importCmd); + if (!(cmdPtr->flags & CMD_REDEF_IN_PROGRESS)) { + for (refPtr = cmdPtr->importRefPtr; refPtr != NULL; + refPtr = nextRefPtr) { + nextRefPtr = refPtr->nextPtr; + importCmd = (Tcl_Command) refPtr->importedCmdPtr; + Tcl_DeleteCommandFromToken(interp, importCmd); + } } /* @@ -2993,11 +3157,10 @@ Tcl_DeleteCommandFromToken( } /* - * Mark the Command structure as no longer valid. This allows - * TclExecuteByteCode to recognize when a Command has logically been - * deleted and a pointer to this Command structure cached in a CmdName - * object is invalid. TclExecuteByteCode will look up the command again in - * the interpreter's command hashtable. + * A number of tests for particular kinds of commands are done by checking + * whether the objProc field holds a known value. Set the field to NULL so + * that such tests won't have false positives when applied to deleted + * commands. */ cmdPtr->objProc = NULL; @@ -3007,14 +3170,31 @@ Tcl_DeleteCommandFromToken( * from a CmdName Tcl object in some ByteCode code sequence. In that case, * delay the cleanup until all references are either discarded (when a * ByteCode is freed) or replaced by a new reference (when a cached - * CmdName Command reference is found to be invalid and TclExecuteByteCode - * looks up the command in the command hashtable). + * CmdName Command reference is found to be invalid and + * TclNRExecuteByteCode looks up the command in the command hashtable). */ TclCleanupCommandMacro(cmdPtr); return 0; } +/* + *---------------------------------------------------------------------- + * + * CallCommandTraces -- + * + * Abstraction of the code to call traces on a command. + * + * Results: + * Currently always NULL. + * + * Side effects: + * Anything; this may recursively evaluate scripts and code exists to do + * just that. + * + *---------------------------------------------------------------------- + */ + static char * CallCommandTraces( Interp *iPtr, /* Interpreter containing command. */ @@ -3085,11 +3265,11 @@ CallCommandTraces( if (state == NULL) { state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, TCL_OK); } - (*tracePtr->traceProc)(tracePtr->clientData, - (Tcl_Interp *) iPtr, oldName, newName, flags); + tracePtr->traceProc(tracePtr->clientData, (Tcl_Interp *) iPtr, + oldName, newName, flags); cmdPtr->flags &= ~tracePtr->flags; if ((--tracePtr->refCount) <= 0) { - ckfree((char *) tracePtr); + ckfree(tracePtr); } } @@ -3116,6 +3296,26 @@ CallCommandTraces( Tcl_Release(iPtr); return result; } + +/* + *---------------------------------------------------------------------- + * + * CancelEvalProc -- + * + * Marks this interpreter as being canceled. This causes current + * executions to be unwound as the interpreter enters a state where it + * refuses to execute more commands or handle [catch] or [try], yet the + * interpreter is still able to execute further commands after the + * cancelation is cleared (unlike if it is deleted). + * + * Results: + * The value given for the code argument. + * + * Side effects: + * Transfers a message from the cancelation message to the interpreter. + * + *---------------------------------------------------------------------- + */ static int CancelEvalProc( @@ -3132,33 +3332,35 @@ CancelEvalProc( if (iPtr != NULL) { /* - * Setting this flag will cause the script in progress to be - * canceled as soon as possible. The core honors this flag at all - * the necessary places to ensure script cancellation is + * Setting the CANCELED flag will cause the script in progress to + * be canceled as soon as possible. The core honors this flag at + * all the necessary places to ensure script cancellation is * responsive. Extensions can check for this flag by calling * Tcl_Canceled and checking if TCL_ERROR is returned or they can * choose to ignore the script cancellation flag and the - * associated functionality altogether. + * associated functionality altogether. Currently, the only other + * flag we care about here is the TCL_CANCEL_UNWIND flag (from + * Tcl_CancelEval). We do not want to simply combine all the flags + * from original Tcl_CancelEval call with the interp flags here + * just in case the caller passed flags that might cause behaviour + * unrelated to script cancellation. */ - iPtr->flags |= CANCELED; + TclSetCancelFlags(iPtr, cancelInfo->flags | CANCELED); /* - * Currently, we only care about the TCL_CANCEL_UNWIND flag from - * Tcl_CancelEval. We do not want to simply combine all the flags - * from original Tcl_CancelEval call with the interp flags here - * just in case the caller passed flags that might cause behaviour - * unrelated to script cancellation. + * Now, we must set the script cancellation flags on all the slave + * interpreters belonging to this one. */ - if (cancelInfo->flags & TCL_CANCEL_UNWIND) { - iPtr->flags |= TCL_CANCEL_UNWIND; - } + TclSetSlaveCancelFlags((Tcl_Interp *) iPtr, + cancelInfo->flags | CANCELED, 0); /* * Create the result object now so that Tcl_Canceled can avoid * locking the cancelLock mutex. */ + if (cancelInfo->result != NULL) { Tcl_SetStringObj(iPtr->asyncCancelMsg, cancelInfo->result, cancelInfo->length); @@ -3175,66 +3377,6 @@ CancelEvalProc( /* *---------------------------------------------------------------------- * - * GetCommandSource -- - * - * This function returns a Tcl_Obj with the full source string for the - * command. This insures that traces get a correct NUL-terminated command - * string. The Tcl_Obj has refCount==1. - * - * *** MAINTAINER WARNING *** - * The returned Tcl_Obj is all wrong for any purpose but getting the - * source string for an objc/objv command line in the stringRep (no - * stringRep if no source is available) and the corresponding substituted - * version in the List intrep. - * This means that the intRep and stringRep DO NOT COINCIDE! Using these - * Tcl_Objs normally is likely to break things. - * - *---------------------------------------------------------------------- - */ - -static Tcl_Obj * -GetCommandSource( - Interp *iPtr, - int objc, - Tcl_Obj *const objv[], - int lookup) -{ - Tcl_Obj *objPtr, *obj2Ptr; - CmdFrame *cfPtr = iPtr->cmdFramePtr; - const char *command = NULL; - int numChars; - - objPtr = Tcl_NewListObj(objc, objv); - if (lookup && cfPtr && (cfPtr->numLevels == iPtr->numLevels-1)) { - switch (cfPtr->type) { - case TCL_LOCATION_EVAL: - case TCL_LOCATION_SOURCE: - command = cfPtr->cmd.str.cmd; - numChars = cfPtr->cmd.str.len; - break; - case TCL_LOCATION_BC: - case TCL_LOCATION_PREBC: - command = TclGetSrcInfoForCmd(iPtr, &numChars); - break; - case TCL_LOCATION_EVAL_LIST: - /* Got it already */ - break; - } - if (command) { - obj2Ptr = Tcl_NewStringObj(command, numChars); - objPtr->bytes = obj2Ptr->bytes; - objPtr->length = numChars; - obj2Ptr->bytes = NULL; - Tcl_DecrRefCount(obj2Ptr); - } - } - Tcl_IncrRefCount(objPtr); - return objPtr; -} - -/* - *---------------------------------------------------------------------- - * * TclCleanupCommand -- * * This function frees up a Command structure unless it is still @@ -3260,7 +3402,7 @@ TclCleanupCommand( { cmdPtr->refCount--; if (cmdPtr->refCount <= 0) { - ckfree((char *) cmdPtr); + ckfree(cmdPtr); } } @@ -3301,18 +3443,16 @@ Tcl_CreateMathFunc( * function. */ { Tcl_DString bigName; - OldMathFuncData *data = (OldMathFuncData *) - ckalloc(sizeof(OldMathFuncData)); + OldMathFuncData *data = ckalloc(sizeof(OldMathFuncData)); data->proc = proc; data->numArgs = numArgs; - data->argTypes = (Tcl_ValueType *) - ckalloc(numArgs * sizeof(Tcl_ValueType)); + data->argTypes = ckalloc(numArgs * sizeof(Tcl_ValueType)); memcpy(data->argTypes, argTypes, numArgs * sizeof(Tcl_ValueType)); data->clientData = clientData; Tcl_DStringInit(&bigName); - Tcl_DStringAppend(&bigName, "::tcl::mathfunc::", -1); + TclDStringAppendLiteral(&bigName, "::tcl::mathfunc::"); Tcl_DStringAppend(&bigName, name, -1); Tcl_CreateObjCommand(interp, Tcl_DStringValue(&bigName), @@ -3364,9 +3504,9 @@ OldMathFuncProc( * Convert arguments from Tcl_Obj's to Tcl_Value's. */ - args = (Tcl_Value *) ckalloc(dataPtr->numArgs * sizeof(Tcl_Value)); + args = ckalloc(dataPtr->numArgs * sizeof(Tcl_Value)); for (j = 1, k = 0; j < objc; ++j, ++k) { - /* TODO: Convert to TclGetNumberFromObj() ? */ + /* TODO: Convert to TclGetNumberFromObj? */ valuePtr = objv[j]; result = Tcl_GetDoubleFromObj(NULL, valuePtr, &d); #ifdef ACCEPT_NAN @@ -3380,11 +3520,11 @@ OldMathFuncProc( * We have a non-numeric argument. */ - Tcl_SetResult(interp, + Tcl_SetObjResult(interp, Tcl_NewStringObj( "argument to math function didn't have numeric value", - TCL_STATIC); + -1)); TclCheckBadOctal(interp, Tcl_GetString(valuePtr)); - ckfree((char *) args); + ckfree(args); return TCL_ERROR; } @@ -3398,12 +3538,12 @@ OldMathFuncProc( args[k].type = dataPtr->argTypes[k]; switch (args[k].type) { case TCL_EITHER: - if (Tcl_GetLongFromObj(NULL, valuePtr, &(args[k].intValue)) + if (Tcl_GetLongFromObj(NULL, valuePtr, &args[k].intValue) == TCL_OK) { args[k].type = TCL_INT; break; } - if (Tcl_GetWideIntFromObj(interp, valuePtr, &(args[k].wideValue)) + if (Tcl_GetWideIntFromObj(interp, valuePtr, &args[k].wideValue) == TCL_OK) { args[k].type = TCL_WIDE_INT; break; @@ -3415,21 +3555,21 @@ OldMathFuncProc( args[k].doubleValue = d; break; case TCL_INT: - if (ExprIntFunc(NULL, interp, 2, &(objv[j-1])) != TCL_OK) { - ckfree((char *) args); + if (ExprIntFunc(NULL, interp, 2, &objv[j-1]) != TCL_OK) { + ckfree(args); return TCL_ERROR; } valuePtr = Tcl_GetObjResult(interp); - Tcl_GetLongFromObj(NULL, valuePtr, &(args[k].intValue)); + Tcl_GetLongFromObj(NULL, valuePtr, &args[k].intValue); Tcl_ResetResult(interp); break; case TCL_WIDE_INT: - if (ExprWideFunc(NULL, interp, 2, &(objv[j-1])) != TCL_OK) { - ckfree((char *) args); + if (ExprWideFunc(NULL, interp, 2, &objv[j-1]) != TCL_OK) { + ckfree(args); return TCL_ERROR; } valuePtr = Tcl_GetObjResult(interp); - Tcl_GetWideIntFromObj(NULL, valuePtr, &(args[k].wideValue)); + Tcl_GetWideIntFromObj(NULL, valuePtr, &args[k].wideValue); Tcl_ResetResult(interp); break; } @@ -3440,8 +3580,8 @@ OldMathFuncProc( */ errno = 0; - result = (*dataPtr->proc)(dataPtr->clientData, interp, args, &funcResult); - ckfree((char *) args); + result = dataPtr->proc(dataPtr->clientData, interp, args, &funcResult); + ckfree(args); if (result != TCL_OK) { return result; } @@ -3480,12 +3620,12 @@ OldMathFuncProc( static void OldMathFuncDeleteProc( - ClientData clientData) + ClientData clientData) { OldMathFuncData *dataPtr = clientData; - ckfree((void *) dataPtr->argTypes); - ckfree((void *) dataPtr); + ckfree(dataPtr->argTypes); + ckfree(dataPtr); } /* @@ -3539,12 +3679,9 @@ Tcl_GetMathFuncInfo( */ if (cmdPtr == NULL) { - Tcl_Obj *message; - - TclNewLiteralStringObj(message, "unknown math function \""); - Tcl_AppendToObj(message, name, -1); - Tcl_AppendToObj(message, "\"", 1); - Tcl_SetObjResult(interp, message); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "unknown math function \"%s\"", name)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "MATHFUNC", name, NULL); *numArgsPtr = -1; *argTypesPtr = NULL; *procPtr = NULL; @@ -3598,41 +3735,28 @@ Tcl_ListMathFuncs( Tcl_Interp *interp, const char *pattern) { - Namespace *globalNsPtr = (Namespace *) Tcl_GetGlobalNamespace(interp); - Namespace *nsPtr; - Namespace *dummy1NsPtr; - Namespace *dummy2NsPtr; - const char *dummyNamePtr; - Tcl_Obj *result = Tcl_NewObj(); - - TclGetNamespaceForQualName(interp, "::tcl::mathfunc", - globalNsPtr, TCL_FIND_ONLY_NS | TCL_GLOBAL_ONLY, - &nsPtr, &dummy1NsPtr, &dummy2NsPtr, &dummyNamePtr); - if (nsPtr == NULL) { - return result; + Tcl_Obj *script = Tcl_NewStringObj("::info functions ", -1); + Tcl_Obj *result; + Tcl_InterpState state; + + if (pattern) { + Tcl_Obj *patternObj = Tcl_NewStringObj(pattern, -1); + Tcl_Obj *arg = Tcl_NewListObj(1, &patternObj); + + Tcl_AppendObjToObj(script, arg); + Tcl_DecrRefCount(arg); /* Should tear down patternObj too */ } - if ((pattern != NULL) && TclMatchIsTrivial(pattern)) { - if (Tcl_FindHashEntry(&nsPtr->cmdTable, pattern) != NULL) { - Tcl_ListObjAppendElement(NULL, result, - Tcl_NewStringObj(pattern, -1)); - } + state = Tcl_SaveInterpState(interp, TCL_OK); + Tcl_IncrRefCount(script); + if (TCL_OK == Tcl_EvalObjEx(interp, script, 0)) { + result = Tcl_DuplicateObj(Tcl_GetObjResult(interp)); } else { - Tcl_HashSearch cmdHashSearch; - Tcl_HashEntry *cmdHashEntry = - Tcl_FirstHashEntry(&nsPtr->cmdTable,&cmdHashSearch); - - for (; cmdHashEntry != NULL; - cmdHashEntry = Tcl_NextHashEntry(&cmdHashSearch)) { - const char *cmdNamePtr = - Tcl_GetHashKey(&nsPtr->cmdTable, cmdHashEntry); - - if (pattern == NULL || Tcl_StringMatch(cmdNamePtr, pattern)) { - Tcl_ListObjAppendElement(NULL, result, - Tcl_NewStringObj(cmdNamePtr, -1)); - } - } + result = Tcl_NewObj(); } + Tcl_DecrRefCount(script); + Tcl_RestoreInterpState(interp, state); + return result; } @@ -3672,15 +3796,22 @@ TclInterpReady( */ if (iPtr->flags & DELETED) { - /* JJM - Superfluous Tcl_ResetResult call removed. */ - Tcl_AppendResult(interp, - "attempt to call eval in deleted interpreter", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "attempt to call eval in deleted interpreter", -1)); Tcl_SetErrorCode(interp, "TCL", "IDELETE", "attempt to call eval in deleted interpreter", NULL); return TCL_ERROR; } - if (iPtr->execEnvPtr->rewind || + if (iPtr->execEnvPtr->rewind) { + return TCL_ERROR; + } + + /* + * Make sure the script being evaluated (if any) has not been canceled. + */ + + if (TclCanceled(iPtr) && (TCL_OK != Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG))) { return TCL_ERROR; } @@ -3694,8 +3825,9 @@ TclInterpReady( return TCL_OK; } - Tcl_AppendResult(interp, - "too many nested evaluations (infinite loop?)", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "too many nested evaluations (infinite loop?)", -1)); + Tcl_SetErrorCode(interp, "TCL", "LIMIT", "STACK", NULL); return TCL_ERROR; } @@ -3729,7 +3861,7 @@ TclResetCancellation( } if (force || (iPtr->numLevels == 0)) { - iPtr->flags &= (~(CANCELED | TCL_CANCEL_UNWIND)); + TclUnsetCancelFlags(iPtr); } return TCL_OK; } @@ -3767,105 +3899,78 @@ Tcl_Canceled( register Interp *iPtr = (Interp *) interp; /* - * Traverse up the to the top-level interp, checking for the CANCELED flag - * along the way. If any of the intervening interps have the CANCELED flag - * set, the current script in progress is considered to be canceled and we - * stop checking. Otherwise, if any interp has the DELETED flag set we - * stop checking. + * Has the current script in progress for this interpreter been canceled + * or is the stack being unwound due to the previous script cancellation? */ - for (; iPtr!=NULL; iPtr = (Interp *) Tcl_GetMaster((Tcl_Interp *) iPtr)) { - /* - * Has the current script in progress for this interpreter been - * canceled or is the stack being unwound due to the previous script - * cancellation? - */ - - if ((iPtr->flags & CANCELED) || (iPtr->flags & TCL_CANCEL_UNWIND)) { - /* - * The CANCELED flag is a one-shot flag that is reset immediately - * upon being detected; however, if the TCL_CANCEL_UNWIND flag is - * set we will continue to report that the script in progress has - * been canceled thereby allowing the evaluation stack for the - * interp to be fully unwound. - */ - - iPtr->flags &= ~CANCELED; + if (!TclCanceled(iPtr)) { + return TCL_OK; + } - /* - * The CANCELED flag was detected and reset; however, if the - * caller specified the TCL_CANCEL_UNWIND flag, we only return - * TCL_ERROR (indicating that the script in progress has been - * canceled) if the evaluation stack for the interp is being fully - * unwound. - */ + /* + * The CANCELED flag is a one-shot flag that is reset immediately upon + * being detected; however, if the TCL_CANCEL_UNWIND flag is set we will + * continue to report that the script in progress has been canceled + * thereby allowing the evaluation stack for the interp to be fully + * unwound. + */ - if (!(flags & TCL_CANCEL_UNWIND) - || (iPtr->flags & TCL_CANCEL_UNWIND)) { - /* - * If the TCL_LEAVE_ERR_MSG flags bit is set, place an error - * in the interp's result; otherwise, we leave it alone. - */ + iPtr->flags &= ~CANCELED; - if (flags & TCL_LEAVE_ERR_MSG) { - const char *id, *message = NULL; - int length; + /* + * The CANCELED flag was detected and reset; however, if the caller + * specified the TCL_CANCEL_UNWIND flag, we only return TCL_ERROR + * (indicating that the script in progress has been canceled) if the + * evaluation stack for the interp is being fully unwound. + */ - /* - * Setup errorCode variables so that we can differentiate - * between being canceled and unwound. - */ + if ((flags & TCL_CANCEL_UNWIND) && !(iPtr->flags & TCL_CANCEL_UNWIND)) { + return TCL_OK; + } - if (iPtr->asyncCancelMsg != NULL) { - message = Tcl_GetStringFromObj(iPtr->asyncCancelMsg, - &length); - } else { - length = 0; - } + /* + * If the TCL_LEAVE_ERR_MSG flags bit is set, place an error in the + * interp's result; otherwise, we leave it alone. + */ - if (iPtr->flags & TCL_CANCEL_UNWIND) { - id = "IUNWIND"; - if (length == 0) { - message = "eval unwound"; - } - } else { - id = "ICANCEL"; - if (length == 0) { - message = "eval canceled"; - } - } + if (flags & TCL_LEAVE_ERR_MSG) { + const char *id, *message = NULL; + int length; - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, message, NULL); - Tcl_SetErrorCode(interp, "TCL", id, message, NULL); - } + /* + * Setup errorCode variables so that we can differentiate between + * being canceled and unwound. + */ - /* - * Return TCL_ERROR to the caller (not necessarily just the - * Tcl core itself) that indicates further processing of the - * script or command in progress should halt gracefully and as - * soon as possible. - */ + if (iPtr->asyncCancelMsg != NULL) { + message = Tcl_GetStringFromObj(iPtr->asyncCancelMsg, &length); + } else { + length = 0; + } - return TCL_ERROR; - } - } else { - /* - * FIXME: If this interpreter is being deleted we cannot continue - * to traverse up the interp chain due to an issue with - * Tcl_GetMaster (really the slave interp bookkeeping) that - * causes us to run off into a freed interp struct. Ideally, this - * check would not be necessary because Tcl_GetMaster would - * return NULL instead of a pointer to invalid (freed) memory. - */ + if (iPtr->flags & TCL_CANCEL_UNWIND) { + id = "IUNWIND"; + if (length == 0) { + message = "eval unwound"; + } + } else { + id = "ICANCEL"; + if (length == 0) { + message = "eval canceled"; + } + } - if (iPtr->flags & DELETED) { - break; - } - } + Tcl_SetObjResult(interp, Tcl_NewStringObj(message, -1)); + Tcl_SetErrorCode(interp, "TCL", "CANCEL", id, message, NULL); } - return TCL_OK; + /* + * Return TCL_ERROR to the caller (not necessarily just the Tcl core + * itself) that indicates further processing of the script or command in + * progress should halt gracefully and as soon as possible. + */ + + return TCL_ERROR; } /* @@ -3958,6 +4063,30 @@ Tcl_CancelEval( /* *---------------------------------------------------------------------- * + * Tcl_InterpActive -- + * + * Returns non-zero if the specified interpreter is in use, i.e. if there + * is an evaluation currently active in the interpreter. + * + * Results: + * See above. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_InterpActive( + Tcl_Interp *interp) +{ + return ((Interp *) interp)->numLevels > 0; +} + +/* + *---------------------------------------------------------------------- + * * Tcl_EvalObjv -- * * This function evaluates a Tcl command that has already been parsed @@ -3986,10 +4115,10 @@ Tcl_EvalObjv( * TCL_EVAL_NOERR are currently supported. */ { int result; - TEOV_callback *rootPtr = TOP_CB(interp); + NRE_callback *rootPtr = TOP_CB(interp); result = TclNREvalObjv(interp, objc, objv, flags, NULL); - return TclNRRunCallbacks(interp, result, rootPtr, 0); + return TclNRRunCallbacks(interp, result, rootPtr); } int @@ -4008,39 +4137,39 @@ TclNREvalObjv( * requested Command struct to be invoked. */ { Interp *iPtr = (Interp *) interp; - int result; - Namespace *lookupNsPtr = iPtr->lookupNsPtr; - Tcl_ObjCmdProc *objProc; - ClientData objClientData; - Command **cmdPtrPtr; - - iPtr->lookupNsPtr = NULL; /* - * Push a callback with cleanup tasks for commands; the cmdPtr at data[0] - * will be filled later when the command is found: save its address at - * objProcPtr. - * - * data[1] stores a marker for use by tailcalls; it will be reset to 0 by + * data[1] stores a marker for use by tailcalls; it will be set to 1 by * command redirectors (imports, alias, ensembles) so that tailcalls * finishes the source command and not just the target. */ - TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); - cmdPtrPtr = (Command **) &(TOP_CB(interp)->data[0]); - - TclResetCancellation(interp, 0); - iPtr->numLevels++; - result = TclInterpReady(interp); - - if ((result != TCL_OK) || (objc == 0)) { - return result; + if (iPtr->deferredCallbacks) { + iPtr->deferredCallbacks = NULL; + } else { + TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); } - if (cmdPtr) { - goto commandFound; - } + iPtr->numLevels++; + TclNRAddCallback(interp, EvalObjvCore, cmdPtr, INT2PTR(flags), + INT2PTR(objc), objv); + return TCL_OK; +} +static int +EvalObjvCore( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + Command *cmdPtr = NULL, *preCmdPtr = data[0]; + int flags = PTR2INT(data[1]); + int objc = PTR2INT(data[2]); + Tcl_Obj **objv = data[3]; + Interp *iPtr = (Interp *) interp; + Namespace *lookupNsPtr = NULL; + int enterTracesDone = 0; + /* * Push records for task to be done on return, in INVERSE order. First, if * needed, the exception handlers (as they should happen last). @@ -4050,65 +4179,154 @@ TclNREvalObjv( TEOV_PushExceptionHandlers(interp, objc, objv, flags); } + if (TCL_OK != TclInterpReady(interp)) { + return TCL_ERROR; + } + + if (objc == 0) { + return TCL_OK; + } + + if (TclLimitExceeded(iPtr->limit)) { + return TCL_ERROR; + } + /* * Configure evaluation context to match the requested flags. */ - if ((flags & TCL_EVAL_INVOKE) || lookupNsPtr) { - if (!lookupNsPtr) { - lookupNsPtr = iPtr->globalNsPtr; - } + if (iPtr->lookupNsPtr) { + + /* + * Capture the namespace we should do command name resolution in, as + * instructed by our caller sneaking it in to us in a private interp + * field. Clear that field right away so we cannot possibly have its + * use leak where it should not. The sneaky message pass is done. + * + * Use of this mechanism overrides the TCL_EVAL_GLOBAL flag. + * TODO: Is that a bug? + */ + + lookupNsPtr = iPtr->lookupNsPtr; + iPtr->lookupNsPtr = NULL; + } else if (flags & TCL_EVAL_INVOKE) { + lookupNsPtr = iPtr->globalNsPtr; } else { - if (flags & TCL_EVAL_GLOBAL) { - TEOV_SwitchVarFrame(interp); - lookupNsPtr = iPtr->globalNsPtr; - } /* * TCL_EVAL_INVOKE was not set: clear rewrite rules */ iPtr->ensembleRewrite.sourceObjs = NULL; + + if (flags & TCL_EVAL_GLOBAL) { + TEOV_SwitchVarFrame(interp); + lookupNsPtr = iPtr->globalNsPtr; + } } /* - * Lookup the command + * Lookup the Command to dispatch. */ - cmdPtr = TEOV_LookupCmdFromObj(interp, objv[0], lookupNsPtr); - if (!cmdPtr) { - notFound: - result = TEOV_NotFound(interp, objc, objv, lookupNsPtr); - return result; + reresolve: + assert(cmdPtr == NULL); + if (preCmdPtr) { + /* Caller gave it to us */ + if (!(preCmdPtr->flags & CMD_IS_DELETED)) { + /* So long as it exists, use it. */ + cmdPtr = preCmdPtr; + } else if (flags & TCL_EVAL_NORESOLVE) { + /* + * When it's been deleted, and we're told not to attempt + * resolving it ourselves, all we can do is raise an error. + */ + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "attempt to invoke a deleted command")); + Tcl_SetErrorCode(interp, "TCL", "EVAL", "DELETEDCOMMAND", NULL); + return TCL_ERROR; + } } - - iPtr->cmdCount++; - if (TclLimitExceeded(iPtr->limit)) { - return TCL_ERROR; + if (cmdPtr == NULL) { + cmdPtr = TEOV_LookupCmdFromObj(interp, objv[0], lookupNsPtr); + if (!cmdPtr) { + return TEOV_NotFound(interp, objc, objv, lookupNsPtr); + } } - /* - * Found a command! The real work begins now ... - */ + if (enterTracesDone || iPtr->tracePtr + || (cmdPtr->flags & CMD_HAS_EXEC_TRACES)) { - commandFound: - if (iPtr->tracePtr || (cmdPtr->flags & CMD_HAS_EXEC_TRACES)) { - /* - * Call enter traces. They will schedule a call to the leave traces if - * necessary. - */ + Tcl_Obj *commandPtr = TclGetSourceFromFrame( + flags & TCL_EVAL_SOURCE_IN_FRAME ? iPtr->cmdFramePtr : NULL, + objc, objv); + Tcl_IncrRefCount(commandPtr); - result = TEOV_RunEnterTraces(interp, &cmdPtr, objc, objv, lookupNsPtr); - if (!cmdPtr) { - goto notFound; - } - if (result != TCL_OK) { - return result; + if (!enterTracesDone) { + + int code = TEOV_RunEnterTraces(interp, &cmdPtr, commandPtr, + objc, objv); + + /* + * Send any exception from enter traces back as an exception + * raised by the traced command. + * TODO: Is this a bug? Letting an execution trace BREAK or + * CONTINUE or RETURN in the place of the traced command? + * Would either converting all exceptions to TCL_ERROR, or + * just swallowing them be better? (Swallowing them has the + * problem of permanently hiding program errors.) + */ + + if (code != TCL_OK) { + Tcl_DecrRefCount(commandPtr); + return code; + } + + /* + * If the enter traces made the resolved cmdPtr unusable, go + * back and resolve again, but next time don't run enter + * traces again. + */ + + if (cmdPtr == NULL) { + enterTracesDone = 1; + Tcl_DecrRefCount(commandPtr); + goto reresolve; + } } + + /* + * Schedule leave traces. Raise the refCount on the resolved + * cmdPtr, so that when it passes to the leave traces we know + * it's still valid. + */ + + cmdPtr->refCount++; + TclNRAddCallback(interp, TEOV_RunLeaveTraces, INT2PTR(objc), + commandPtr, cmdPtr, objv); } + TclNRAddCallback(interp, Dispatch, + cmdPtr->nreProc ? cmdPtr->nreProc : cmdPtr->objProc, + cmdPtr->objClientData, INT2PTR(objc), objv); + return TCL_OK; +} + +static int +Dispatch( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + Tcl_ObjCmdProc *objProc = data[0]; + ClientData clientData = data[1]; + int objc = PTR2INT(data[2]); + Tcl_Obj **objv = data[3]; + Interp *iPtr = (Interp *) interp; + +#ifdef USE_DTRACE if (TCL_DTRACE_CMD_ARGS_ENABLED()) { - char *a[10]; + const char *a[10]; int i = 0; while (i < 10) { @@ -4119,57 +4337,36 @@ TclNREvalObjv( } if (TCL_DTRACE_CMD_INFO_ENABLED() && iPtr->cmdFramePtr) { Tcl_Obj *info = TclInfoFrame(interp, iPtr->cmdFramePtr); - char *a[6]; int i[2]; + const char *a[6]; int i[2]; TclDTraceInfo(info, a, i); TCL_DTRACE_CMD_INFO(a[0], a[1], a[2], a[3], i[0], i[1], a[4], a[5]); TclDecrRefCount(info); } - if (TCL_DTRACE_CMD_RETURN_ENABLED() || TCL_DTRACE_CMD_RESULT_ENABLED()) { + if ((TCL_DTRACE_CMD_RETURN_ENABLED() || TCL_DTRACE_CMD_RESULT_ENABLED()) + && objc) { TclNRAddCallback(interp, DTraceCmdReturn, objv[0], NULL, NULL, NULL); } - if (TCL_DTRACE_CMD_ENTRY_ENABLED()) { + if (TCL_DTRACE_CMD_ENTRY_ENABLED() && objc) { TCL_DTRACE_CMD_ENTRY(TclGetString(objv[0]), objc - 1, (Tcl_Obj **)(objv + 1)); } +#endif /* USE_DTRACE */ - /* - * Fix the original callback to point to the now known cmdPtr. Insure that - * the Command struct lives until the command returns. - */ - - *cmdPtrPtr = cmdPtr; - cmdPtr->refCount++; - - /* - * Find the objProc to call: nreProc if available, objProc otherwise. Push - * a callback to do the actual running. - */ - - objProc = cmdPtr->nreProc; - if (!objProc) { - objProc = cmdPtr->objProc; - } - objClientData = cmdPtr->objClientData; - - TclNRAddCallback(interp, NRRunObjProc, objProc, objClientData, - INT2PTR(objc), (ClientData) objv); - return TCL_OK; + iPtr->cmdCount++; + return objProc(clientData, interp, objc, objv); } int TclNRRunCallbacks( Tcl_Interp *interp, int result, - struct TEOV_callback *rootPtr, + struct NRE_callback *rootPtr) /* All callbacks down to rootPtr not inclusive * are to be run. */ - int tebcCall) /* Normal callers set this to 0; TEBC sets it - * to 1 when executing a bytecode, to 2 when - * cleaning up after a bytecode returns. */ { Interp *iPtr = (Interp *) interp; - TEOV_callback *callbackPtr; + NRE_callback *callbackPtr; Tcl_NRPostProc *procPtr; /* @@ -4186,40 +4383,13 @@ TclNRRunCallbacks( (void) Tcl_GetObjResult(interp); } - restart: while (TOP_CB(interp) != rootPtr) { callbackPtr = TOP_CB(interp); - procPtr = callbackPtr->procPtr; - - if (tebcCall && (procPtr == NRCallTEBC)) { - NRE_ASSERT(result==TCL_OK); - return TCL_OK; - } - - /* - * IMPLEMENTATION REMARKS (FIXME) - * - * Add here other direct handling possibilities for optimisation? One - * could handle the very frequent NRCommand and NRRunObjProc right - * here to save an indirect function call and improve icache - * management. Would it? Test it, time it ... - */ - TOP_CB(interp) = callbackPtr->nextPtr; - result = (procPtr)(callbackPtr->data, interp, result); + result = procPtr(callbackPtr->data, interp, result); TCLNR_FREE(interp, callbackPtr); } - if (iPtr->atExitPtr) { - callbackPtr = iPtr->atExitPtr; - while (callbackPtr->nextPtr) { - callbackPtr = callbackPtr->nextPtr; - } - callbackPtr->nextPtr = rootPtr; - TOP_CB(iPtr) = iPtr->atExitPtr; - iPtr->atExitPtr = NULL; - goto restart; - } return result; } @@ -4230,13 +4400,16 @@ NRCommand( int result) { Interp *iPtr = (Interp *) interp; - Command *cmdPtr = data[0]; - /* int cmdStart = PTR2INT(data[1]); NOT USED HERE */ - if (cmdPtr) { - TclCleanupCommandMacro(cmdPtr); + iPtr->numLevels--; + + /* + * If there is a tailcall, schedule it + */ + + if (data[1] && (data[1] != INT2PTR(1))) { + TclNRAddCallback(interp, TclNRTailcallEval, data[1], NULL, NULL, NULL); } - ((Interp *)interp)->numLevels--; /* OPT ?? * Do not interrupt a series of cleanups with async or limit checks: @@ -4246,74 +4419,15 @@ NRCommand( if (TclAsyncReady(iPtr)) { result = Tcl_AsyncInvoke(interp, result); } - if (result == TCL_OK) { + if ((result == TCL_OK) && TclCanceled(iPtr)) { result = Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG); } if (result == TCL_OK && TclLimitReady(iPtr->limit)) { result = Tcl_LimitCheck(interp); } - return result; -} - -static int -NRRunObjProc( - ClientData data[], - Tcl_Interp *interp, - int result) -{ - /* OPT: do not call? */ - - Tcl_ObjCmdProc *objProc = data[0]; - ClientData objClientData = data[1]; - int objc = PTR2INT(data[2]); - Tcl_Obj **objv = data[3]; - if (result == TCL_OK) { - return (*objProc)(objClientData, interp, objc, objv); - } return result; } - -int -NRCallTEBC( - ClientData data[], - Tcl_Interp *interp, - int result) -{ - /* - * This is not run normally, the callback is passed up to tebc. This - function is only called when no tebc is above. - */ - int type = PTR2INT(data[0]); - Interp *iPtr = ((Interp *) interp); - - NRE_ASSERT(result == TCL_OK); - - switch (type) { - case TCL_NR_BC_TYPE: - return TclExecuteByteCode(interp, data[1]); - case TCL_NR_ATEXIT_TYPE: - case TCL_NR_TAILCALL_TYPE: - /* For atProcExit and tailcalls */ - Tcl_SetResult(interp, - "atProcExit/tailcall can only be called from a proc or lambda", - TCL_STATIC); - return TCL_ERROR; - case TCL_NR_YIELD_TYPE: - if (iPtr->execEnvPtr->corPtr) { - Tcl_SetResult(interp, "cannot yield: C stack busy", TCL_STATIC); - Tcl_SetErrorCode(interp, "COROUTINE_CANT_YIELD", NULL); - } else { - Tcl_SetResult(interp, "yield can only be called in a coroutine", - TCL_STATIC); - Tcl_SetErrorCode(interp, "COROUTINE_ILLEGAL_YIELD", NULL); - } - return TCL_ERROR; - default: - Tcl_Panic("unknown call type to TEBC"); - } - return result; /* not reached */ -} /* *---------------------------------------------------------------------- @@ -4407,6 +4521,14 @@ TEOV_Exception( result = TCL_ERROR; } } + + /* + * We are returning to level 0, so should process TclResetCancellation. As + * numLevels has not *yet* been decreased, do not call it: do the thing + * here directly. + */ + + TclUnsetCancelFlags(iPtr); return result; } @@ -4418,7 +4540,7 @@ TEOV_Error( { Interp *iPtr = (Interp *) interp; Tcl_Obj *listPtr; - char *cmdString; + const char *cmdString; int cmdLen; int objc = PTR2INT(data[0]); Tcl_Obj **objv = data[1]; @@ -4427,7 +4549,7 @@ TEOV_Error( /* * If there was an error, a command string will be needed for the * error log: get it out of the itemPtr. The details depend on the - * type + * type. */ listPtr = Tcl_NewListObj(objc, objv); @@ -4451,7 +4573,6 @@ TEOV_NotFound( int i, newObjc, handlerObjc; Tcl_Obj **newObjv, **handlerObjv; CallFrame *varFramePtr = iPtr->varFramePtr; - int result = TCL_OK; Namespace *currNsPtr = NULL;/* Used to check for and invoke any registered * unknown command handler for the current * namespace (TIP 181). */ @@ -4510,28 +4631,59 @@ TEOV_NotFound( cmdPtr = TEOV_LookupCmdFromObj(interp, newObjv[0], lookupNsPtr); if (cmdPtr == NULL) { - Tcl_AppendResult(interp, "invalid command name \"", - TclGetString(objv[0]), "\"", NULL); - result = TCL_ERROR; - } else { - if (lookupNsPtr) { - savedNsPtr = varFramePtr->nsPtr; - varFramePtr->nsPtr = lookupNsPtr; - } - result = Tcl_EvalObjv(interp, newObjc, newObjv, TCL_EVAL_NOERR); - if (savedNsPtr) { - varFramePtr->nsPtr = savedNsPtr; + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "invalid command name \"%s\"", TclGetString(objv[0]))); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COMMAND", + TclGetString(objv[0]), NULL); + + /* + * Release any resources we locked and allocated during the handler + * call. + */ + + for (i = 0; i < handlerObjc; ++i) { + Tcl_DecrRefCount(newObjv[i]); } + TclStackFree(interp, newObjv); + return TCL_ERROR; + } + + if (lookupNsPtr) { + savedNsPtr = varFramePtr->nsPtr; + varFramePtr->nsPtr = lookupNsPtr; + } + TclSkipTailcall(interp); + TclNRAddCallback(interp, TEOV_NotFoundCallback, INT2PTR(handlerObjc), + newObjv, savedNsPtr, NULL); + return TclNREvalObjv(interp, newObjc, newObjv, TCL_EVAL_NOERR, NULL); +} + +static int +TEOV_NotFoundCallback( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + Interp *iPtr = (Interp *) interp; + int objc = PTR2INT(data[0]); + Tcl_Obj **objv = data[1]; + Namespace *savedNsPtr = data[2]; + + int i; + + if (savedNsPtr) { + iPtr->varFramePtr->nsPtr = savedNsPtr; } /* * Release any resources we locked and allocated during the handler call. */ - for (i = 0; i < handlerObjc; ++i) { - Tcl_DecrRefCount(newObjv[i]); + for (i = 0; i < objc; ++i) { + Tcl_DecrRefCount(objv[i]); } - TclStackFree(interp, newObjv); + TclStackFree(interp, objv); + return result; } @@ -4539,27 +4691,21 @@ static int TEOV_RunEnterTraces( Tcl_Interp *interp, Command **cmdPtrPtr, + Tcl_Obj *commandPtr, int objc, - Tcl_Obj *const objv[], - Namespace *lookupNsPtr) + Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; Command *cmdPtr = *cmdPtrPtr; - int traceCode = TCL_OK; - int cmdEpoch = cmdPtr->cmdEpoch; - int newEpoch; - char *command; - int length; - Tcl_Obj *commandPtr; - - commandPtr = GetCommandSource(iPtr, objc, objv, 1); - command = Tcl_GetStringFromObj(commandPtr, &length); + int newEpoch, cmdEpoch = cmdPtr->cmdEpoch; + int length, traceCode = TCL_OK; + const char *command = Tcl_GetStringFromObj(commandPtr, &length); /* * Call trace functions. * Execute any command or execution traces. Note that we bump up the - * command's reference count for the duration of the calling of the traces - * so that the structure doesn't go away underneath our feet. + * command's reference count for the duration of the calling of the + * traces so that the structure doesn't go away underneath our feet. */ cmdPtr->refCount++; @@ -4574,29 +4720,22 @@ TEOV_RunEnterTraces( newEpoch = cmdPtr->cmdEpoch; TclCleanupCommandMacro(cmdPtr); - /* - * If the traces modified/deleted the command or any existing traces, they - * will update the command's epoch. We need to lookup again, but do not - * run enter traces on the newly found cmdPtr. - */ - - if (cmdEpoch != newEpoch) { - cmdPtr = TEOV_LookupCmdFromObj(interp, objv[0], lookupNsPtr); - *cmdPtrPtr = cmdPtr; + if (traceCode != TCL_OK) { + if (traceCode == TCL_ERROR) { + Tcl_Obj *info; + + TclNewLiteralStringObj(info, "\n (enter trace on \""); + Tcl_AppendLimitedToObj(info, command, length, 55, "..."); + Tcl_AppendToObj(info, "\")", 2); + Tcl_AppendObjToErrorInfo(interp, info); + iPtr->flags |= ERR_ALREADY_LOGGED; + } + return traceCode; } - - if (cmdPtr) { - /* - * Command was found: push a record to schedule the leave traces. - */ - - TclNRAddCallback(interp, TEOV_RunLeaveTraces, INT2PTR(traceCode), - commandPtr, cmdPtr, NULL); - cmdPtr->refCount++; - } else { - Tcl_DecrRefCount(commandPtr); + if (cmdEpoch != newEpoch) { + *cmdPtrPtr = NULL; } - return traceCode; + return TCL_OK; } static int @@ -4606,20 +4745,16 @@ TEOV_RunLeaveTraces( int result) { Interp *iPtr = (Interp *) interp; - char *command; - int length, objc; - Tcl_Obj **objv; - int traceCode = PTR2INT(data[0]); + int traceCode = TCL_OK; + int objc = PTR2INT(data[0]); Tcl_Obj *commandPtr = data[1]; Command *cmdPtr = data[2]; - - command = Tcl_GetStringFromObj(commandPtr, &length); - if (TCL_OK != Tcl_ListObjGetElements(interp, commandPtr, &objc, &objv)) { - Tcl_Panic("Who messed with commandPtr?"); - } + Tcl_Obj **objv = data[3]; + int length; + const char *command = Tcl_GetStringFromObj(commandPtr, &length); if (!(cmdPtr->flags & CMD_IS_DELETED)) { - if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && traceCode == TCL_OK){ + if (cmdPtr->flags & CMD_HAS_EXEC_TRACES){ traceCode = TclCheckExecutionTraces(interp, command, length, cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv); } @@ -4628,7 +4763,6 @@ TEOV_RunLeaveTraces( cmdPtr, result, TCL_TRACE_LEAVE_EXEC, objc, objv); } } - Tcl_DecrRefCount(commandPtr); /* * As cmdPtr is set, TclNRRunCallbacks is about to reduce the numlevels. @@ -4639,8 +4773,18 @@ TEOV_RunLeaveTraces( TclCleanupCommandMacro(cmdPtr); if (traceCode != TCL_OK) { - return traceCode; + if (traceCode == TCL_ERROR) { + Tcl_Obj *info; + + TclNewLiteralStringObj(info, "\n (leave trace on \""); + Tcl_AppendLimitedToObj(info, command, length, 55, "..."); + Tcl_AppendToObj(info, "\")", 2); + Tcl_AppendObjToErrorInfo(interp, info); + iPtr->flags |= ERR_ALREADY_LOGGED; + } + result = traceCode; } + Tcl_DecrRefCount(commandPtr); return result; } @@ -4656,7 +4800,6 @@ TEOV_LookupCmdFromObj( if (lookupNsPtr) { iPtr->varFramePtr->nsPtr = lookupNsPtr; - iPtr->lookupNsPtr = NULL; } cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, namePtr); iPtr->varFramePtr->nsPtr = savedNsPtr; @@ -4693,7 +4836,8 @@ Tcl_EvalTokensStandard( int count) /* Number of tokens to consider at tokenPtr. * Must be at least 1. */ { - return TclSubstTokens(interp, tokenPtr, count, /* numLeftPtr */ NULL, 1); + return TclSubstTokens(interp, tokenPtr, count, /* numLeftPtr */ NULL, 1, + NULL, NULL); } /* @@ -4777,7 +4921,7 @@ Tcl_EvalEx( * evaluation of the script. Only * TCL_EVAL_GLOBAL is currently supported. */ { - return TclEvalEx(interp, script, numBytes, flags, 1); + return TclEvalEx(interp, script, numBytes, flags, 1, NULL, script); } int @@ -4791,7 +4935,24 @@ TclEvalEx( int flags, /* Collection of OR-ed bits that control the * evaluation of the script. Only * TCL_EVAL_GLOBAL is currently supported. */ - int line) /* The line the script starts on. */ + int line, /* The line the script starts on. */ + int *clNextOuter, /* Information about an outer context for */ + const char *outerScript) /* continuation line data. This is set only in + * TclSubstTokens(), to properly handle + * [...]-nested commands. The 'outerScript' + * refers to the most-outer script containing + * the embedded command, which is refered to + * by 'script'. The 'clNextOuter' refers to + * the current entry in the table of + * continuation lines in this "master script", + * and the character offsets are relative to + * the 'outerScript' as well. + * + * If outerScript == script, then this call is + * for the outer-most script/command. See + * Tcl_EvalEx() and TclEvalObjEx() for places + * generating arguments for which this is + * true. */ { Interp *iPtr = (Interp *) interp; const char *p, *next; @@ -4817,6 +4978,21 @@ TclEvalEx( int *linesStack = TclStackAlloc(interp, minObjs * sizeof(int)); /* TIP #280 Structures for tracking of command * locations. */ + int *clNext = NULL; /* Pointer for the tracking of invisible + * continuation lines. Initialized only if the + * caller gave us a table of locations to + * track, via scriptCLLocPtr. It always refers + * to the table entry holding the location of + * the next invisible continuation line to + * look for, while parsing the script. */ + + if (iPtr->scriptCLLocPtr) { + if (clNextOuter) { + clNext = clNextOuter; + } else { + clNext = &iPtr->scriptCLLocPtr->loc[0]; + } + } if (numBytes < 0) { numBytes = strlen(script); @@ -4842,31 +5018,22 @@ TclEvalEx( /* * TIP #280 Initialize tracking. Do not push on the frame stack yet. * - * We may cont. counting based on a specific context (CTX), or open a new - * context, either for a sourced script, or 'eval'. For sourced files we - * always have a path object, even if nothing was specified in the interp - * itself. That makes code using it simpler as NULL checks can be left - * out. Sourced file without path in the 'scriptFile' is possible during - * Tcl initialization. + * We open a new context, either for a sourced script, or 'eval'. + * For sourced files we always have a path object, even if nothing was + * specified in the interp itself. That makes code using it simpler as + * NULL checks can be left out. Sourced file without path in the + * 'scriptFile' is possible during Tcl initialization. */ eeFramePtr->level = iPtr->cmdFramePtr ? iPtr->cmdFramePtr->level + 1 : 1; - eeFramePtr->numLevels = iPtr->numLevels; eeFramePtr->framePtr = iPtr->framePtr; eeFramePtr->nextPtr = iPtr->cmdFramePtr; eeFramePtr->nline = 0; eeFramePtr->line = NULL; + eeFramePtr->cmdObj = NULL; iPtr->cmdFramePtr = eeFramePtr; - if (iPtr->evalFlags & TCL_EVAL_CTX) { - /* - * Path information comes out of the context. - */ - - eeFramePtr->type = TCL_LOCATION_SOURCE; - eeFramePtr->data.eval.path = iPtr->invokeCmdFramePtr->data.eval.path; - Tcl_IncrRefCount(eeFramePtr->data.eval.path); - } else if (iPtr->evalFlags & TCL_EVAL_FILE) { + if (iPtr->evalFlags & TCL_EVAL_FILE) { /* * Set up for a sourced file. */ @@ -4909,38 +5076,44 @@ TclEvalEx( do { if (Tcl_ParseCommand(interp, p, bytesLeft, 0, parsePtr) != TCL_OK) { code = TCL_ERROR; - goto error; + Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, + parsePtr->term + 1 - parsePtr->commandStart); + goto posterror; } /* * TIP #280 Track lines. The parser may have skipped text till it * found the command we are now at. We have to count the lines in this - * block. + * block, and do not forget invisible continuation lines. */ TclAdvanceLines(&line, p, parsePtr->commandStart); + TclAdvanceContinuations(&line, &clNext, + parsePtr->commandStart - outerScript); gotParse = 1; if (parsePtr->numWords > 0) { /* - * TIP #280. Track lines within the words of the current command. + * TIP #280. Track lines within the words of the current + * command. We use a separate pointer into the table of + * continuation line locations to not lose our position for the + * per-command parsing. */ int wordLine = line; const char *wordStart = parsePtr->commandStart; + int *wordCLNext = clNext; + unsigned int objectsNeeded = 0; + unsigned int numWords = parsePtr->numWords; /* * Generate an array of objects for the words of the command. */ - unsigned int objectsNeeded = 0; - unsigned int numWords = parsePtr->numWords; - if (numWords > minObjs) { - expand = (int *) ckalloc(numWords * sizeof(int)); - objvSpace = (Tcl_Obj **) - ckalloc(numWords * sizeof(Tcl_Obj *)); - lineSpace = (int *) ckalloc(numWords * sizeof(int)); + expand = ckalloc(numWords * sizeof(int)); + objvSpace = ckalloc(numWords * sizeof(Tcl_Obj *)); + lineSpace = ckalloc(numWords * sizeof(int)); } expandRequested = 0; objv = objvSpace; @@ -4959,6 +5132,8 @@ TclEvalEx( */ TclAdvanceLines(&wordLine, wordStart, tokenPtr->start); + TclAdvanceContinuations(&wordLine, &wordCLNext, + tokenPtr->start - outerScript); wordStart = tokenPtr->start; lines[objectsUsed] = TclWordKnownAtCompileTime(tokenPtr, NULL) @@ -4969,7 +5144,8 @@ TclEvalEx( } code = TclSubstTokens(interp, tokenPtr+1, - tokenPtr->numComponents, NULL, wordLine); + tokenPtr->numComponents, NULL, wordLine, + wordCLNext, outerScript); iPtr->evalFlags = 0; @@ -5001,6 +5177,11 @@ TclEvalEx( expand[objectsUsed] = 0; objectsNeeded++; } + + if (wordCLNext) { + TclContinuationsEnterDerived(objv[objectsUsed], + wordStart - outerScript, wordCLNext); + } } /* for loop */ iPtr->cmdFramePtr = eeFramePtr; if (code != TCL_OK) { @@ -5017,10 +5198,9 @@ TclEvalEx( int objIdx = objectsNeeded - 1; if ((numWords > minObjs) || (objectsNeeded > minObjs)) { - objv = objvSpace = (Tcl_Obj **) + objv = objvSpace = ckalloc(objectsNeeded * sizeof(Tcl_Obj *)); - lines = lineSpace = (int *) - ckalloc(objectsNeeded * sizeof(int)); + lines = lineSpace = ckalloc(objectsNeeded * sizeof(int)); } objectsUsed = 0; @@ -5047,10 +5227,10 @@ TclEvalEx( objv += objIdx+1; if (copy != stackObjArray) { - ckfree((char *) copy); + ckfree(copy); } if (lcopy != linesStack) { - ckfree((char *) lcopy); + ckfree(lcopy); } } @@ -5064,23 +5244,28 @@ TclEvalEx( * have been executed. */ - eeFramePtr->cmd.str.cmd = parsePtr->commandStart; - eeFramePtr->cmd.str.len = parsePtr->commandSize; + eeFramePtr->cmd = parsePtr->commandStart; + eeFramePtr->len = parsePtr->commandSize; if (parsePtr->term == parsePtr->commandStart + parsePtr->commandSize - 1) { - eeFramePtr->cmd.str.len--; + eeFramePtr->len--; } eeFramePtr->nline = objectsUsed; eeFramePtr->line = lines; TclArgumentEnter(interp, objv, objectsUsed, eeFramePtr); - code = Tcl_EvalObjv(interp, objectsUsed, objv, TCL_EVAL_NOERR); + code = Tcl_EvalObjv(interp, objectsUsed, objv, + TCL_EVAL_NOERR | TCL_EVAL_SOURCE_IN_FRAME); TclArgumentRelease(interp, objv, objectsUsed); eeFramePtr->line = NULL; eeFramePtr->nline = 0; + if (eeFramePtr->cmdObj) { + Tcl_DecrRefCount(eeFramePtr->cmdObj); + eeFramePtr->cmdObj = NULL; + } if (code != TCL_OK) { goto error; @@ -5090,9 +5275,9 @@ TclEvalEx( } objectsUsed = 0; if (objvSpace != stackObjArray) { - ckfree((char *) objvSpace); + ckfree(objvSpace); objvSpace = stackObjArray; - ckfree((char *) lineSpace); + ckfree(lineSpace); lineSpace = linesStack; } @@ -5102,7 +5287,7 @@ TclEvalEx( */ if (expand != expandStack) { - ckfree((char *) expand); + ckfree(expand); expand = expandStack; } } @@ -5154,6 +5339,7 @@ TclEvalEx( Tcl_LogCommandInfo(interp, script, parsePtr->commandStart, commandLength); } + posterror: iPtr->flags &= ~ERR_ALREADY_LOGGED; /* @@ -5167,11 +5353,11 @@ TclEvalEx( Tcl_FreeParse(parsePtr); } if (objvSpace != stackObjArray) { - ckfree((char *) objvSpace); - ckfree((char *) lineSpace); + ckfree(objvSpace); + ckfree(lineSpace); } if (expand != expandStack) { - ckfree((char *) expand); + ckfree(expand); } iPtr->varFramePtr = savedVarFramePtr; @@ -5228,6 +5414,55 @@ TclAdvanceLines( /* *---------------------------------------------------------------------- + * + * TclAdvanceContinuations -- + * + * This procedure is a helper which counts the number of continuation + * lines (CL) in a block of text using a table of CL locations and + * advances an external counter, and the pointer into the table. + * + * Results: + * None. + * + * Side effects: + * The specified counter is advanced per the number of continuation lines + * found. + * + * TIP #280 + *---------------------------------------------------------------------- + */ + +void +TclAdvanceContinuations( + int *line, + int **clNextPtrPtr, + int loc) +{ + /* + * Track the invisible continuation lines embedded in a script, if any. + * Here they are just spaces (already). They were removed by + * TclSubstTokens via TclParseBackslash. + * + * *clNextPtrPtr <=> We have continuation lines to track. + * **clNextPtrPtr >= 0 <=> We are not beyond the last possible location. + * loc >= **clNextPtrPtr <=> We stepped beyond the current cont. line. + */ + + while (*clNextPtrPtr && (**clNextPtrPtr >= 0) + && (loc >= **clNextPtrPtr)) { + /* + * We just stepped over an invisible continuation line. Adjust the + * line counter and step to the table entry holding the location of + * the next continuation line to track. + */ + + (*line)++; + (*clNextPtrPtr)++; + } +} + +/* + *---------------------------------------------------------------------- * Note: The whole data structure access for argument location tracking is * hidden behind these three functions. The only parts open are the lineLAPtr * field in the Interp structure. The CFWord definition is internal to here. @@ -5279,14 +5514,14 @@ TclArgumentEnter( if (cfPtr->line[i] < 0) { continue; } - hPtr = Tcl_CreateHashEntry(iPtr->lineLAPtr, (char *) objv[i], &new); + hPtr = Tcl_CreateHashEntry(iPtr->lineLAPtr, objv[i], &new); if (new) { /* * The word is not on the stack yet, remember the current location * and initialize references. */ - cfwPtr = (CFWord *) ckalloc(sizeof(CFWord)); + cfwPtr = ckalloc(sizeof(CFWord)); cfwPtr->framePtr = cfPtr; cfwPtr->word = i; cfwPtr->refCount = 1; @@ -5308,10 +5543,10 @@ TclArgumentEnter( * * TclArgumentRelease -- * - * This procedure is a helper for the TIP #280 uplevel extension. - * It removes the location references for the arguments of a command - * just done. Usage is counted down, the data is removed only when - * no user is left over. + * This procedure is a helper for the TIP #280 uplevel extension. It + * removes the location references for the arguments of a command just + * done. Usage is counted down, the data is removed only when no user is + * left over. * * Results: * None. @@ -5334,20 +5569,20 @@ TclArgumentRelease( for (i = 1; i < objc; i++) { CFWord *cfwPtr; - Tcl_HashEntry *hPtr = Tcl_FindHashEntry(iPtr->lineLAPtr, - (char *) objv[i]); + Tcl_HashEntry *hPtr = + Tcl_FindHashEntry(iPtr->lineLAPtr, (char *) objv[i]); if (!hPtr) { continue; } - cfwPtr = (CFWord *) Tcl_GetHashValue(hPtr); + cfwPtr = Tcl_GetHashValue(hPtr); cfwPtr->refCount--; if (cfwPtr->refCount > 0) { continue; } - ckfree((char *) cfwPtr); + ckfree(cfwPtr); Tcl_DeleteHashEntry(hPtr); } } @@ -5374,49 +5609,93 @@ TclArgumentRelease( void TclArgumentBCEnter( - Tcl_Interp *interp, - void *codePtr, - CmdFrame *cfPtr) -{ + Tcl_Interp *interp, + Tcl_Obj *objv[], + int objc, + void *codePtr, + CmdFrame *cfPtr, + int cmd, + int pc) +{ + ExtCmdLoc *eclPtr; + int word; + ECL *ePtr; + CFWordBC *lastPtr = NULL; Interp *iPtr = (Interp *) interp; - Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, - (char *) codePtr); - - if (hePtr) { - ExtCmdLoc *eclPtr = Tcl_GetHashValue(hePtr); - int i; - - for (i = 0; i < eclPtr->nueiloc; i++) { - ExtIndex *eiPtr = &eclPtr->eiloc[i]; - Tcl_Obj *obj = eiPtr->obj; - int new; - Tcl_HashEntry *hPtr; - CFWordBC *cfwPtr; - - hPtr = Tcl_CreateHashEntry(iPtr->lineLABCPtr, (char *) obj, &new); - if (new) { + Tcl_HashEntry *hePtr = + Tcl_FindHashEntry(iPtr->lineBCPtr, (char *) codePtr); + + if (!hePtr) { + return; + } + eclPtr = Tcl_GetHashValue(hePtr); + ePtr = &eclPtr->loc[cmd]; + + /* + * ePtr->nline is the number of words originally parsed. + * + * objc is the number of elements getting invoked. + * + * If they are not the same, we arrived here by compiling an + * ensemble dispatch. Ensemble subcommands that lead to script + * evaluation are not supposed to get compiled, because a command + * such as [info level] in the script can expose some of the dispatch + * shenanigans. This means that we don't have to tend to the + * housekeeping, and can escape now. + */ + + if (ePtr->nline != objc) { + return; + } + + /* + * Having disposed of the ensemble cases, we can state... + * A few truths ... + * (1) ePtr->nline == objc + * (2) (ePtr->line[word] < 0) => !literal, for all words + * (3) (word == 0) => !literal + * + * Item (2) is why we can use objv to get the literals, and do not + * have to save them at compile time. + */ + + for (word = 1; word < objc; word++) { + if (ePtr->line[word] >= 0) { + int isnew; + Tcl_HashEntry *hPtr = Tcl_CreateHashEntry(iPtr->lineLABCPtr, + objv[word], &isnew); + CFWordBC *cfwPtr = ckalloc(sizeof(CFWordBC)); + + cfwPtr->framePtr = cfPtr; + cfwPtr->obj = objv[word]; + cfwPtr->pc = pc; + cfwPtr->word = word; + cfwPtr->nextPtr = lastPtr; + lastPtr = cfwPtr; + + if (isnew) { /* * The word is not on the stack yet, remember the current * location and initialize references. */ - cfwPtr = (CFWordBC *) ckalloc(sizeof(CFWordBC)); - cfwPtr->framePtr = cfPtr; - cfwPtr->eiPtr = eiPtr; - cfwPtr->refCount = 1; - Tcl_SetHashValue(hPtr, cfwPtr); + cfwPtr->prevPtr = NULL; } else { /* - * The word is already on the stack, its current location is - * not relevant. Just remember the reference to prevent early - * removal. + * The object is already on the stack, however it may have + * a different location now (literal sharing may map + * multiple location to a single Tcl_Obj*. Save the old + * information in the new structure. */ - cfwPtr = Tcl_GetHashValue(hPtr); - cfwPtr->refCount++; + cfwPtr->prevPtr = Tcl_GetHashValue(hPtr); } + + Tcl_SetHashValue(hPtr, cfwPtr); } - } + } /* for */ + + cfPtr->litarg = lastPtr; } /* @@ -5441,38 +5720,33 @@ TclArgumentBCEnter( void TclArgumentBCRelease( - Tcl_Interp *interp, - void *codePtr) + Tcl_Interp *interp, + CmdFrame *cfPtr) { Interp *iPtr = (Interp *) interp; - Tcl_HashEntry *hePtr = Tcl_FindHashEntry(iPtr->lineBCPtr, - (char *) codePtr); + CFWordBC *cfwPtr = (CFWordBC *) cfPtr->litarg; - if (hePtr) { - ExtCmdLoc *eclPtr = Tcl_GetHashValue(hePtr); - int i; + while (cfwPtr) { + CFWordBC *nextPtr = cfwPtr->nextPtr; + Tcl_HashEntry *hPtr = + Tcl_FindHashEntry(iPtr->lineLABCPtr, (char *) cfwPtr->obj); + CFWordBC *xPtr = Tcl_GetHashValue(hPtr); - for (i = 0; i < eclPtr->nueiloc; i++) { - Tcl_Obj *obj = eclPtr->eiloc[i].obj; - Tcl_HashEntry *hPtr = Tcl_FindHashEntry(iPtr->lineLABCPtr, - (char *) obj); - CFWordBC *cfwPtr; - - if (!hPtr) { - continue; - } - - cfwPtr = Tcl_GetHashValue(hPtr); - - cfwPtr->refCount--; - if (cfwPtr->refCount > 0) { - continue; - } + if (xPtr != cfwPtr) { + Tcl_Panic("TclArgumentBC Enter/Release Mismatch"); + } - ckfree((char *) cfwPtr); + if (cfwPtr->prevPtr) { + Tcl_SetHashValue(hPtr, cfwPtr->prevPtr); + } else { Tcl_DeleteHashEntry(hPtr); } + + ckfree(cfwPtr); + cfwPtr = nextPtr; } + + cfPtr->litarg = NULL; } /* @@ -5480,8 +5754,8 @@ TclArgumentBCRelease( * * TclArgumentGet -- * - * This procedure is a helper for the TIP #280 uplevel extension. - * It find the location references for a Tcl_Obj, if any. + * This procedure is a helper for the TIP #280 uplevel extension. It + * finds the location references for a Tcl_Obj, if any. * * Results: * None. @@ -5511,8 +5785,7 @@ TclArgumentGet( * up by the caller. It knows better than us. */ - if ((!obj->bytes) || ((obj->typePtr == &tclListType) && - ((List *)obj->internalRep.twoPtrValue.ptr1)->canonicalFlag)) { + if ((obj->bytes == NULL) || TclListObjIsCanonical(obj)) { return; } @@ -5538,13 +5811,12 @@ TclArgumentGet( hPtr = Tcl_FindHashEntry(iPtr->lineLABCPtr, (char *) obj); if (hPtr) { CFWordBC *cfwPtr = Tcl_GetHashValue(hPtr); - ExtIndex *eiPtr = cfwPtr->eiPtr; framePtr = cfwPtr->framePtr; framePtr->data.tebc.pc = (char *) (((ByteCode *) - framePtr->data.tebc.codePtr)->codeStart + eiPtr->pc); + framePtr->data.tebc.codePtr)->codeStart + cfwPtr->pc); *cfPtrPtr = cfwPtr->framePtr; - *wordPtr = eiPtr->word; + *wordPtr = cfwPtr->word; return; } } @@ -5571,6 +5843,7 @@ TclArgumentGet( *---------------------------------------------------------------------- */ +#undef Tcl_Eval int Tcl_Eval( Tcl_Interp *interp, /* Token for command interpreter (returned by @@ -5614,7 +5887,6 @@ Tcl_EvalObj( { return Tcl_EvalObjEx(interp, objPtr, 0); } - #undef Tcl_GlobalEvalObj int Tcl_GlobalEvalObj( @@ -5633,6 +5905,11 @@ Tcl_GlobalEvalObj( * compiled into bytecodes if necessary, unless TCL_EVAL_DIRECT is * specified. * + * If the flag TCL_EVAL_DIRECT is passed in, the value of invoker + * must be NULL. Support for non-NULL invokers in that mode has + * been removed since it was unused and untested. Failure to + * follow this limitation will lead to an assertion panic. + * * Results: * The return value is one of the return codes defined in tcl.h (such as * TCL_OK), and the interpreter's result contains a value to supplement @@ -5673,10 +5950,10 @@ TclEvalObjEx( int word) /* Index of the word which is in objPtr. */ { int result = TCL_OK; - TEOV_callback *rootPtr = TOP_CB(interp); + NRE_callback *rootPtr = TOP_CB(interp); result = TclNREvalObjEx(interp, objPtr, flags, invoker, word); - return TclNRRunCallbacks(interp, result, rootPtr, 0); + return TclNRRunCallbacks(interp, result, rootPtr); } int @@ -5693,24 +5970,20 @@ TclNREvalObjEx( { Interp *iPtr = (Interp *) interp; int result; - List *listRepPtr = objPtr->internalRep.twoPtrValue.ptr1; /* * This function consists of three independent blocks for: direct - * evaluation of canonical lists, compileation and bytecode execution and + * evaluation of canonical lists, compilation and bytecode execution and * finally direct evaluation. Precisely one of these blocks will be run. */ - if ((objPtr->typePtr == &tclListType) && /* is a list... */ - ((objPtr->bytes == NULL || /* ...without a string rep */ - listRepPtr->canonicalFlag))) { /* ...or that is canonical */ - Tcl_Obj *listPtr = objPtr; + if (TclListObjIsCanonical(objPtr)) { CmdFrame *eoFramePtr = NULL; int objc; - Tcl_Obj **objv; + Tcl_Obj *listPtr, **objv; /* - * Pure List Optimization (no string representation). In this case, we + * Canonical List Optimization: In this case, we * can safely use Tcl_EvalObjv instead and get an appreciable * improvement in execution speed. This is because it allows us to * avoid a setFromAny step that would just pack everything into a @@ -5718,13 +5991,22 @@ TclNREvalObjEx( * * This also preserves any associations between list elements and * location information for such elements. + */ + + /* + * Shimmer protection! Always pass an unshared obj. The caller could + * incr the refCount of objPtr AFTER calling us! To be completely safe + * we always make a copy. The callback takes care od the refCounts for + * both listPtr and objPtr. * - * This restriction has been relaxed a bit by storing in lists whether - * they are "canonical" or not (a canonical list being one that is - * either pure or that has its string rep derived by - * UpdateStringOfList from the internal rep). + * TODO: Create a test to demo this need, or eliminate it. + * FIXME OPT: preserve just the internal rep? */ + Tcl_IncrRefCount(objPtr); + listPtr = TclListObjCopy(interp, objPtr); + Tcl_IncrRefCount(listPtr); + if (word != INT_MIN) { /* * TIP #280 Structures for tracking lines. As we know that this is @@ -5746,33 +6028,25 @@ TclNREvalObjEx( eoFramePtr->nline = 0; eoFramePtr->line = NULL; - eoFramePtr->type = TCL_LOCATION_EVAL_LIST; + eoFramePtr->type = TCL_LOCATION_EVAL; eoFramePtr->level = (iPtr->cmdFramePtr == NULL? 1 : iPtr->cmdFramePtr->level + 1); - eoFramePtr->numLevels = iPtr->numLevels; eoFramePtr->framePtr = iPtr->framePtr; eoFramePtr->nextPtr = iPtr->cmdFramePtr; - eoFramePtr->cmd.listPtr = objPtr; + eoFramePtr->cmdObj = objPtr; + eoFramePtr->cmd = NULL; + eoFramePtr->len = 0; eoFramePtr->data.eval.path = NULL; iPtr->cmdFramePtr = eoFramePtr; - } - /* - * Shimmer protection! Always pass an unshared obj. The caller could - * incr the refCount of objPtr AFTER calling us! To be completely safe - * we always make a copy. The callback takes care od the refCounts for - * both listPtr and objPtr. - * - * FIXME OPT: preserve just the internal rep? - */ + flags |= TCL_EVAL_SOURCE_IN_FRAME; + } - Tcl_IncrRefCount(objPtr); - listPtr = TclListObjCopy(interp, objPtr); - Tcl_IncrRefCount(listPtr); - TclNRAddCallback(interp, TEOEx_ListCallback, objPtr, eoFramePtr, - listPtr, NULL); + TclMarkTailcall(interp); + TclNRAddCallback(interp, TEOEx_ListCallback, listPtr, eoFramePtr, + objPtr, NULL); ListObjGetElements(listPtr, objc, objv); return TclNREvalObjv(interp, objc, objv, flags, NULL); @@ -5792,6 +6066,9 @@ TclNREvalObjEx( * iPtr->varFramePtr in case * TCL_EVAL_GLOBAL was set. */ + if (TclInterpReady(interp) != TCL_OK) { + return TCL_ERROR; + } if (flags & TCL_EVAL_GLOBAL) { savedVarFramePtr = iPtr->varFramePtr; iPtr->varFramePtr = iPtr->rootFramePtr; @@ -5800,10 +6077,8 @@ TclNREvalObjEx( codePtr = TclCompileObj(interp, objPtr, invoker, word); TclNRAddCallback(interp, TEOEx_ByteCodeCallback, savedVarFramePtr, - objPtr, INT2PTR(allowExceptions), NULL); - TclNRAddCallback(interp, NRCallTEBC, INT2PTR(TCL_NR_BC_TYPE), codePtr, - NULL, NULL); - return TCL_OK; + objPtr, INT2PTR(allowExceptions), NULL); + return TclNRExecuteByteCode(interp, codePtr); } { @@ -5811,87 +6086,42 @@ TclNREvalObjEx( * We're not supposed to use the compiler or byte-code * interpreter. Let Tcl_EvalEx evaluate the command directly (and * probably more slowly). - * - * TIP #280. Propagate context as much as we can. Especially if the - * script to evaluate is a single literal it makes sense to look if - * our context is one with absolute line numbers we can then track - * into the literal itself too. - * - * See also tclCompile.c, TclInitCompileEnv, for the equivalent code - * in the bytecode compiler. */ - char *script; + const char *script; int numSrcBytes; - Tcl_IncrRefCount(objPtr); - if (invoker == NULL) { - /* - * No context, force opening of our own. - */ - - script = Tcl_GetStringFromObj(objPtr, &numSrcBytes); - result = Tcl_EvalEx(interp, script, numSrcBytes, flags); - } else { - /* - * We have an invoker, describing the command asking for the - * evaluation of a subordinate script. This script may originate - * in a literal word, or from a variable, etc. Using the line - * array we now check if we have good line information for the - * relevant word. The type of context is relevant as well. In a - * non-'source' context we don't have to try tracking lines. - * - * First see if the word exists and is a literal. If not we go - * through the easy dynamic branch. No need to perform more - * complex invokations. - */ - - int pc = 0; - CmdFrame *ctxPtr = TclStackAlloc(interp, sizeof(CmdFrame)); - - *ctxPtr = *invoker; - if (invoker->type == TCL_LOCATION_BC) { - /* - * Note: Type BC => ctxPtr->data.eval.path is not used. - * ctxPtr->data.tebc.codePtr is used instead. - */ - - TclGetSrcInfoForPc(ctxPtr); - pc = 1; - } - - script = Tcl_GetStringFromObj(objPtr, &numSrcBytes); + /* + * Now we check if we have data about invisible continuation lines for + * the script, and make it available to the direct script parser and + * evaluator we are about to call, if so. + * + * It may be possible that the script Tcl_Obj* can be free'd while the + * evaluator is using it, leading to the release of the associated + * ContLineLoc structure as well. To ensure that the latter doesn't + * happen we set a lock on it. We release this lock later in this + * function, after the evaluator is done. The relevant "lineCLPtr" + * hashtable is managed in the file "tclObj.c". + * + * Another important action is to save (and later restore) the + * continuation line information of the caller, in case we are + * executing nested commands in the eval/direct path. + */ - if ((invoker->nline <= word) || - (invoker->line[word] < 0) || - (ctxPtr->type != TCL_LOCATION_SOURCE)) { - /* - * Dynamic script, or dynamic context, force our own context. - */ + ContLineLoc *saveCLLocPtr = iPtr->scriptCLLocPtr; - result = Tcl_EvalEx(interp, script, numSrcBytes, flags); - } else { - /* - * Absolute context to reuse. - */ + assert(invoker == NULL); - iPtr->invokeCmdFramePtr = ctxPtr; - iPtr->evalFlags |= TCL_EVAL_CTX; + iPtr->scriptCLLocPtr = TclContinuationsGet(objPtr); - result = TclEvalEx(interp, script, numSrcBytes, flags, - ctxPtr->line[word]); + Tcl_IncrRefCount(objPtr); - if (pc) { - /* - * Death of SrcInfo reference. - */ + script = Tcl_GetStringFromObj(objPtr, &numSrcBytes); + result = Tcl_EvalEx(interp, script, numSrcBytes, flags); - Tcl_DecrRefCount(ctxPtr->data.eval.path); - } - } - TclStackFree(interp, ctxPtr); - } TclDecrRefCount(objPtr); + + iPtr->scriptCLLocPtr = saveCLLocPtr; return result; } } @@ -5912,7 +6142,7 @@ TEOEx_ByteCodeCallback( result = TclUpdateReturnInfo(iPtr); } if ((result != TCL_OK) && (result != TCL_ERROR) && !allowExceptions) { - char *script; + const char *script; int numSrcBytes; ProcessUnexpectedResult(interp, result); @@ -5920,6 +6150,13 @@ TEOEx_ByteCodeCallback( script = Tcl_GetStringFromObj(objPtr, &numSrcBytes); Tcl_LogCommandInfo(interp, script, script, numSrcBytes); } + + /* + * We are returning to level 0, so should call TclResetCancellation. + * Let us just unset the flags inline. + */ + + TclUnsetCancelFlags(iPtr); } iPtr->evalFlags = 0; @@ -5942,9 +6179,9 @@ TEOEx_ListCallback( int result) { Interp *iPtr = (Interp *) interp; - Tcl_Obj *objPtr = data[0]; + Tcl_Obj *listPtr = data[0]; CmdFrame *eoFramePtr = data[1]; - Tcl_Obj *listPtr = data[2]; + Tcl_Obj *objPtr = data[2]; /* * Remove the cmdFrame @@ -5986,17 +6223,21 @@ ProcessUnexpectedResult( * result code was returned. */ int returnCode) /* The unexpected result code. */ { + char buf[TCL_INTEGER_SPACE]; + Tcl_ResetResult(interp); if (returnCode == TCL_BREAK) { - Tcl_AppendResult(interp, - "invoked \"break\" outside of a loop", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "invoked \"break\" outside of a loop", -1)); } else if (returnCode == TCL_CONTINUE) { - Tcl_AppendResult(interp, - "invoked \"continue\" outside of a loop", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "invoked \"continue\" outside of a loop", -1)); } else { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "command returned bad code: %d", returnCode)); } + sprintf(buf, "%d", returnCode); + Tcl_SetErrorCode(interp, "TCL", "UNEXPECTED_RESULT_CODE", buf, NULL); } /* @@ -6313,29 +6554,32 @@ TclObjInvoke( * TCL_INVOKE_HIDDEN, TCL_INVOKE_NO_UNKNOWN, * or TCL_INVOKE_NO_TRACEBACK. */ { - register Interp *iPtr = (Interp *) interp; - Tcl_HashTable *hTblPtr; /* Table of hidden commands. */ - char *cmdName; /* Name of the command from objv[0]. */ - Tcl_HashEntry *hPtr = NULL; - Command *cmdPtr; - int result; - if (interp == NULL) { return TCL_ERROR; } - if ((objc < 1) || (objv == NULL)) { - Tcl_AppendResult(interp, "illegal argument vector", NULL); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "illegal argument vector", -1)); return TCL_ERROR; } - if ((flags & TCL_INVOKE_HIDDEN) == 0) { Tcl_Panic("TclObjInvoke: called without TCL_INVOKE_HIDDEN"); } + return Tcl_NRCallObjProc(interp, TclNRInvoke, NULL, objc, objv); +} - if (TclInterpReady(interp) == TCL_ERROR) { - return TCL_ERROR; - } +int +TclNRInvoke( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + register Interp *iPtr = (Interp *) interp; + Tcl_HashTable *hTblPtr; /* Table of hidden commands. */ + const char *cmdName; /* Name of the command from objv[0]. */ + Tcl_HashEntry *hPtr = NULL; + Command *cmdPtr; cmdName = TclGetString(objv[0]); hTblPtr = iPtr->hiddenCmdTablePtr; @@ -6343,37 +6587,35 @@ TclObjInvoke( hPtr = Tcl_FindHashEntry(hTblPtr, cmdName); } if (hPtr == NULL) { - Tcl_AppendResult(interp, "invalid hidden command name \"", - cmdName, "\"", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "invalid hidden command name \"%s\"", cmdName)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "HIDDENTOKEN", cmdName, + NULL); return TCL_ERROR; } cmdPtr = Tcl_GetHashValue(hPtr); - /* - * Invoke the command function. - */ - - iPtr->cmdCount++; - result = cmdPtr->objProc(cmdPtr->objClientData, interp, objc, objv); + /* Avoid the exception-handling brain damage when numLevels == 0 . */ + iPtr->numLevels++; + Tcl_NRAddCallback(interp, NRPostInvoke, NULL, NULL, NULL, NULL); /* - * If an error occurred, record information about what was being executed - * when the error occurred. + * Normal command resolution of objv[0] isn't going to find cmdPtr. + * That's the whole point of **hidden** commands. So tell the + * Eval core machinery not to even try (and risk finding something wrong). */ - if ((result == TCL_ERROR) - && ((flags & TCL_INVOKE_NO_TRACEBACK) == 0) - && ((iPtr->flags & ERR_ALREADY_LOGGED) == 0)) { - int length; - Tcl_Obj *command = Tcl_NewListObj(objc, objv); - const char *cmdString; + return TclNREvalObjv(interp, objc, objv, TCL_EVAL_NORESOLVE, cmdPtr); +} - Tcl_IncrRefCount(command); - cmdString = Tcl_GetStringFromObj(command, &length); - Tcl_LogCommandInfo(interp, cmdString, cmdString, length); - Tcl_DecrRefCount(command); - iPtr->flags &= ~ERR_ALREADY_LOGGED; - } +static int +NRPostInvoke( + ClientData clientData[], + Tcl_Interp *interp, + int result) +{ + Interp *iPtr = (Interp *)interp; + iPtr->numLevels--; return result; } @@ -6410,7 +6652,7 @@ Tcl_ExprString( * An empty string. Just set the interpreter's result to 0. */ - Tcl_SetResult(interp, "0", TCL_VOLATILE); + Tcl_SetObjResult(interp, Tcl_NewIntObj(0)); } else { Tcl_Obj *resultPtr, *exprObj = Tcl_NewStringObj(expr, -1); @@ -6421,13 +6663,13 @@ Tcl_ExprString( Tcl_SetObjResult(interp, resultPtr); Tcl_DecrRefCount(resultPtr); } + } - /* - * Force the string rep of the interp result. - */ + /* + * Force the string rep of the interp result. + */ - (void) Tcl_GetStringResult(interp); - } + (void) Tcl_GetStringResult(interp); return code; } @@ -6450,6 +6692,7 @@ Tcl_ExprString( *---------------------------------------------------------------------- */ +#undef Tcl_AddObjErrorInfo void Tcl_AppendObjToErrorInfo( Tcl_Interp *interp, /* Interpreter to which error information @@ -6483,6 +6726,7 @@ Tcl_AppendObjToErrorInfo( *---------------------------------------------------------------------- */ +#undef Tcl_AddErrorInfo void Tcl_AddErrorInfo( Tcl_Interp *interp, /* Interpreter to which error information @@ -6540,7 +6784,7 @@ Tcl_AddObjErrorInfo( * interp->result completely. */ - iPtr->errorInfo = Tcl_NewStringObj(interp->result, -1); + iPtr->errorInfo = Tcl_NewStringObj(iPtr->result, -1); } else { iPtr->errorInfo = iPtr->objResultPtr; } @@ -6663,9 +6907,11 @@ Tcl_VarEval( *---------------------------------------------------------------------- */ +#undef Tcl_GlobalEval int Tcl_GlobalEval( - Tcl_Interp *interp, /* Interpreter in which to evaluate command. */ + Tcl_Interp *interp, /* Interpreter in which to evaluate + * command. */ const char *command) /* Command to evaluate. */ { register Interp *iPtr = (Interp *) interp; @@ -6962,7 +7208,10 @@ ExprIsqrtFunc( return TCL_OK; negarg: - Tcl_SetResult(interp, "square root of negative argument", TCL_STATIC); + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "square root of negative argument", -1)); + Tcl_SetErrorCode(interp, "ARITH", "DOMAIN", + "domain error: argument not in valid range", NULL); return TCL_ERROR; } @@ -7037,7 +7286,7 @@ ExprUnaryFunc( return TCL_ERROR; } errno = 0; - return CheckDoubleResult(interp, (*func)(d)); + return CheckDoubleResult(interp, func(d)); } static int @@ -7108,7 +7357,7 @@ ExprBinaryFunc( return TCL_ERROR; } errno = 0; - return CheckDoubleResult(interp, (*func)(d1, d2)); + return CheckDoubleResult(interp, func(d1, d2)); } static int @@ -7134,52 +7383,73 @@ ExprAbsFunc( if (type == TCL_NUMBER_LONG) { long l = *((const long *) ptr); - if (l <= (long)0) { - if (l == LONG_MIN) { - TclBNInitBignumFromLong(&big, l); - goto tooLarge; + + if (l > (long)0) { + goto unChanged; + } else if (l == (long)0) { + const char *string = objv[1]->bytes; + if (string) { + while (*string != '0') { + if (*string == '-') { + Tcl_SetObjResult(interp, Tcl_NewLongObj(0)); + return TCL_OK; + } + string++; + } } - Tcl_SetObjResult(interp, Tcl_NewLongObj(-l)); - } else { - Tcl_SetObjResult(interp, objv[1]); + goto unChanged; + } else if (l == LONG_MIN) { + TclBNInitBignumFromLong(&big, l); + goto tooLarge; } + Tcl_SetObjResult(interp, Tcl_NewLongObj(-l)); return TCL_OK; } if (type == TCL_NUMBER_DOUBLE) { double d = *((const double *) ptr); - if (d <= 0.0) { - Tcl_SetObjResult(interp, Tcl_NewDoubleObj(-d)); - } else { - Tcl_SetObjResult(interp, objv[1]); + static const double poszero = 0.0; + + /* + * We need to distinguish here between positive 0.0 and negative -0.0. + * [Bug 2954959] + */ + + if (d == -0.0) { + if (!memcmp(&d, &poszero, sizeof(double))) { + goto unChanged; + } + } else if (d > -0.0) { + goto unChanged; } + Tcl_SetObjResult(interp, Tcl_NewDoubleObj(-d)); return TCL_OK; } -#ifndef NO_WIDE_TYPE +#ifndef TCL_WIDE_INT_IS_LONG if (type == TCL_NUMBER_WIDE) { Tcl_WideInt w = *((const Tcl_WideInt *) ptr); - if (w < (Tcl_WideInt)0) { - if (w == LLONG_MIN) { - TclBNInitBignumFromWideInt(&big, w); - goto tooLarge; - } - Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-w)); - } else { - Tcl_SetObjResult(interp, objv[1]); + + if (w >= (Tcl_WideInt)0) { + goto unChanged; + } + if (w == LLONG_MIN) { + TclBNInitBignumFromWideInt(&big, w); + goto tooLarge; } + Tcl_SetObjResult(interp, Tcl_NewWideIntObj(-w)); return TCL_OK; } #endif if (type == TCL_NUMBER_BIG) { - /* TODO: const correctness ? */ - if (mp_cmp_d((mp_int *) ptr, 0) == MP_LT) { + if (mp_cmp_d((const mp_int *) ptr, 0) == MP_LT) { Tcl_GetBignumFromObj(NULL, objv[1], &big); tooLarge: mp_neg(&big, &big); Tcl_SetObjResult(interp, Tcl_NewBignumObj(&big)); } else { + unChanged: Tcl_SetObjResult(interp, objv[1]); } return TCL_OK; @@ -7191,6 +7461,7 @@ ExprAbsFunc( return TCL_OK; #else double d; + Tcl_GetDoubleFromObj(interp, objv[1], &d); return TCL_ERROR; #endif @@ -7228,6 +7499,7 @@ ExprDoubleFunc( Tcl_Obj *const *objv) /* Actual parameter vector. */ { double dResult; + if (objc != 2) { MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; @@ -7343,6 +7615,7 @@ ExprWideFunc( { Tcl_WideInt wResult; Tcl_Obj *objPtr; + if (ExprEntierFunc(NULL, interp, objc, objv) != TCL_OK) { return TCL_ERROR; } @@ -7392,7 +7665,7 @@ ExprRandFunc( * to insure different seeds in different threads (bug #416643) */ - iPtr->randSeed = TclpGetClicks() + ((long)Tcl_GetCurrentThread()<<12); + iPtr->randSeed = TclpGetClicks() + (PTR2INT(Tcl_GetCurrentThread())<<12); /* * Make sure 1 <= randSeed <= (2^31) - 2. See below. @@ -7471,7 +7744,7 @@ ExprRoundFunc( int type; if (objc != 2) { - MathFuncWrongNumArgs(interp, 1, objc, objv); + MathFuncWrongNumArgs(interp, 2, objc, objv); return TCL_ERROR; } @@ -7571,7 +7844,7 @@ ExprSrandFunc( /* * Reset the seed. Make sure 1 <= randSeed <= 2^31 - 2. See comments in - * ExprRandFunc() for more details. + * ExprRandFunc for more details. */ iPtr->flags |= RAND_SEED_INITIALIZED; @@ -7618,7 +7891,7 @@ MathFuncWrongNumArgs( const char *tail = name + strlen(name); while (tail > name+1) { - --tail; + tail--; if (*tail == ':' && tail[-1] == ':') { name = tail+1; break; @@ -7627,6 +7900,7 @@ MathFuncWrongNumArgs( Tcl_SetObjResult(interp, Tcl_ObjPrintf( "too %s arguments for math function \"%s\"", (found < expected ? "few" : "many"), name)); + Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL); } #ifdef USE_DTRACE @@ -7685,7 +7959,7 @@ DTraceObjCmd( void TclDTraceInfo( Tcl_Obj *info, - char **args, + const char **args, int *argsi) { static Tcl_Obj *keys[10] = { NULL }; @@ -7718,7 +7992,7 @@ TclDTraceInfo( for (i = 0; i < 2; i++) { Tcl_DictObjGet(NULL, info, *k++, &val); if (val) { - TclGetIntFromObj(NULL, val, &(argsi[i])); + TclGetIntFromObj(NULL, val, &argsi[i]); } else { argsi[i] = 0; } @@ -7760,7 +8034,7 @@ DTraceCmdReturn( return result; } -TCL_DTRACE_DEBUG_LOG(); +TCL_DTRACE_DEBUG_LOG() #endif /* USE_DTRACE */ @@ -7792,37 +8066,11 @@ Tcl_NRCallObjProc( int objc, Tcl_Obj *const objv[]) { - int result = TCL_OK; - TEOV_callback *rootPtr = TOP_CB(interp); - - if (TCL_DTRACE_CMD_ARGS_ENABLED()) { - char *a[10]; - int i = 0; + NRE_callback *rootPtr = TOP_CB(interp); - while (i < 10) { - a[i] = i < objc ? TclGetString(objv[i]) : NULL; i++; - } - TCL_DTRACE_CMD_ARGS(a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], - a[8], a[9]); - } - if (TCL_DTRACE_CMD_INFO_ENABLED() && ((Interp *) interp)->cmdFramePtr) { - Tcl_Obj *info = TclInfoFrame(interp, ((Interp *) interp)->cmdFramePtr); - char *a[6]; int i[2]; - - TclDTraceInfo(info, a, i); - TCL_DTRACE_CMD_INFO(a[0], a[1], a[2], a[3], i[0], i[1], a[4], a[5]); - TclDecrRefCount(info); - } - if ((TCL_DTRACE_CMD_RETURN_ENABLED() || TCL_DTRACE_CMD_RESULT_ENABLED()) - && objc) { - TclNRAddCallback(interp, DTraceCmdReturn, objv[0], NULL, NULL, NULL); - } - if (TCL_DTRACE_CMD_ENTRY_ENABLED() && objc) { - TCL_DTRACE_CMD_ENTRY(TclGetString(objv[0]), objc - 1, - (Tcl_Obj **)(objv + 1)); - } - result = (*objProc)(clientData, interp, objc, objv); - return TclNRRunCallbacks(interp, result, rootPtr, 0); + TclNRAddCallback(interp, Dispatch, objProc, clientData, + INT2PTR(objc), objv); + return TclNRRunCallbacks(interp, TCL_OK, rootPtr); } /* @@ -7906,7 +8154,7 @@ Tcl_NREvalObjv( { return TclNREvalObjv(interp, objc, objv, flags, NULL); } - + int Tcl_NRCmdSwap( Tcl_Interp *interp, @@ -7915,7 +8163,8 @@ Tcl_NRCmdSwap( Tcl_Obj *const objv[], int flags) { - return TclNREvalObjv(interp, objc, objv, flags, (Command *) cmd); + return TclNREvalObjv(interp, objc, objv, flags|TCL_EVAL_NOERR, + (Command *) cmd); } /***************************************************************************** @@ -7942,79 +8191,162 @@ Tcl_NRCmdSwap( * FIXME NRE! */ +void +TclMarkTailcall( + Tcl_Interp *interp) +{ + Interp *iPtr = (Interp *) interp; + + if (iPtr->deferredCallbacks == NULL) { + TclNRAddCallback(interp, NRCommand, NULL, NULL, + NULL, NULL); + iPtr->deferredCallbacks = TOP_CB(interp); + } +} + +void +TclSkipTailcall( + Tcl_Interp *interp) +{ + Interp *iPtr = (Interp *) interp; + + TclMarkTailcall(interp); + iPtr->deferredCallbacks->data[1] = INT2PTR(1); +} + +void +TclPushTailcallPoint( + Tcl_Interp *interp) +{ + TclNRAddCallback(interp, NRCommand, NULL, NULL, NULL, NULL); + ((Interp *) interp)->numLevels++; +} + +void +TclSetTailcall( + Tcl_Interp *interp, + Tcl_Obj *listPtr) +{ + /* + * Find the splicing spot: right before the NRCommand of the thing + * being tailcalled. Note that we skip NRCommands marked by a 1 in data[1] + * (used by command redirectors). + */ + + NRE_callback *runPtr; + + for (runPtr = TOP_CB(interp); runPtr; runPtr = runPtr->nextPtr) { + if (((runPtr->procPtr) == NRCommand) && !runPtr->data[1]) { + break; + } + } + if (!runPtr) { + Tcl_Panic("tailcall cannot find the right splicing spot: should not happen!"); + } + runPtr->data[1] = listPtr; +} + int -TclNRAtProcExitObjCmd( +TclNRTailcallObjCmd( ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *const objv[]) { Interp *iPtr = (Interp *) interp; - Tcl_Obj *listPtr; - Namespace *nsPtr = iPtr->varFramePtr->nsPtr; - if (objc < 2) { - Tcl_WrongNumArgs(interp, 1, objv, "command ?arg ...?"); + if (objc < 1) { + Tcl_WrongNumArgs(interp, 1, objv, "?command? ?arg ...?"); return TCL_ERROR; } - if (!iPtr->varFramePtr->isProcCallFrame || /* is not a body ... */ - (iPtr->framePtr != iPtr->varFramePtr)) { /* or is upleveled */ - Tcl_SetResult(interp, - "atProcExit/tailcall can only be called from a proc or lambda", - TCL_STATIC); + if (!(iPtr->varFramePtr->isProcCallFrame & 1)) { /* or is upleveled */ + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "tailcall can only be called from a proc or lambda", -1)); + Tcl_SetErrorCode(interp, "TCL", "TAILCALL", "ILLEGAL", NULL); return TCL_ERROR; } - nsPtr->activationCount++; - listPtr = Tcl_NewListObj(objc-1, objv+1); - Tcl_IncrRefCount(listPtr); + /* + * Invocation without args just clears a scheduled tailcall; invocation + * with an argument replaces any previously scheduled tailcall. + */ + + if (iPtr->varFramePtr->tailcallPtr) { + Tcl_DecrRefCount(iPtr->varFramePtr->tailcallPtr); + iPtr->varFramePtr->tailcallPtr = NULL; + } /* - * Add two callbacks: first the one to actually evaluate the tailcalled - * command, then the one that signals TEBC to stash the first at its - * proper place. + * Create the callback to actually evaluate the tailcalled + * command, then set it in the varFrame so that PopCallFrame can use it + * at the proper time. Being lazy: exploit the TclNRAddCallBack macro to + * build the callback. */ - TclNRAddCallback(interp, NRAtProcExitEval, listPtr, nsPtr, NULL, NULL); - TclNRAddCallback(interp, NRCallTEBC, clientData, NULL, NULL, NULL); - return TCL_OK; + if (objc > 1) { + Tcl_Obj *listPtr, *nsObjPtr; + Tcl_Namespace *nsPtr = (Tcl_Namespace *) iPtr->varFramePtr->nsPtr; + Tcl_Namespace *ns1Ptr; + + /* The tailcall data is in a Tcl list: the first element is the + * namespace, the rest the command to be tailcalled. */ + + listPtr = Tcl_NewListObj(objc, objv); + + nsObjPtr = Tcl_NewStringObj(nsPtr->fullName, -1); + if ((TCL_OK != TclGetNamespaceFromObj(interp, nsObjPtr, &ns1Ptr)) + || (nsPtr != ns1Ptr)) { + Tcl_Panic("Tailcall failed to find the proper namespace"); + } + TclListObjSetElement(interp, listPtr, 0, nsObjPtr); + + iPtr->varFramePtr->tailcallPtr = listPtr; + } + return TCL_RETURN; } int -NRAtProcExitEval( +TclNRTailcallEval( ClientData data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; - Tcl_Obj *listPtr = data[0]; - Namespace *nsPtr = data[1]; + Tcl_Obj *listPtr = data[0], *nsObjPtr; + Tcl_Namespace *nsPtr; int objc; Tcl_Obj **objv; - TclNRAddCallback(interp, AtProcExitCleanup, listPtr, NULL, NULL, NULL); + Tcl_ListObjGetElements(interp, listPtr, &objc, &objv); + nsObjPtr = objv[0]; + if (result == TCL_OK) { - iPtr->lookupNsPtr = nsPtr; - ListObjGetElements(listPtr, objc, objv); - result = TclNREvalObjv(interp, objc, objv, 0, NULL); + result = TclGetNamespaceFromObj(interp, nsObjPtr, &nsPtr); } - nsPtr->activationCount--; - if ((nsPtr->flags & NS_DYING) - && (nsPtr->activationCount - (nsPtr == iPtr->globalNsPtr) == 0)) { - /* - * FIXME NRE tailcall: is this the proper way to manage this? This is - * like what CallFrames do. - */ + if (result != TCL_OK) { + /* + * Tailcall execution was preempted, eg by an intervening catch or by + * a now-gone namespace: cleanup and return. + */ - Tcl_DeleteNamespace((Tcl_Namespace *) nsPtr); + TailcallCleanup(data, interp, result); + return result; } - return result; + + /* + * Perform the tailcall + */ + + TclMarkTailcall(interp); + TclNRAddCallback(interp, TailcallCleanup, listPtr, NULL, NULL,NULL); + iPtr->lookupNsPtr = (Namespace *) nsPtr; + return TclNREvalObjv(interp, objc-1, objv+1, 0, NULL); } static int -AtProcExitCleanup( +TailcallCleanup( ClientData data[], Tcl_Interp *interp, int result) @@ -8022,6 +8354,7 @@ AtProcExitCleanup( Tcl_DecrRefCount((Tcl_Obj *) data[0]); return result; } + void Tcl_NRAddCallback( @@ -8060,32 +8393,6 @@ Tcl_NRAddCallback( *---------------------------------------------------------------------- */ -static int NRInterpCoroutine(ClientData clientData, - Tcl_Interp *interp, int objc, - Tcl_Obj *const objv[]); -static int RewindCoroutine(CoroutineData *corPtr, int result); -static void DeleteCoroutine(ClientData clientData); -static void PlugCoroutineChains(CoroutineData *corPtr); - -static int NRCoroutineFirstCallback(ClientData data[], - Tcl_Interp *interp, int result); -static int NRCoroutineExitCallback(ClientData data[], - Tcl_Interp *interp, int result); -static int NRCoroutineCallerCallback(ClientData data[], - Tcl_Interp *interp, int result); - -static const CorContext NULL_CONTEXT = {NULL, NULL, NULL}; - -#define SAVE_CONTEXT(context) \ - (context).framePtr = iPtr->framePtr; \ - (context).varFramePtr = iPtr->varFramePtr; \ - (context).cmdFramePtr = iPtr->cmdFramePtr - -#define RESTORE_CONTEXT(context) \ - iPtr->framePtr = (context).framePtr; \ - iPtr->varFramePtr = (context).varFramePtr; \ - iPtr->cmdFramePtr = (context).cmdFramePtr - #define iPtr ((Interp *) interp) int @@ -8095,14 +8402,17 @@ TclNRYieldObjCmd( int objc, Tcl_Obj *const objv[]) { + CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; + if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?returnValue?"); return TCL_ERROR; } - if (!iPtr->execEnvPtr->corPtr) { - Tcl_SetResult(interp, "yield can only be called in a coroutine", - TCL_STATIC); + if (!corPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "yield can only be called in a coroutine", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", NULL); return TCL_ERROR; } @@ -8110,93 +8420,104 @@ TclNRYieldObjCmd( Tcl_SetObjResult(interp, objv[1]); } - TclNRAddCallback(interp, NRCallTEBC, INT2PTR(TCL_NR_YIELD_TYPE), - NULL, NULL, NULL); + NRE_ASSERT(!COR_IS_SUSPENDED(corPtr)); + TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, + clientData, NULL, NULL); return TCL_OK; } -static int -RewindCoroutine( - CoroutineData *corPtr, - int result) +int +TclNRYieldToObjCmd( + ClientData clientData, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) { - Tcl_Obj *objPtr; - Tcl_Interp *interp = corPtr->eePtr->interp; - Tcl_InterpState state = Tcl_SaveInterpState(interp, result); + CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; + Tcl_Obj *listPtr, *nsObjPtr; + Tcl_Namespace *nsPtr = TclGetCurrentNamespace(interp); - NRE_ASSERT(COR_IS_SUSPENDED(corPtr)); - NRE_ASSERT(corPtr->eePtr != NULL); - NRE_ASSERT(corPtr->eePtr->bottomPtr != NULL); - NRE_ASSERT(corPtr->eePtr != iPtr->execEnvPtr); - - TclNewObj(objPtr); - Tcl_IncrRefCount(objPtr); - - corPtr->eePtr->rewind = 1; - result = NRInterpCoroutine(corPtr, interp, 1, &objPtr); - - NRE_ASSERT(iPtr->execEnvPtr == corPtr->eePtr); + if (objc < 2) { + Tcl_WrongNumArgs(interp, 1, objv, "command ?arg ...?"); + return TCL_ERROR; + } - Tcl_DecrRefCount(objPtr); - result = Tcl_RestoreInterpState(interp, state); - return result; -} + if (!corPtr) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "yieldto can only be called in a coroutine", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ILLEGAL_YIELD", NULL); + return TCL_ERROR; + } -static void -DeleteCoroutine( - ClientData clientData) -{ - CoroutineData *corPtr = (CoroutineData *) clientData; - Tcl_Interp *interp = corPtr->eePtr->interp; - TEOV_callback *rootPtr = TOP_CB(interp); - - if (COR_IS_SUSPENDED(corPtr)) { - (void) TclNRRunCallbacks(interp, RewindCoroutine(corPtr, TCL_OK), rootPtr, 0); + if (((Namespace *) nsPtr)->flags & NS_DYING) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "yieldto called in deleted namespace", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "YIELDTO_IN_DELETED", + NULL); + return TCL_ERROR; } -} -static void -PlugCoroutineChains( - CoroutineData *corPtr) -{ - Tcl_Interp *interp = corPtr->eePtr->interp; + /* + * Add the tailcall in the caller env, then just yield. + * + * This is essentially code from TclNRTailcallObjCmd + */ + + listPtr = Tcl_NewListObj(objc, objv); + nsObjPtr = Tcl_NewStringObj(nsPtr->fullName, -1); + TclListObjSetElement(interp, listPtr, 0, nsObjPtr); /* - * Called to plug the coroutine's running environment into the caller's, - * so that the frame chains are uninterrupted. Note that the levels and - * numlevels may be wrong - we should fix them for the whole chain and not - * just the base! This probably breaks Tip 280 and should be fixed, or at - * least rethought as some of 280's functionality makes doubtful sense in - * presence of coroutines (maybe the cmdFrame should be attached to the - * execEnv and not the interp?) + * Add the callback in the caller's env, then instruct TEBC to yield. */ - corPtr->base.framePtr->callerPtr = corPtr->caller.framePtr; - corPtr->base.framePtr->callerVarPtr = corPtr->caller.varFramePtr; + iPtr->execEnvPtr = corPtr->callerEEPtr; + TclSetTailcall(interp, listPtr); + iPtr->execEnvPtr = corPtr->eePtr; - corPtr->base.cmdFramePtr->nextPtr = corPtr->caller.cmdFramePtr; + return TclNRYieldObjCmd(INT2PTR(CORO_ACTIVATE_YIELDM), interp, 1, objv); } - + static int -NRCoroutineFirstCallback( +RewindCoroutineCallback( ClientData data[], Tcl_Interp *interp, int result) { - CoroutineData *corPtr = data[0]; - register CmdFrame *tmpPtr = iPtr->cmdFramePtr; + return Tcl_RestoreInterpState(interp, data[0]); +} - if (corPtr->eePtr) { - while (tmpPtr->nextPtr != corPtr->caller.cmdFramePtr) { - tmpPtr = tmpPtr->nextPtr; - } +static int +RewindCoroutine( + CoroutineData *corPtr, + int result) +{ + Tcl_Interp *interp = corPtr->eePtr->interp; + Tcl_InterpState state = Tcl_SaveInterpState(interp, result); - corPtr->base.cmdFramePtr = tmpPtr; - } + NRE_ASSERT(COR_IS_SUSPENDED(corPtr)); + NRE_ASSERT(corPtr->eePtr != NULL); + NRE_ASSERT(corPtr->eePtr != iPtr->execEnvPtr); - return result; + corPtr->eePtr->rewind = 1; + TclNRAddCallback(interp, RewindCoroutineCallback, state, + NULL, NULL, NULL); + return TclNRInterpCoroutine(corPtr, interp, 0, NULL); } + +static void +DeleteCoroutine( + ClientData clientData) +{ + CoroutineData *corPtr = clientData; + Tcl_Interp *interp = corPtr->eePtr->interp; + NRE_callback *rootPtr = TOP_CB(interp); + if (COR_IS_SUSPENDED(corPtr)) { + TclNRRunCallbacks(interp, RewindCoroutine(corPtr,TCL_OK), rootPtr); + } +} + static int NRCoroutineCallerCallback( ClientData data[], @@ -8222,7 +8543,7 @@ NRCoroutineCallerCallback( NRE_ASSERT(iPtr->varFramePtr == corPtr->caller.varFramePtr); NRE_ASSERT(iPtr->framePtr == corPtr->caller.framePtr); NRE_ASSERT(iPtr->cmdFramePtr == corPtr->caller.cmdFramePtr); - ckfree((char *) corPtr); + ckfree(corPtr); return result; } @@ -8232,9 +8553,9 @@ NRCoroutineCallerCallback( if (cmdPtr->flags & CMD_IS_DELETED) { /* - * The command was deleted while it was running: wind down the execEnv, - * this will do the complete cleanup. RewindCoroutine will restore both - * the caller's context and interp state. + * The command was deleted while it was running: wind down the + * execEnv, this will do the complete cleanup. RewindCoroutine will + * restore both the caller's context and interp state. */ return RewindCoroutine(corPtr, result); @@ -8242,7 +8563,7 @@ NRCoroutineCallerCallback( return result; } - + static int NRCoroutineExitCallback( ClientData data[], @@ -8262,13 +8583,7 @@ NRCoroutineExitCallback( NRE_ASSERT(TOP_CB(interp) == NULL); NRE_ASSERT(iPtr->execEnvPtr == corPtr->eePtr); NRE_ASSERT(!COR_IS_SUSPENDED(corPtr)); - NRE_ASSERT(TOP_CB(interp) == NULL); - NRE_ASSERT((corPtr->callerEEPtr->callbackPtr->procPtr == NRCoroutineCallerCallback) - || ((corPtr->callerEEPtr->callbackPtr->procPtr == NRCoroutineFirstCallback) && - (corPtr->callerEEPtr->callbackPtr->nextPtr->procPtr == NRCoroutineCallerCallback))); - - NRE_ASSERT(iPtr->framePtr->compiledLocals == NULL); - TclPopStackFrame(interp); + NRE_ASSERT((corPtr->callerEEPtr->callbackPtr->procPtr == NRCoroutineCallerCallback)); cmdPtr->deleteProc = NULL; Tcl_DeleteCommandFromToken(interp, (Tcl_Command) cmdPtr); @@ -8278,59 +8593,233 @@ NRCoroutineExitCallback( TclDeleteExecEnv(corPtr->eePtr); corPtr->eePtr = NULL; - /* RESTORE_CONTEXT(corPtr->caller); AUTOMATIC! */ + corPtr->stackLevel = NULL; + + /* + * #280. + * Drop the coroutine-owned copy of the lineLABCPtr hashtable for literal + * command arguments in bytecode. + */ - NRE_ASSERT(iPtr->framePtr == corPtr->caller.framePtr); - NRE_ASSERT(iPtr->cmdFramePtr == corPtr->caller.cmdFramePtr); - iPtr->varFramePtr = corPtr->caller.varFramePtr; + Tcl_DeleteHashTable(corPtr->lineLABCPtr); + ckfree(corPtr->lineLABCPtr); + corPtr->lineLABCPtr = NULL; + RESTORE_CONTEXT(corPtr->caller); iPtr->execEnvPtr = corPtr->callerEEPtr; + iPtr->numLevels++; return result; } + +/* + *---------------------------------------------------------------------- + * + * TclNRCoroutineActivateCallback -- + * + * This is the workhorse for coroutines: it implements both yield and + * resume. + * + * It is important that both be implemented in the same callback: the + * detection of the impossibility to suspend due to a busy C-stack relies + * on the precise position of a local variable in the stack. We do not + * want the compiler to play tricks on us, either by moving things around + * or inlining. + * + *---------------------------------------------------------------------- + */ + +int +TclNRCoroutineActivateCallback( + ClientData data[], + Tcl_Interp *interp, + int result) +{ + CoroutineData *corPtr = data[0]; + int type = PTR2INT(data[1]); + int numLevels, unused; + int *stackLevel = &unused; + + if (!corPtr->stackLevel) { + /* + * -- Coroutine is suspended -- + * Push the callback to restore the caller's context on yield or + * return. + */ + + TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, + NULL, NULL, NULL); + + /* + * Record the stackLevel at which the resume is happening, then swap + * the interp's environment to make it suitable to run this coroutine. + */ + + corPtr->stackLevel = stackLevel; + numLevels = corPtr->auxNumLevels; + corPtr->auxNumLevels = iPtr->numLevels; + + SAVE_CONTEXT(corPtr->caller); + corPtr->callerEEPtr = iPtr->execEnvPtr; + RESTORE_CONTEXT(corPtr->running); + iPtr->execEnvPtr = corPtr->eePtr; + iPtr->numLevels += numLevels; + } else { + /* + * Coroutine is active: yield + */ + + if (corPtr->stackLevel != stackLevel) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "cannot yield: C stack busy", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "CANT_YIELD", + NULL); + return TCL_ERROR; + } + + if (type == CORO_ACTIVATE_YIELD) { + corPtr->nargs = COROUTINE_ARGUMENTS_SINGLE_OPTIONAL; + } else if (type == CORO_ACTIVATE_YIELDM) { + corPtr->nargs = COROUTINE_ARGUMENTS_ARBITRARY; + } else { + Tcl_Panic("Yield received an option which is not implemented"); + } + + corPtr->stackLevel = NULL; + + numLevels = iPtr->numLevels; + iPtr->numLevels = corPtr->auxNumLevels; + corPtr->auxNumLevels = numLevels - corPtr->auxNumLevels; + + iPtr->execEnvPtr = corPtr->callerEEPtr; + } + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * + * NRCoroInjectObjCmd -- + * + * Implementation of [::tcl::unsupported::inject] command. + * + *---------------------------------------------------------------------- + */ static int -NRInterpCoroutine( +NRCoroInjectObjCmd( ClientData clientData, - Tcl_Interp *interp, /* Current interpreter. */ - int objc, /* Number of arguments. */ - Tcl_Obj *const objv[]) /* Argument objects. */ + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) { - CoroutineData *corPtr = clientData; + Command *cmdPtr; + CoroutineData *corPtr; + ExecEnv *savedEEPtr = iPtr->execEnvPtr; - if ((objc != 1) && (objc != 2)) { - Tcl_WrongNumArgs(interp, 1, objv, "?arg?"); + /* + * Usage more or less like tailcall: + * inject coroName cmd ?arg1 arg2 ...? + */ + + if (objc < 3) { + Tcl_WrongNumArgs(interp, 1, objv, "coroName cmd ?arg1 arg2 ...?"); return TCL_ERROR; } + cmdPtr = (Command *) Tcl_GetCommandFromObj(interp, objv[1]); + if ((!cmdPtr) || (cmdPtr->nreProc != TclNRInterpCoroutine)) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can only inject a command into a coroutine", -1)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "COROUTINE", + TclGetString(objv[1]), NULL); + return TCL_ERROR; + } + + corPtr = cmdPtr->objClientData; if (!COR_IS_SUSPENDED(corPtr)) { - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "coroutine \"", Tcl_GetString(objv[0]), - "\" is already running", NULL); - Tcl_SetErrorCode(interp, "COROUTINE_BUSY", NULL); - return TCL_ERROR; + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "can only inject a command into a suspended coroutine", -1)); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "ACTIVE", NULL); + return TCL_ERROR; } /* - * Swap the interp's environment to make it suitable to run this coroutine. - * TEBC needs no info to resume executing after a suspension: the codePtr - * will be read from the execEnv's saved bottomPtr. + * Add the callback to the coro's execEnv, so that it is the first thing + * to happen when the coro is resumed. */ - if (objc == 2) { - Tcl_SetObjResult(interp, objv[1]); - } + iPtr->execEnvPtr = corPtr->eePtr; + TclNREvalObjEx(interp, Tcl_NewListObj(objc-2, objv+2), 0, NULL, INT_MIN); + iPtr->execEnvPtr = savedEEPtr; - SAVE_CONTEXT(corPtr->caller); - RESTORE_CONTEXT(corPtr->running); - PlugCoroutineChains(corPtr); + return TCL_OK; +} + +int +TclNRInterpCoroutine( + ClientData clientData, + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *const objv[]) /* Argument objects. */ +{ + CoroutineData *corPtr = clientData; - TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, NULL, NULL, - NULL); + if (!COR_IS_SUSPENDED(corPtr)) { + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "coroutine \"%s\" is already running", + Tcl_GetString(objv[0]))); + Tcl_SetErrorCode(interp, "TCL", "COROUTINE", "BUSY", NULL); + return TCL_ERROR; + } - iPtr->execEnvPtr = corPtr->eePtr; - return TclExecuteByteCode(interp, NULL); + /* + * Parse all the arguments to work out what to feed as the result of the + * [yield]. TRICKY POINT: objc==0 happens here! It occurs when a coroutine + * is deleted! + */ + + switch (corPtr->nargs) { + case COROUTINE_ARGUMENTS_SINGLE_OPTIONAL: + if (objc == 2) { + Tcl_SetObjResult(interp, objv[1]); + } else if (objc > 2) { + Tcl_WrongNumArgs(interp, 1, objv, "?arg?"); + return TCL_ERROR; + } + break; + default: + if (corPtr->nargs != objc-1) { + Tcl_SetObjResult(interp, + Tcl_NewStringObj("wrong coro nargs; how did we get here? " + "not implemented!", -1)); + Tcl_SetErrorCode(interp, "TCL", "WRONGARGS", NULL); + return TCL_ERROR; + } + /* fallthrough */ + case COROUTINE_ARGUMENTS_ARBITRARY: + if (objc > 1) { + Tcl_SetObjResult(interp, Tcl_NewListObj(objc-1, objv+1)); + } + break; + } + + TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, + NULL, NULL, NULL); + return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * TclNRCoroutineObjCmd -- + * + * Implementation of [coroutine] command; see documentation for + * description of what this does. + * + *---------------------------------------------------------------------- + */ int TclNRCoroutineObjCmd( @@ -8341,13 +8830,10 @@ TclNRCoroutineObjCmd( { Command *cmdPtr; CoroutineData *corPtr; - Tcl_Obj *cmdObjPtr; - CallFrame *framePtr, **framePtrPtr; - TEOV_callback *rootPtr = TOP_CB(interp); - char *fullName; - const char *procName; + const char *fullName, *procName; Namespace *nsPtr, *altNsPtr, *cxtNsPtr; Tcl_DString ds; + Namespace *lookupNsPtr = iPtr->varFramePtr->nsPtr; if (objc < 3) { Tcl_WrongNumArgs(interp, 1, objv, "name cmd ?arg ...?"); @@ -8364,108 +8850,159 @@ TclNRCoroutineObjCmd( &nsPtr, &altNsPtr, &cxtNsPtr, &procName); if (nsPtr == NULL) { - Tcl_AppendResult(interp, "can't create procedure \"", fullName, - "\": unknown namespace", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create procedure \"%s\": unknown namespace", + fullName)); + Tcl_SetErrorCode(interp, "TCL", "LOOKUP", "NAMESPACE", NULL); return TCL_ERROR; } if (procName == NULL) { - Tcl_AppendResult(interp, "can't create procedure \"", fullName, - "\": bad procedure name", NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create procedure \"%s\": bad procedure name", + fullName)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", fullName, NULL); return TCL_ERROR; } if ((nsPtr != iPtr->globalNsPtr) && (procName != NULL) && (procName[0] == ':')) { - Tcl_AppendResult(interp, "can't create procedure \"", procName, - "\" in non-global namespace with name starting with \":\"", - NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "can't create procedure \"%s\" in non-global namespace with" + " name starting with \":\"", procName)); + Tcl_SetErrorCode(interp, "TCL", "VALUE", "COMMAND", procName, NULL); return TCL_ERROR; } - corPtr = (CoroutineData *) ckalloc(sizeof(CoroutineData)); - corPtr->eePtr = TclCreateExecEnv(interp); - corPtr->callerEEPtr = iPtr->execEnvPtr; - corPtr->eePtr->corPtr = corPtr; - corPtr->stackLevel = NULL; - /* - * On first run just set a 0 level-offset, the natural numbering is - * correct. The offset will be fixed for later runs. + * We ARE creating the coroutine command: allocate the corresponding + * struct and create the corresponding command. */ - + + corPtr = ckalloc(sizeof(CoroutineData)); + Tcl_DStringInit(&ds); if (nsPtr != iPtr->globalNsPtr) { Tcl_DStringAppend(&ds, nsPtr->fullName, -1); - Tcl_DStringAppend(&ds, "::", 2); + TclDStringAppendLiteral(&ds, "::"); } Tcl_DStringAppend(&ds, procName, -1); cmdPtr = (Command *) Tcl_NRCreateCommand(interp, Tcl_DStringValue(&ds), - /*objProc*/ NULL, NRInterpCoroutine, corPtr, DeleteCoroutine); + /*objProc*/ NULL, TclNRInterpCoroutine, corPtr, DeleteCoroutine); Tcl_DStringFree(&ds); corPtr->cmdPtr = cmdPtr; cmdPtr->refCount++; /* - * Be sure not to pass a canonical list for the command so that we insure - * the body is bytecompiled: we need a TEBC instance to handle [yield] + * #280. + * Provide the new coroutine with its own copy of the lineLABCPtr + * hashtable for literal command arguments in bytecode. Note that that + * CFWordBC chains are not duplicated, only the entrypoints to them. This + * means that in the presence of coroutines each chain is potentially a + * tree. Like the chain -> tree conversion of the CmdFrame stack. */ - cmdObjPtr = Tcl_NewListObj(objc-2, &objv[2]); - TclGetString(cmdObjPtr); - TclFreeIntRep(cmdObjPtr); - cmdObjPtr->typePtr = NULL; - Tcl_IncrRefCount(cmdObjPtr); + { + Tcl_HashSearch hSearch; + Tcl_HashEntry *hePtr; + + corPtr->lineLABCPtr = ckalloc(sizeof(Tcl_HashTable)); + Tcl_InitHashTable(corPtr->lineLABCPtr, TCL_ONE_WORD_KEYS); + + for (hePtr = Tcl_FirstHashEntry(iPtr->lineLABCPtr,&hSearch); + hePtr; hePtr = Tcl_NextHashEntry(&hSearch)) { + int isNew; + Tcl_HashEntry *newPtr = + Tcl_CreateHashEntry(corPtr->lineLABCPtr, + Tcl_GetHashKey(iPtr->lineLABCPtr, hePtr), + &isNew); + + Tcl_SetHashValue(newPtr, Tcl_GetHashValue(hePtr)); + } + } /* - * Set up the callback in caller execEnv and switch to the new execEnv. - * Switch now so that the CallFrame is allocated on the new execEnv's - * stack. Then push a CallFrame and CmdFrame. + * Create the base context. */ - TclNRAddCallback(interp, NRCoroutineCallerCallback, corPtr, NULL, NULL, - NULL); - TclNRAddCallback(interp, NRCoroutineFirstCallback, corPtr, NULL, NULL, - NULL); - SAVE_CONTEXT(corPtr->caller); + corPtr->running.framePtr = iPtr->rootFramePtr; + corPtr->running.varFramePtr = iPtr->rootFramePtr; + corPtr->running.cmdFramePtr = NULL; + corPtr->running.lineLABCPtr = corPtr->lineLABCPtr; + corPtr->stackLevel = NULL; + corPtr->auxNumLevels = 0; + + /* + * Create the coro's execEnv, switch to it to push the exit and coro + * command callbacks, then switch back. + */ + corPtr->eePtr = TclCreateExecEnv(interp, CORO_STACK_INITIAL_SIZE); + corPtr->callerEEPtr = iPtr->execEnvPtr; + corPtr->eePtr->corPtr = corPtr; + + SAVE_CONTEXT(corPtr->caller); + corPtr->callerEEPtr = iPtr->execEnvPtr; + RESTORE_CONTEXT(corPtr->running); iPtr->execEnvPtr = corPtr->eePtr; - framePtrPtr = &framePtr; - if (TCL_OK != TclPushStackFrame(interp, (Tcl_CallFrame **) framePtrPtr, - NULL, 0)) { - corPtr->eePtr->corPtr = NULL; - TclDeleteExecEnv(corPtr->eePtr); - ckfree((char *) corPtr); - return TCL_ERROR; - } - framePtr->objc = objc-2; - framePtr->objv = &objv[2]; + TclNRAddCallback(interp, NRCoroutineExitCallback, corPtr, + NULL, NULL, NULL); - SAVE_CONTEXT(corPtr->base); - corPtr->running = NULL_CONTEXT; + /* insure that the command is looked up in the correct namespace */ + iPtr->lookupNsPtr = lookupNsPtr; + Tcl_NREvalObj(interp, Tcl_NewListObj(objc-2, objv+2), 0); + iPtr->numLevels--; + + SAVE_CONTEXT(corPtr->running); + RESTORE_CONTEXT(corPtr->caller); + iPtr->execEnvPtr = corPtr->callerEEPtr; /* - * Eval things in 'uplevel #0', except for the very first command lookup - * which should be looked up in caller's context. - * - * A better approach would use the lambda infrastructure, but it is a bit - * clumsy for now: we have the "lambda is a nameless proc" hack, we'd need - * the cleaner "proc is a named lambda" to do this properly. + * Now just resume the coroutine. */ - iPtr->varFramePtr = iPtr->rootFramePtr; - iPtr->lookupNsPtr = iPtr->framePtr->nsPtr; + TclNRAddCallback(interp, TclNRCoroutineActivateCallback, corPtr, + NULL, NULL, NULL); + return TCL_OK; +} + +/* + * This is used in the [info] ensemble + */ + +int +TclInfoCoroutineCmd( + ClientData dummy, + Tcl_Interp *interp, + int objc, + Tcl_Obj *const objv[]) +{ + CoroutineData *corPtr = iPtr->execEnvPtr->corPtr; - TclNRAddCallback(interp, NRCoroutineExitCallback, corPtr, NULL,NULL,NULL); - return TclNRRunCallbacks(interp, - TclNREvalObjEx(interp, cmdObjPtr, 0, NULL, 0), rootPtr, 0); + if (objc != 1) { + Tcl_WrongNumArgs(interp, 1, objv, NULL); + return TCL_ERROR; + } + + if (corPtr && !(corPtr->cmdPtr->flags & CMD_IS_DELETED)) { + Tcl_Obj *namePtr; + + TclNewObj(namePtr); + Tcl_GetCommandFullName(interp, (Tcl_Command) corPtr->cmdPtr, namePtr); + Tcl_SetObjResult(interp, namePtr); + } + return TCL_OK; } + +#undef iPtr /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 + * tab-width: 8 + * indent-tabs-mode: nil * End: */ |