summaryrefslogtreecommitdiffstats
path: root/generic/tclBasic.c
diff options
context:
space:
mode:
Diffstat (limited to 'generic/tclBasic.c')
-rw-r--r--generic/tclBasic.c450
1 files changed, 445 insertions, 5 deletions
diff --git a/generic/tclBasic.c b/generic/tclBasic.c
index 9210fd7..d0aa8e2 100644
--- a/generic/tclBasic.c
+++ b/generic/tclBasic.c
@@ -10,11 +10,12 @@
* Copyright (c) 1998-1999 by Scriptics Corporation.
* Copyright (c) 2001, 2002 by Kevin B. Kenny. All rights reserved.
* Copyright (c) 2007 Daniel A. Steffen <das@users.sourceforge.net>
+ * Copyright (c) 2006-2008 by Joe Mistachkin. All rights reserved.
*
* 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.301 2008/06/08 03:21:31 msofer Exp $
+ * RCS: @(#) $Id: tclBasic.c,v 1.302 2008/06/13 05:45:08 mistachkin Exp $
*/
#include "tclInt.h"
@@ -53,6 +54,8 @@ typedef struct OldMathFuncData {
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);
@@ -362,6 +365,56 @@ static int stackGrowsDown = 1;
#endif /* TCL_NO_STACK_CHECK/TCL_CROSS_COMPILE */
/*
+ * 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 --
+ *
+ * Finalizes the script cancellation hash table.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclFinalizeEvaluation(void)
+{
+ Tcl_MutexLock(&cancelLock);
+ if (cancelTableInitialized == 1) {
+ Tcl_DeleteHashTable(&cancelTable);
+ cancelTableInitialized = 0;
+ }
+ Tcl_MutexUnlock(&cancelLock);
+}
+
+/*
*----------------------------------------------------------------------
*
* Tcl_CreateInterp --
@@ -389,6 +442,9 @@ Tcl_CreateInterp(void)
const OpCmdInfo *opcmdInfoPtr;
const CmdInfo *cmdInfoPtr;
Tcl_Namespace *mathfuncNSPtr, *mathopNSPtr;
+ Tcl_HashEntry *hPtr;
+ int isNew;
+ CancelInfo *cancelInfo;
union {
char c[sizeof(short)];
short s;
@@ -412,6 +468,15 @@ Tcl_CreateInterp(void)
Tcl_Panic("Tcl_CallFrame and CallFrame are not the same size");
}
+ if (cancelTableInitialized == 0) {
+ Tcl_MutexLock(&cancelLock);
+ if (cancelTableInitialized == 0) {
+ Tcl_InitHashTable(&cancelTable, TCL_ONE_WORD_KEYS);
+ cancelTableInitialized = 1;
+ }
+ Tcl_MutexUnlock(&cancelLock);
+ }
+
/*
* Initialize support for namespaces and create the global namespace
* (whose name is ""; an alias is "::"). This also initializes the Tcl
@@ -546,6 +611,25 @@ Tcl_CreateInterp(void)
iPtr->chanMsg = NULL;
/*
+ * TIP #285, Script cancellation support.
+ */
+
+ iPtr->asyncCancelMsg = Tcl_NewObj();
+
+ cancelInfo = (CancelInfo *) ckalloc(sizeof(CancelInfo));
+ cancelInfo->interp = interp;
+
+ iPtr->asyncCancel = Tcl_AsyncCreate(CancelEvalProc, cancelInfo);
+ cancelInfo->async = iPtr->asyncCancel;
+ cancelInfo->result = NULL;
+ cancelInfo->length = 0;
+
+ Tcl_MutexLock(&cancelLock);
+ hPtr = Tcl_CreateHashEntry(&cancelTable, (char *) iPtr, &isNew);
+ Tcl_SetHashValue(hPtr, cancelInfo);
+ Tcl_MutexUnlock(&cancelLock);
+
+ /*
* Initialize the compilation and execution statistics kept for this
* interpreter.
*/
@@ -629,9 +713,6 @@ Tcl_CreateInterp(void)
*/
for (cmdInfoPtr = builtInCmds; cmdInfoPtr->name != NULL; cmdInfoPtr++) {
- int isNew;
- Tcl_HashEntry *hPtr;
-
if ((cmdInfoPtr->objProc == NULL)
&& (cmdInfoPtr->compileProc == NULL)) {
Tcl_Panic("builtin command with NULL object command proc and a NULL compile proc");
@@ -1202,6 +1283,7 @@ DeleteInterpProc(
Tcl_HashSearch search;
Tcl_HashTable *hTablePtr;
ResolverScheme *resPtr, *nextResPtr;
+ CancelInfo *cancelInfo;
/*
* Punt if there is an error in the Tcl_Release/Tcl_Preserve matchup.
@@ -1230,6 +1312,39 @@ DeleteInterpProc(
}
/*
+ * TIP #285, Script cancellation support. Delete this interp from the
+ * global hash table of CancelInfo structs.
+ */
+
+ Tcl_MutexLock(&cancelLock);
+ hPtr = Tcl_FindHashEntry(&cancelTable, (char *) iPtr);
+ if (hPtr != NULL) {
+ cancelInfo = (CancelInfo *) Tcl_GetHashValue(hPtr);
+
+ if (cancelInfo != NULL) {
+ if (cancelInfo->result != NULL) {
+ ckfree((char *) cancelInfo->result);
+ cancelInfo->result = NULL;
+ }
+ ckfree((char *) cancelInfo);
+ cancelInfo = NULL;
+ }
+
+ Tcl_DeleteHashEntry(hPtr);
+ }
+
+ if (iPtr->asyncCancel != NULL) {
+ Tcl_AsyncDelete(iPtr->asyncCancel);
+ iPtr->asyncCancel = NULL;
+ }
+
+ if (iPtr->asyncCancelMsg != NULL) {
+ Tcl_DecrRefCount(iPtr->asyncCancelMsg);
+ iPtr->asyncCancelMsg = NULL;
+ }
+ Tcl_MutexUnlock(&cancelLock);
+
+ /*
* Shut down all limit handler callback scripts that call back into this
* interpreter. Then eliminate all limit handlers for this interpreter.
*/
@@ -2948,6 +3063,59 @@ CallCommandTraces(
return result;
}
+static int
+CancelEvalProc(clientData, interp, code)
+ ClientData clientData; /* Interp to cancel the script in progress. */
+ Tcl_Interp *interp; /* Ignored */
+ int code; /* Current return code from command. */
+{
+ CancelInfo *cancelInfo = (CancelInfo *) clientData;
+ Interp *iPtr;
+
+ if (cancelInfo != NULL) {
+ Tcl_MutexLock(&cancelLock);
+ iPtr = (Interp *) cancelInfo->interp;
+
+ 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 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.
+ */
+ iPtr->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.
+ */
+ if (cancelInfo->flags & TCL_CANCEL_UNWIND) {
+ iPtr->flags |= TCL_CANCEL_UNWIND;
+ }
+
+ /*
+ * 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);
+ } else {
+ Tcl_SetObjLength(iPtr->asyncCancelMsg, 0);
+ }
+ }
+ Tcl_MutexUnlock(&cancelLock);
+ }
+
+ return code;
+}
+
/*
*----------------------------------------------------------------------
*
@@ -3418,7 +3586,7 @@ TclInterpReady(
*/
if (iPtr->flags & DELETED) {
- Tcl_ResetResult(interp);
+ /* JJM - Superfluous Tcl_ResetResult call removed. */
Tcl_AppendResult(interp,
"attempt to call eval in deleted interpreter", NULL);
Tcl_SetErrorCode(interp, "TCL", "IDELETE",
@@ -3449,6 +3617,260 @@ TclInterpReady(
/*
*----------------------------------------------------------------------
*
+ * TclResetCancellation --
+ *
+ * Reset the script cancellation flags if the nesting level
+ * (iPtr->numLevels) for the interp is zero or argument force is
+ * non-zero.
+ *
+ * Results:
+ * A standard Tcl result.
+ *
+ * Side effects:
+ * The script cancellation flags for the interp may be reset.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclResetCancellation(
+ Tcl_Interp *interp, int force)
+{
+ register Interp *iPtr = (Interp *) interp;
+
+ if (iPtr != NULL) {
+ if (force || (iPtr->numLevels == 0)) {
+ iPtr->flags &= (~(CANCELED | TCL_CANCEL_UNWIND));
+ }
+
+ return TCL_OK;
+ } else {
+ return TCL_ERROR;
+ }
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_Canceled --
+ *
+ * Check if the script in progress has been canceled, i.e.,
+ * Tcl_CancelEval was called for this interpreter or any of its
+ * master interpreters.
+ *
+ * Results:
+ * The return value is TCL_OK if the script evaluation has not been
+ * canceled, TCL_ERROR otherwise.
+ *
+ * If "flags" contains TCL_LEAVE_ERR_MSG, an error message is returned
+ * in the interpreter's result object. Otherwise, the interpreter's
+ * result object is left unchanged. If "flags" contains
+ * TCL_CANCEL_UNWIND, TCL_ERROR will only be returned if the script
+ * evaluation is being completely unwound.
+ *
+ * Side effects:
+ * The CANCELED flag for the interp will be reset if it is set.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_Canceled(
+ Tcl_Interp *interp,
+ int flags)
+{
+ register Interp *iPtr = (Interp *) interp;
+ const char *id, *message;
+ int length;
+
+ /*
+ * 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.
+ */
+ 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;
+
+ /*
+ * 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.
+ */
+ 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.
+ */
+ if (flags & TCL_LEAVE_ERR_MSG) {
+ /*
+ * Setup errorCode variables so that we can differentiate between
+ * being canceled and unwound.
+ */
+ if (iPtr->asyncCancelMsg != NULL) {
+ message = Tcl_GetStringFromObj(iPtr->asyncCancelMsg, &length);
+ } else {
+ length = 0;
+ }
+
+ if (iPtr->flags & TCL_CANCEL_UNWIND) {
+ id = "IUNWIND";
+ if (length == 0) {
+ message = "eval unwound";
+ }
+ } else {
+ id = "ICANCEL";
+ if (length == 0) {
+ message = "eval canceled";
+ }
+ }
+
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, message, NULL);
+ Tcl_SetErrorCode(interp, "TCL", id, message, NULL);
+ }
+
+ /*
+ * 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;
+ }
+ } 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 & DELETED) {
+ break;
+ }
+ }
+ }
+
+ return TCL_OK;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_CancelEval --
+ *
+ * This function schedules the cancellation of the current script in
+ * the given interpreter.
+ *
+ * Results:
+ * The return value is a standard Tcl completion code such as TCL_OK or
+ * TCL_ERROR. Since the interp may belong to a different thread, no
+ * error message can be left in the interp's result.
+ *
+ * Side effects:
+ * The script in progress in the specified interpreter will be
+ * canceled with TCL_ERROR after asynchronous handlers are invoked at
+ * the next Tcl_Canceled check.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_CancelEval(
+ Tcl_Interp *interp, /* Interpreter in which to cancel the
+ * script. */
+ Tcl_Obj *resultObjPtr, /* The script cancellation error message
+ * or NULL for a default error message. */
+ ClientData clientData, /* Passed to CancelEvalProc. */
+ int flags) /* Collection of OR-ed bits that control
+ * the cancellation of the script. Only
+ * TCL_CANCEL_UNWIND is currently
+ * supported. */
+{
+ Tcl_HashEntry *hPtr;
+ CancelInfo *cancelInfo;
+ int code;
+ const char *result;
+
+ Tcl_MutexLock(&cancelLock);
+ if (cancelTableInitialized == 1) {
+ if (interp != NULL) {
+ hPtr = Tcl_FindHashEntry(&cancelTable, (char *) interp);
+
+ if (hPtr != NULL) {
+ cancelInfo = (CancelInfo *) Tcl_GetHashValue(hPtr);
+
+ if (cancelInfo != NULL) {
+ /*
+ * Populate information needed by the interpreter thread
+ * to fulfill the cancellation request. Currently,
+ * clientData is ignored. If the TCL_CANCEL_UNWIND flags
+ * bit is set, the script in progress is not allowed to
+ * catch the script cancellation because the evaluation
+ * stack for the interp is completely unwound.
+ */
+ if (resultObjPtr != NULL) {
+ result = Tcl_GetStringFromObj(resultObjPtr, &cancelInfo->length);
+ cancelInfo->result = ckrealloc(cancelInfo->result,
+ cancelInfo->length);
+ memcpy((void *) cancelInfo->result, (void *) result,
+ (size_t) cancelInfo->length);
+ Tcl_DecrRefCount(resultObjPtr); /* discard their result object. */
+ } else {
+ cancelInfo->result = NULL;
+ cancelInfo->length = 0;
+ }
+
+ cancelInfo->clientData = clientData;
+ cancelInfo->flags = flags;
+
+ Tcl_AsyncMark(cancelInfo->async);
+ code = TCL_OK;
+ } else {
+ /* the CancelInfo for this interp is invalid */
+ code = TCL_ERROR;
+ }
+ } else {
+ /* no CancelInfo for this interp */
+ code = TCL_ERROR;
+ }
+ } else {
+ /* a valid interp must be supplied */
+ code = TCL_ERROR;
+ }
+ } else {
+ /*
+ * No CancelInfo hash table (Tcl_CreateInterp
+ * has never been called?)
+ */
+ code = TCL_ERROR;
+ }
+ Tcl_MutexUnlock(&cancelLock);
+
+ return code;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* TclEvalObjvInternal
*
* This function evaluates a Tcl command that has already been parsed
@@ -3509,6 +3931,10 @@ TclEvalObjvInternal(
return TCL_ERROR;
}
+ if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+
if (objc == 0) {
return TCL_OK;
}
@@ -3656,6 +4082,9 @@ TclEvalObjvInternal(
if (TclAsyncReady(iPtr)) {
code = Tcl_AsyncInvoke(interp, code);
}
+ if (code == TCL_OK && Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
+ code = TCL_ERROR;
+ }
if (code == TCL_OK && TclLimitReady(iPtr->limit)) {
code = Tcl_LimitCheck(interp);
}
@@ -3786,6 +4215,8 @@ TclEvalObjvInternal(
TclGetString(objv[0]), "\"", NULL);
code = TCL_ERROR;
} else {
+ TclResetCancellation(interp, 0);
+
iPtr->numLevels++;
code = TclEvalObjvInternal(interp, newObjc, newObjv, command,
length, 0);
@@ -3841,6 +4272,8 @@ Tcl_EvalObjv(
Interp *iPtr = (Interp *) interp;
int code = TCL_OK;
+ TclResetCancellation(interp, 0);
+
iPtr->numLevels++;
code = TclEvalObjvInternal(interp, objc, objv, NULL, 0, flags);
iPtr->numLevels--;
@@ -4293,6 +4726,9 @@ TclEvalEx(
eeFramePtr->line = lines;
iPtr->cmdFramePtr = eeFramePtr;
+
+ TclResetCancellation(interp, 0);
+
iPtr->numLevels++;
code = TclEvalObjvInternal(interp, objectsUsed, objv,
parsePtr->commandStart, parsePtr->commandSize, 0);
@@ -5165,6 +5601,10 @@ TclObjInvoke(
return TCL_ERROR;
}
+ if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
+ return TCL_ERROR;
+ }
+
cmdName = TclGetString(objv[0]);
hTblPtr = iPtr->hiddenCmdTablePtr;
if (hTblPtr != NULL) {