Attachment "tip143.patch" to
ticket [926771ffff]
added by
dkf
2004-04-20 05:28:33.
Index: generic/tcl.decls
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tcl.decls,v
retrieving revision 1.103
diff -u -b -B -r1.103 tcl.decls
--- generic/tcl.decls 17 Mar 2004 18:14:12 -0000 1.103
+++ generic/tcl.decls 19 Apr 2004 22:18:31 -0000
@@ -1860,6 +1860,56 @@
Tcl_ExitProc *Tcl_SetExitProc(Tcl_ExitProc *proc)
}
+# TIP#143 API
+declare 520 generic {
+ void Tcl_LimitAddHandler(Tcl_Interp *interp, int type,
+ Tcl_LimitHandlerProc *handlerProc, ClientData clientData,
+ Tcl_LimitHandlerDeleteProc *deleteProc)
+}
+declare 521 generic {
+ void Tcl_LimitRemoveHandler(Tcl_Interp *interp, int type,
+ Tcl_LimitHandlerProc *handlerProc, ClientData clientData)
+}
+declare 522 generic {
+ int Tcl_LimitReady(Tcl_Interp *interp)
+}
+declare 523 generic {
+ int Tcl_LimitCheck(Tcl_Interp *interp)
+}
+declare 524 generic {
+ int Tcl_LimitExceeded(Tcl_Interp *interp)
+}
+declare 525 generic {
+ void Tcl_LimitSetCommands(Tcl_Interp *interp, int commandLimit)
+}
+declare 526 generic {
+ void Tcl_LimitSetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr)
+}
+declare 527 generic {
+ void Tcl_LimitSetGranularity(Tcl_Interp *interp, int type, int granularity)
+}
+declare 528 generic {
+ int Tcl_LimitTypeEnabled(Tcl_Interp *interp, int type)
+}
+declare 529 generic {
+ int Tcl_LimitTypeExceeded(Tcl_Interp *interp, int type)
+}
+declare 530 generic {
+ void Tcl_LimitTypeSet(Tcl_Interp *interp, int type)
+}
+declare 531 generic {
+ void Tcl_LimitTypeReset(Tcl_Interp *interp, int type)
+}
+declare 532 generic {
+ int Tcl_LimitGetCommands(Tcl_Interp *interp)
+}
+declare 533 generic {
+ void Tcl_LimitGetTime(Tcl_Interp *interp, Tcl_Time *timeLimitPtr)
+}
+declare 534 generic {
+ int Tcl_LimitGetGranularity(Tcl_Interp *interp, int type)
+}
+
##############################################################################
# Define the platform specific public Tcl interface. These functions are
Index: generic/tcl.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tcl.h,v
retrieving revision 1.176
diff -u -b -B -r1.176 tcl.h
--- generic/tcl.h 31 Mar 2004 18:13:24 -0000 1.176
+++ generic/tcl.h 19 Apr 2004 22:18:31 -0000
@@ -2226,6 +2226,24 @@
} Tcl_Config;
+/*
+ * Flags for TIP#143 limits, detailing which limits are active in an
+ * interpreter. Used for Tcl_{Add,Remove}LimitHandler type argument.
+ */
+
+#define TCL_LIMIT_COMMANDS 0x01
+#define TCL_LIMIT_TIME 0x02
+
+/*
+ * Structure containing information about a limit handler to be called
+ * when a command- or time-limit is exceeded by an interpreter.
+ */
+
+typedef void (Tcl_LimitHandlerProc) _ANSI_ARGS_((ClientData clientData,
+ Tcl_Interp *interp));
+typedef void (Tcl_LimitHandlerDeleteProc) _ANSI_ARGS_((ClientData clientData));
+
+
#ifndef TCL_NO_DEPRECATED
/*
Index: generic/tclBasic.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclBasic.c,v
retrieving revision 1.99
diff -u -b -B -r1.99 tclBasic.c
--- generic/tclBasic.c 6 Apr 2004 22:25:48 -0000 1.99
+++ generic/tclBasic.c 19 Apr 2004 22:18:32 -0000
@@ -411,6 +411,11 @@
iPtr->stubTable = &tclStubs;
+ /*
+ * TIP#143: Initialise the resource limit support.
+ */
+
+ TclInitLimitSupport(interp);
/*
* Create the core commands. Do it here, rather than calling
@@ -3121,7 +3126,7 @@
*/
cmdPtr->refCount++;
iPtr->cmdCount++;
- if ( code == TCL_OK && traceCode == TCL_OK) {
+ if (code == TCL_OK && traceCode == TCL_OK && !Tcl_LimitExceeded(interp)) {
savedVarFramePtr = iPtr->varFramePtr;
if (flags & TCL_EVAL_GLOBAL) {
iPtr->varFramePtr = NULL;
@@ -3132,6 +3137,9 @@
if (Tcl_AsyncReady()) {
code = Tcl_AsyncInvoke(interp, code);
}
+ if (code == TCL_OK && Tcl_LimitReady(interp)) {
+ code = Tcl_LimitCheck(interp);
+ }
/*
* Call 'leave' command traces
@@ -3142,7 +3150,7 @@
Tcl_Obj *saveOptions = iPtr->returnOpts;
Tcl_IncrRefCount(saveOptions);
if ((cmdPtr->flags & CMD_HAS_EXEC_TRACES) && (traceCode == TCL_OK)) {
- traceCode = TclCheckExecutionTraces (interp, command, length,
+ traceCode = TclCheckExecutionTraces(interp, command, length,
cmdPtr, code, TCL_TRACE_LEAVE_EXEC, objc, objv);
}
if (iPtr->tracePtr != NULL && traceCode == TCL_OK) {
Index: generic/tclCmdAH.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclCmdAH.c,v
retrieving revision 1.44
diff -u -b -B -r1.44 tclCmdAH.c
--- generic/tclCmdAH.c 6 Apr 2004 22:25:48 -0000 1.44
+++ generic/tclCmdAH.c 19 Apr 2004 22:18:32 -0000
@@ -257,6 +257,17 @@
result = Tcl_EvalObjEx(interp, objv[1], 0);
+ /*
+ * We disable catch in interpreters where the limit has been exceeded.
+ */
+ if (Tcl_LimitExceeded(interp)) {
+ char msg[32 + TCL_INTEGER_SPACE];
+
+ sprintf(msg, "\n (\"catch\" body line %d)", interp->errorLine);
+ Tcl_AddErrorInfo(interp, msg);
+ return TCL_ERROR;
+ }
+
if (objc >= 3) {
if (NULL == Tcl_ObjSetVar2(interp, varNamePtr, NULL,
Tcl_GetObjResult(interp), 0)) {
Index: generic/tclDecls.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclDecls.h,v
retrieving revision 1.102
diff -u -b -B -r1.102 tclDecls.h
--- generic/tclDecls.h 17 Mar 2004 18:14:13 -0000 1.102
+++ generic/tclDecls.h 19 Apr 2004 22:18:32 -0000
@@ -3222,6 +3222,98 @@
/* 519 */
EXTERN Tcl_ExitProc * Tcl_SetExitProc _ANSI_ARGS_((Tcl_ExitProc * proc));
#endif
+#ifndef Tcl_LimitAddHandler_TCL_DECLARED
+#define Tcl_LimitAddHandler_TCL_DECLARED
+/* 520 */
+EXTERN void Tcl_LimitAddHandler _ANSI_ARGS_((Tcl_Interp * interp,
+ int type, Tcl_LimitHandlerProc * handlerProc,
+ ClientData clientData,
+ Tcl_LimitHandlerDeleteProc * deleteProc));
+#endif
+#ifndef Tcl_LimitRemoveHandler_TCL_DECLARED
+#define Tcl_LimitRemoveHandler_TCL_DECLARED
+/* 521 */
+EXTERN void Tcl_LimitRemoveHandler _ANSI_ARGS_((
+ Tcl_Interp * interp, int type,
+ Tcl_LimitHandlerProc * handlerProc,
+ ClientData clientData));
+#endif
+#ifndef Tcl_LimitReady_TCL_DECLARED
+#define Tcl_LimitReady_TCL_DECLARED
+/* 522 */
+EXTERN int Tcl_LimitReady _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitCheck_TCL_DECLARED
+#define Tcl_LimitCheck_TCL_DECLARED
+/* 523 */
+EXTERN int Tcl_LimitCheck _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitExceeded_TCL_DECLARED
+#define Tcl_LimitExceeded_TCL_DECLARED
+/* 524 */
+EXTERN int Tcl_LimitExceeded _ANSI_ARGS_((Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitSetCommands_TCL_DECLARED
+#define Tcl_LimitSetCommands_TCL_DECLARED
+/* 525 */
+EXTERN void Tcl_LimitSetCommands _ANSI_ARGS_((
+ Tcl_Interp * interp, int commandLimit));
+#endif
+#ifndef Tcl_LimitSetTime_TCL_DECLARED
+#define Tcl_LimitSetTime_TCL_DECLARED
+/* 526 */
+EXTERN void Tcl_LimitSetTime _ANSI_ARGS_((Tcl_Interp * interp,
+ Tcl_Time * timeLimitPtr));
+#endif
+#ifndef Tcl_LimitSetGranularity_TCL_DECLARED
+#define Tcl_LimitSetGranularity_TCL_DECLARED
+/* 527 */
+EXTERN void Tcl_LimitSetGranularity _ANSI_ARGS_((
+ Tcl_Interp * interp, int type,
+ int granularity));
+#endif
+#ifndef Tcl_LimitTypeEnabled_TCL_DECLARED
+#define Tcl_LimitTypeEnabled_TCL_DECLARED
+/* 528 */
+EXTERN int Tcl_LimitTypeEnabled _ANSI_ARGS_((
+ Tcl_Interp * interp, int type));
+#endif
+#ifndef Tcl_LimitTypeExceeded_TCL_DECLARED
+#define Tcl_LimitTypeExceeded_TCL_DECLARED
+/* 529 */
+EXTERN int Tcl_LimitTypeExceeded _ANSI_ARGS_((
+ Tcl_Interp * interp, int type));
+#endif
+#ifndef Tcl_LimitTypeSet_TCL_DECLARED
+#define Tcl_LimitTypeSet_TCL_DECLARED
+/* 530 */
+EXTERN void Tcl_LimitTypeSet _ANSI_ARGS_((Tcl_Interp * interp,
+ int type));
+#endif
+#ifndef Tcl_LimitTypeReset_TCL_DECLARED
+#define Tcl_LimitTypeReset_TCL_DECLARED
+/* 531 */
+EXTERN void Tcl_LimitTypeReset _ANSI_ARGS_((Tcl_Interp * interp,
+ int type));
+#endif
+#ifndef Tcl_LimitGetCommands_TCL_DECLARED
+#define Tcl_LimitGetCommands_TCL_DECLARED
+/* 532 */
+EXTERN int Tcl_LimitGetCommands _ANSI_ARGS_((
+ Tcl_Interp * interp));
+#endif
+#ifndef Tcl_LimitGetTime_TCL_DECLARED
+#define Tcl_LimitGetTime_TCL_DECLARED
+/* 533 */
+EXTERN void Tcl_LimitGetTime _ANSI_ARGS_((Tcl_Interp * interp,
+ Tcl_Time * timeLimitPtr));
+#endif
+#ifndef Tcl_LimitGetGranularity_TCL_DECLARED
+#define Tcl_LimitGetGranularity_TCL_DECLARED
+/* 534 */
+EXTERN int Tcl_LimitGetGranularity _ANSI_ARGS_((
+ Tcl_Interp * interp, int type));
+#endif
typedef struct TclStubHooks {
struct TclPlatStubs *tclPlatStubs;
@@ -3783,6 +3875,21 @@
void (*tcl_GetCommandFullName) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Command command, Tcl_Obj * objPtr)); /* 517 */
int (*tcl_FSEvalFileEx) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Obj * fileName, CONST char * encodingName)); /* 518 */
Tcl_ExitProc * (*tcl_SetExitProc) _ANSI_ARGS_((Tcl_ExitProc * proc)); /* 519 */
+ void (*tcl_LimitAddHandler) _ANSI_ARGS_((Tcl_Interp * interp, int type, Tcl_LimitHandlerProc * handlerProc, ClientData clientData, Tcl_LimitHandlerDeleteProc * deleteProc)); /* 520 */
+ void (*tcl_LimitRemoveHandler) _ANSI_ARGS_((Tcl_Interp * interp, int type, Tcl_LimitHandlerProc * handlerProc, ClientData clientData)); /* 521 */
+ int (*tcl_LimitReady) _ANSI_ARGS_((Tcl_Interp * interp)); /* 522 */
+ int (*tcl_LimitCheck) _ANSI_ARGS_((Tcl_Interp * interp)); /* 523 */
+ int (*tcl_LimitExceeded) _ANSI_ARGS_((Tcl_Interp * interp)); /* 524 */
+ void (*tcl_LimitSetCommands) _ANSI_ARGS_((Tcl_Interp * interp, int commandLimit)); /* 525 */
+ void (*tcl_LimitSetTime) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Time * timeLimitPtr)); /* 526 */
+ void (*tcl_LimitSetGranularity) _ANSI_ARGS_((Tcl_Interp * interp, int type, int granularity)); /* 527 */
+ int (*tcl_LimitTypeEnabled) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 528 */
+ int (*tcl_LimitTypeExceeded) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 529 */
+ void (*tcl_LimitTypeSet) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 530 */
+ void (*tcl_LimitTypeReset) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 531 */
+ int (*tcl_LimitGetCommands) _ANSI_ARGS_((Tcl_Interp * interp)); /* 532 */
+ void (*tcl_LimitGetTime) _ANSI_ARGS_((Tcl_Interp * interp, Tcl_Time * timeLimitPtr)); /* 533 */
+ int (*tcl_LimitGetGranularity) _ANSI_ARGS_((Tcl_Interp * interp, int type)); /* 534 */
} TclStubs;
#ifdef __cplusplus
@@ -5903,6 +6010,66 @@
#define Tcl_SetExitProc \
(tclStubsPtr->tcl_SetExitProc) /* 519 */
#endif
+#ifndef Tcl_LimitAddHandler
+#define Tcl_LimitAddHandler \
+ (tclStubsPtr->tcl_LimitAddHandler) /* 520 */
+#endif
+#ifndef Tcl_LimitRemoveHandler
+#define Tcl_LimitRemoveHandler \
+ (tclStubsPtr->tcl_LimitRemoveHandler) /* 521 */
+#endif
+#ifndef Tcl_LimitReady
+#define Tcl_LimitReady \
+ (tclStubsPtr->tcl_LimitReady) /* 522 */
+#endif
+#ifndef Tcl_LimitCheck
+#define Tcl_LimitCheck \
+ (tclStubsPtr->tcl_LimitCheck) /* 523 */
+#endif
+#ifndef Tcl_LimitExceeded
+#define Tcl_LimitExceeded \
+ (tclStubsPtr->tcl_LimitExceeded) /* 524 */
+#endif
+#ifndef Tcl_LimitSetCommands
+#define Tcl_LimitSetCommands \
+ (tclStubsPtr->tcl_LimitSetCommands) /* 525 */
+#endif
+#ifndef Tcl_LimitSetTime
+#define Tcl_LimitSetTime \
+ (tclStubsPtr->tcl_LimitSetTime) /* 526 */
+#endif
+#ifndef Tcl_LimitSetGranularity
+#define Tcl_LimitSetGranularity \
+ (tclStubsPtr->tcl_LimitSetGranularity) /* 527 */
+#endif
+#ifndef Tcl_LimitTypeEnabled
+#define Tcl_LimitTypeEnabled \
+ (tclStubsPtr->tcl_LimitTypeEnabled) /* 528 */
+#endif
+#ifndef Tcl_LimitTypeExceeded
+#define Tcl_LimitTypeExceeded \
+ (tclStubsPtr->tcl_LimitTypeExceeded) /* 529 */
+#endif
+#ifndef Tcl_LimitTypeSet
+#define Tcl_LimitTypeSet \
+ (tclStubsPtr->tcl_LimitTypeSet) /* 530 */
+#endif
+#ifndef Tcl_LimitTypeReset
+#define Tcl_LimitTypeReset \
+ (tclStubsPtr->tcl_LimitTypeReset) /* 531 */
+#endif
+#ifndef Tcl_LimitGetCommands
+#define Tcl_LimitGetCommands \
+ (tclStubsPtr->tcl_LimitGetCommands) /* 532 */
+#endif
+#ifndef Tcl_LimitGetTime
+#define Tcl_LimitGetTime \
+ (tclStubsPtr->tcl_LimitGetTime) /* 533 */
+#endif
+#ifndef Tcl_LimitGetGranularity
+#define Tcl_LimitGetGranularity \
+ (tclStubsPtr->tcl_LimitGetGranularity) /* 534 */
+#endif
#endif /* defined(USE_TCL_STUBS) && !defined(USE_TCL_STUB_PROCS) */
Index: generic/tclEvent.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclEvent.c,v
retrieving revision 1.32
diff -u -b -B -r1.32 tclEvent.c
--- generic/tclEvent.c 6 Apr 2004 22:25:50 -0000 1.32
+++ generic/tclEvent.c 19 Apr 2004 22:18:32 -0000
@@ -1102,6 +1102,9 @@
foundEvent = 1;
while (!done && foundEvent) {
foundEvent = Tcl_DoOneEvent(TCL_ALL_EVENTS);
+ if (Tcl_LimitExceeded(interp)) {
+ return TCL_ERROR;
+ }
}
Tcl_UntraceVar(interp, nameString,
TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
@@ -1188,7 +1191,9 @@
}
while (Tcl_DoOneEvent(flags) != 0) {
- /* Empty loop body */
+ if (Tcl_LimitExceeded(interp)) {
+ return TCL_ERROR;
+ }
}
/*
Index: generic/tclExecute.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclExecute.c,v
retrieving revision 1.124
diff -u -b -B -r1.124 tclExecute.c
--- generic/tclExecute.c 6 Apr 2004 22:25:50 -0000 1.124
+++ generic/tclExecute.c 19 Apr 2004 22:18:32 -0000
@@ -1011,21 +1011,20 @@
}
iPtr->numLevels--;
-
/*
* If no commands at all were executed, check for asynchronous
- * handlers so that they at least get one change to execute.
- * This is needed to handle event loops written in Tcl with
- * empty bodies.
+ * handlers and resource limits so that they at least get one
+ * change to execute. This is needed to handle event loops
+ * written in Tcl with empty bodies.
*/
- if ((oldCount == iPtr->cmdCount) && Tcl_AsyncReady()) {
+ if (oldCount == iPtr->cmdCount) {
+ if (Tcl_AsyncReady()) {
result = Tcl_AsyncInvoke(interp, result);
-
/*
- * If an error occurred, record information about what was being
- * executed when the error occurred.
+ * If an error occurred, record information about what was
+ * being executed when the error occurred.
*/
if ((result == TCL_ERROR) && !(iPtr->flags & ERR_ALREADY_LOGGED)) {
@@ -1033,6 +1032,20 @@
Tcl_LogCommandInfo(interp, script, script, numSrcBytes);
}
}
+ if (result==TCL_OK && Tcl_LimitReady(interp)) {
+ result = Tcl_LimitCheck(interp);
+
+ /*
+ * If an error occurred, record information about what was
+ * being executed when the error occurred.
+ */
+
+ if (result==TCL_ERROR && !(iPtr->flags & ERR_ALREADY_LOGGED)) {
+ script = Tcl_GetStringFromObj(objPtr, &numSrcBytes);
+ Tcl_LogCommandInfo(interp, script, script, numSrcBytes);
+ }
+ }
+ }
iPtr->flags &= ~ERR_ALREADY_LOGGED;
return result;
@@ -1229,7 +1242,8 @@
* of the form (2**n-1).
*/
- if (!(instructionCount++ & ASYNC_CHECK_COUNT_MASK) && Tcl_AsyncReady()) {
+ if ((instructionCount++ & ASYNC_CHECK_COUNT_MASK) == 0) {
+ if (Tcl_AsyncReady()) {
DECACHE_STACK_INFO();
result = Tcl_AsyncInvoke(interp, result);
CACHE_STACK_INFO();
@@ -1237,6 +1251,15 @@
goto checkForCatch;
}
}
+ if (Tcl_LimitReady(interp)) {
+ DECACHE_STACK_INFO();
+ result = Tcl_LimitCheck(interp);
+ CACHE_STACK_INFO();
+ if (result == TCL_ERROR) {
+ goto checkForCatch;
+ }
+ }
+ }
switch (*pc) {
case INST_START_CMD:
@@ -4549,6 +4572,20 @@
iPtr->flags |= ERR_ALREADY_LOGGED;
}
}
+ /*
+ * We must not catch an exceeded limit. Instead, it blows
+ * outwards until we either hit another interpreter (presumably
+ * where the limit is not exceeded) or we get to the top-level.
+ */
+ if (Tcl_LimitExceeded(interp)) {
+#ifdef TCL_COMPILE_DEBUG
+ if (traceInstructions) {
+ fprintf(stdout, " ... limit exceeded, returning %s\n",
+ StringForResultCode(result));
+ }
+#endif
+ goto abnormalReturn;
+ }
if (catchTop == initCatchTop) {
#ifdef TCL_COMPILE_DEBUG
if (traceInstructions) {
Index: generic/tclHistory.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclHistory.c,v
retrieving revision 1.5
diff -u -b -B -r1.5 tclHistory.c
--- generic/tclHistory.c 6 Apr 2004 22:25:51 -0000 1.5
+++ generic/tclHistory.c 19 Apr 2004 22:18:32 -0000
@@ -133,6 +133,14 @@
Tcl_DecrRefCount(objPtr);
/*
+ * One possible failure mode above: exceeding a resource limit
+ */
+
+ if (Tcl_LimitExceeded(interp)) {
+ return TCL_ERROR;
+ }
+
+ /*
* Execute the command.
*/
Index: generic/tclInt.h
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInt.h,v
retrieving revision 1.152
diff -u -b -B -r1.152 tclInt.h
--- generic/tclInt.h 7 Apr 2004 22:04:29 -0000 1.152
+++ generic/tclInt.h 19 Apr 2004 22:18:32 -0000
@@ -1126,6 +1126,12 @@
} ResolverScheme;
/*
+ * Forward declaration of the TIP#143 limit handler structure.
+ */
+
+typedef struct LimitHandler LimitHandler;
+
+/*
*----------------------------------------------------------------
* This structure defines an interpreter, which is a collection of
* commands plus other state information related to interpreting
@@ -1325,6 +1331,39 @@
Tcl_Obj *returnOptionsKey; /* holds "-options" */
/*
+ * Resource limiting framework support (TIP#143).
+ */
+
+ struct {
+ int active; /* Flag values defining which limits have
+ * been set. */
+ int granularityTicker; /* Counter used to determine how often to
+ * check the limits. */
+ int exceeded; /* Which limits have been exceeded, described
+ * as flag values the same as the 'active'
+ * field. */
+
+ int cmdCount; /* Limit for how many commands to execute
+ * in the interpreter. */
+ LimitHandler *cmdHandlers; /* Handlers to execute when the limit
+ * is reached. */
+ int cmdGranularity; /* Mod factor used to determine how often
+ * to evaluate the limit check. */
+
+ Tcl_Time time; /* Time limit for execution within the
+ * interpreter (in seconds from epoch). */
+ LimitHandler *timeHandlers; /* Handlers to execute when the limit
+ * is reached. */
+ int timeGranularity; /* Mod factor used to determine how often
+ * to evaluate the limit check. */
+
+ Tcl_HashTable callbacks; /* Mapping from (interp,type) pair to data
+ * used to install a limit handler callback
+ * to run in _this_ interp when the limit
+ * is exceeded. */
+ } limit;
+
+ /*
* Statistical information about the bytecode compiler and interpreter's
* operation.
*/
@@ -1403,6 +1442,32 @@
#define MAX_NESTING_DEPTH 1000
/*
+ * TIP#143 limit handler internal representation.
+ */
+
+struct LimitHandler {
+ int flags; /* The state of this particular handler. */
+ Tcl_LimitHandlerProc *handlerProc; /* The handler callback. */
+ ClientData clientData; /* Opaque argument to the handler callback. */
+ Tcl_LimitHandlerDeleteProc *deleteProc; /* How to delete the clientData */
+ LimitHandler *prevPtr; /* Previous item in linked list of handlers */
+ LimitHandler *nextPtr; /* Next item in linked list of handlers */
+};
+
+/*
+ * Values for the LimitHandler flags field.
+ * LIMIT_HANDLER_ACTIVE - Whether the handler is currently being
+ * processed; handlers are never to be entered reentrantly.
+ * LIMIT_HANDLER_DELETED - Whether the handler has been deleted. This
+ * should not normally be observed because when a handler is
+ * deleted it is also spliced out of the list of handlers, but
+ * even so we will be careful.
+ */
+
+#define LIMIT_HANDLER_ACTIVE 0x01
+#define LIMIT_HANDLER_DELETED 0x02
+
+/*
* The macro below is used to modify a "char" value (e.g. by casting
* it to an unsigned character) so that it can be used safely with
* macros such as isspace.
@@ -1676,6 +1741,7 @@
int len));
EXTERN int TclJoinThread _ANSI_ARGS_((Tcl_ThreadId id,
int* result));
+EXTERN void TclInitLimitSupport _ANSI_ARGS_((Tcl_Interp *interp));
EXTERN Tcl_Obj * TclLindexList _ANSI_ARGS_((Tcl_Interp* interp,
Tcl_Obj* listPtr,
Tcl_Obj* argPtr ));
Index: generic/tclInterp.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclInterp.c,v
retrieving revision 1.26
diff -u -b -B -r1.26 tclInterp.c
--- generic/tclInterp.c 6 Apr 2004 22:25:53 -0000 1.26
+++ generic/tclInterp.c 19 Apr 2004 22:18:32 -0000
@@ -145,6 +145,24 @@
} InterpInfo;
/*
+ * Limit callbacks handled by scripts are modelled as structures which
+ * are stored in hashes indexed by a two-word key. Note that the type
+ * of the 'type' field in the key is not int; this is to make sure
+ * that things work properly on 64-bit architectures.
+ */
+
+struct ScriptLimitCallback {
+ Tcl_Interp *interp;
+ Tcl_Obj *scriptObj;
+ int type;
+};
+
+struct ScriptLimitCallbackKey {
+ Tcl_Interp *interp;
+ long type;
+};
+
+/*
* Prototypes for local static procedures:
*/
@@ -196,6 +214,23 @@
static int SlaveRecursionLimit _ANSI_ARGS_((Tcl_Interp *interp,
Tcl_Interp *slaveInterp, int objc,
Tcl_Obj *CONST objv[]));
+static int SlaveCommandLimit _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Interp *slaveInterp, int consumedObjc,
+ int objc, Tcl_Obj *CONST objv[]));
+static int SlaveTimeLimit _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Interp *slaveInterp, int consumedObjc,
+ int objc, Tcl_Obj *CONST objv[]));
+static void InheritLimits _ANSI_ARGS_((Tcl_Interp *slaveInterp,
+ Tcl_Interp *masterInterp));
+static void SetLimitCallback _ANSI_ARGS_((Tcl_Interp *interp,
+ int type, Tcl_Interp *targetInterp,
+ Tcl_Obj *scriptObj));
+static void CallScriptLimitCallback _ANSI_ARGS_((
+ ClientData clientData, Tcl_Interp *interp));
+static void DeleteScriptLimitCallback _ANSI_ARGS_((
+ ClientData clientData));
+static void RunLimitHandlers _ANSI_ARGS_((LimitHandler *handlerPtr,
+ Tcl_Interp *interp));
/*
@@ -357,16 +392,16 @@
static CONST char *options[] = {
"alias", "aliases", "create", "delete",
"eval", "exists", "expose", "hide",
- "hidden", "issafe", "invokehidden", "marktrusted",
- "recursionlimit", "slaves", "share",
+ "hidden", "issafe", "invokehidden", "limit",
+ "marktrusted", "recursionlimit","slaves", "share",
"target", "transfer",
NULL
};
enum option {
OPT_ALIAS, OPT_ALIASES, OPT_CREATE, OPT_DELETE,
OPT_EVAL, OPT_EXISTS, OPT_EXPOSE, OPT_HIDE,
- OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHID, OPT_MARKTRUSTED,
- OPT_RECLIMIT, OPT_SLAVES, OPT_SHARE,
+ OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHID, OPT_LIMIT,
+ OPT_MARKTRUSTED,OPT_RECLIMIT, OPT_SLAVES, OPT_SHARE,
OPT_TARGET, OPT_TRANSFER
};
@@ -628,6 +663,35 @@
return SlaveInvokeHidden(interp, slaveInterp, global, objc - i,
objv + i);
}
+ case OPT_LIMIT: {
+ Tcl_Interp *slaveInterp;
+ static CONST char *limitTypes[] = {
+ "commands", "time", NULL
+ };
+ enum LimitTypes {
+ LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME
+ };
+ int limitType;
+
+ if (objc < 4) {
+ Tcl_WrongNumArgs(interp, 2, objv, "path limitType ?options?");
+ return TCL_ERROR;
+ }
+ slaveInterp = GetInterp(interp, objv[2]);
+ if (slaveInterp == NULL) {
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[3], limitTypes, "limit type",
+ 0, &limitType) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum LimitTypes) limitType) {
+ case LIMIT_TYPE_COMMANDS:
+ return SlaveCommandLimit(interp, slaveInterp, 4, objc, objv);
+ case LIMIT_TYPE_TIME:
+ return SlaveTimeLimit(interp, slaveInterp, 4, objc, objv);
+ }
+ }
case OPT_MARKTRUSTED: {
Tcl_Interp *slaveInterp;
@@ -1838,6 +1902,12 @@
*/
Tcl_InitMemory(slaveInterp);
}
+
+ /*
+ * Inherit the TIP#143 limits.
+ */
+ InheritLimits(slaveInterp, masterInterp);
+
return slaveInterp;
error:
@@ -1876,12 +1946,12 @@
static CONST char *options[] = {
"alias", "aliases", "eval", "expose",
"hide", "hidden", "issafe", "invokehidden",
- "marktrusted", "recursionlimit", NULL
+ "limit", "marktrusted", "recursionlimit", NULL
};
enum options {
OPT_ALIAS, OPT_ALIASES, OPT_EVAL, OPT_EXPOSE,
OPT_HIDE, OPT_HIDDEN, OPT_ISSAFE, OPT_INVOKEHIDDEN,
- OPT_MARKTRUSTED, OPT_RECLIMIT
+ OPT_LIMIT, OPT_MARKTRUSTED, OPT_RECLIMIT
};
slaveInterp = (Tcl_Interp *) clientData;
@@ -1992,6 +2062,30 @@
return SlaveInvokeHidden(interp, slaveInterp, global, objc - i,
objv + i);
}
+ case OPT_LIMIT: {
+ static CONST char *limitTypes[] = {
+ "commands", "time", NULL
+ };
+ enum LimitTypes {
+ LIMIT_TYPE_COMMANDS, LIMIT_TYPE_TIME
+ };
+ int limitType;
+
+ if (objc < 3) {
+ Tcl_WrongNumArgs(interp, 2, objv, "limitType ?options?");
+ return TCL_ERROR;
+ }
+ if (Tcl_GetIndexFromObj(interp, objv[2], limitTypes, "limit type",
+ 0, &limitType) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum LimitTypes) limitType) {
+ case LIMIT_TYPE_COMMANDS:
+ return SlaveCommandLimit(interp, slaveInterp, 3, objc, objv);
+ case LIMIT_TYPE_TIME:
+ return SlaveTimeLimit(interp, slaveInterp, 3, objc, objv);
+ }
+ }
case OPT_MARKTRUSTED: {
if (objc != 2) {
Tcl_WrongNumArgs(interp, 2, objv, NULL);
@@ -2486,3 +2580,918 @@
return TCL_OK;
}
+
+int
+Tcl_LimitExceeded(interp)
+ Tcl_Interp *interp;
+{
+ register Interp *iPtr = (Interp *) interp;
+
+ return iPtr->limit.exceeded != 0;
+}
+
+int
+Tcl_LimitReady(interp)
+ Tcl_Interp *interp;
+{
+ register Interp *iPtr = (Interp *) interp;
+
+ if (iPtr->limit.active != 0) {
+ register int ticker = ++iPtr->limit.granularityTicker;
+
+ if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) &&
+ ((iPtr->limit.cmdGranularity == 1) ||
+ (ticker % iPtr->limit.cmdGranularity == 0))) {
+ return 1;
+ }
+ if ((iPtr->limit.active & TCL_LIMIT_TIME) &&
+ ((iPtr->limit.timeGranularity == 1) ||
+ (ticker % iPtr->limit.timeGranularity == 0))) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+int
+Tcl_LimitCheck(interp)
+ Tcl_Interp *interp;
+{
+ Interp *iPtr = (Interp *) interp;
+ register int ticker = iPtr->limit.granularityTicker;
+
+ if (Tcl_InterpDeleted(interp)) {
+ return TCL_OK;
+ }
+
+ if ((iPtr->limit.active & TCL_LIMIT_COMMANDS) &&
+ ((iPtr->limit.cmdGranularity == 1) ||
+ (ticker % iPtr->limit.cmdGranularity == 0)) &&
+ (iPtr->limit.cmdCount < iPtr->cmdCount)) {
+ iPtr->limit.exceeded |= TCL_LIMIT_COMMANDS;
+ Tcl_Preserve(interp);
+ RunLimitHandlers(iPtr->limit.cmdHandlers, interp);
+ if (iPtr->limit.cmdCount >= iPtr->cmdCount) {
+ iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+ } else {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "command count limit exceeded", NULL);
+ Tcl_Release(interp);
+ return TCL_ERROR;
+ }
+ }
+
+ if ((iPtr->limit.active & TCL_LIMIT_TIME) &&
+ ((iPtr->limit.timeGranularity == 1) ||
+ (ticker % iPtr->limit.timeGranularity == 0))) {
+ Tcl_Time now;
+
+ Tcl_GetTime(&now);
+ if (iPtr->limit.time.sec < now.sec ||
+ (iPtr->limit.time.sec == now.sec &&
+ iPtr->limit.time.usec < now.usec)) {
+ iPtr->limit.exceeded |= TCL_LIMIT_TIME;
+ Tcl_Preserve(interp);
+ RunLimitHandlers(iPtr->limit.timeHandlers, interp);
+ if (iPtr->limit.time.sec < now.sec ||
+ (iPtr->limit.time.sec == now.sec &&
+ iPtr->limit.time.usec < now.usec)) {
+ iPtr->limit.exceeded &= ~TCL_LIMIT_TIME;
+ } else {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "time limit exceeded", NULL);
+ Tcl_Release(interp);
+ return TCL_ERROR;
+ }
+ }
+ }
+
+ return TCL_OK;
+}
+
+static void
+RunLimitHandlers(handlerPtr, interp)
+ LimitHandler *handlerPtr;
+ Tcl_Interp *interp;
+{
+ LimitHandler *nextPtr;
+ for (; handlerPtr!=NULL ; handlerPtr=nextPtr) {
+ if (handlerPtr->flags & (LIMIT_HANDLER_DELETED|LIMIT_HANDLER_ACTIVE)) {
+ /*
+ * Reentrant call or something seriously strange in the
+ * delete code.
+ */
+ nextPtr = handlerPtr->nextPtr;
+ continue;
+ }
+
+ /*
+ * Set the ACTIVE flag while running the limit handler itself
+ * so we cannot reentrantly call this handler and know to use
+ * the alternate method of deletion if necessary.
+ */
+
+ handlerPtr->flags |= LIMIT_HANDLER_ACTIVE;
+ (handlerPtr->handlerProc)(handlerPtr->clientData, interp);
+ handlerPtr->flags &= ~LIMIT_HANDLER_ACTIVE;
+
+ /*
+ * Rediscover this value; it might have changed during the
+ * processing of a limit handler. We have to record it here
+ * because we might delete the structure below, and reading a
+ * value out of a deleted structure is unsafe (even if
+ * actually legal with some malloc()/free() implementations.)
+ */
+
+ nextPtr = handlerPtr->nextPtr;
+
+ /*
+ * If we deleted the current handler while we were executing
+ * it, we will have spliced it out of the list and set the
+ * LIMIT_HANDLER_DELETED flag.
+ */
+ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) {
+ if (handlerPtr->deleteProc != NULL) {
+ (handlerPtr->deleteProc)(handlerPtr->clientData);
+ }
+ ckfree((char *) handlerPtr);
+ }
+ }
+}
+
+void
+Tcl_LimitAddHandler(interp, type, handlerProc, clientData, deleteProc)
+ Tcl_Interp *interp;
+ int type;
+ Tcl_LimitHandlerProc *handlerProc;
+ ClientData clientData;
+ Tcl_LimitHandlerDeleteProc *deleteProc;
+{
+ Interp *iPtr = (Interp *) interp;
+ LimitHandler *handlerPtr;
+
+ if (deleteProc == (Tcl_LimitHandlerDeleteProc *) TCL_DYNAMIC) {
+ deleteProc = (Tcl_LimitHandlerDeleteProc *) Tcl_Alloc;
+ }
+ if (deleteProc == (Tcl_LimitHandlerDeleteProc *) TCL_STATIC) {
+ deleteProc = (Tcl_LimitHandlerDeleteProc *) NULL;
+ }
+
+ switch (type) {
+ case TCL_LIMIT_COMMANDS:
+ handlerPtr = (LimitHandler *) ckalloc(sizeof(LimitHandler));
+
+ handlerPtr->flags = 0;
+ handlerPtr->handlerProc = handlerProc;
+ handlerPtr->clientData = clientData;
+ handlerPtr->deleteProc = deleteProc;
+ handlerPtr->prevPtr = NULL;
+ handlerPtr->nextPtr = iPtr->limit.cmdHandlers;
+ if (handlerPtr->nextPtr != NULL) {
+ handlerPtr->nextPtr->prevPtr = handlerPtr;
+ }
+ iPtr->limit.cmdHandlers = handlerPtr;
+ return;
+
+ case TCL_LIMIT_TIME:
+ handlerPtr = (LimitHandler *) ckalloc(sizeof(LimitHandler));
+
+ handlerPtr->flags = 0;
+ handlerPtr->handlerProc = handlerProc;
+ handlerPtr->clientData = clientData;
+ handlerPtr->deleteProc = deleteProc;
+ handlerPtr->prevPtr = NULL;
+ handlerPtr->nextPtr = iPtr->limit.timeHandlers;
+ if (handlerPtr->nextPtr != NULL) {
+ handlerPtr->nextPtr->prevPtr = handlerPtr;
+ }
+ iPtr->limit.timeHandlers = handlerPtr;
+ return;
+ }
+
+ Tcl_Panic("unknown type of resource limit");
+}
+
+void
+Tcl_LimitRemoveHandler(interp, type, handlerProc, clientData)
+ Tcl_Interp *interp;
+ int type;
+ Tcl_LimitHandlerProc *handlerProc;
+ ClientData clientData;
+{
+ Interp *iPtr = (Interp *) interp;
+ LimitHandler *handlerPtr;
+
+ switch (type) {
+ case TCL_LIMIT_COMMANDS:
+ handlerPtr = iPtr->limit.cmdHandlers;
+ break;
+ case TCL_LIMIT_TIME:
+ handlerPtr = iPtr->limit.timeHandlers;
+ break;
+ default:
+ Tcl_Panic("unknown type of resource limit");
+ }
+
+ for (; handlerPtr!=NULL ; handlerPtr=handlerPtr->nextPtr) {
+ if ((handlerPtr->handlerProc != handlerProc) ||
+ (handlerPtr->clientData != clientData)) {
+ continue;
+ }
+
+ /*
+ * We've found the handler to delete; mark it as doomed if not
+ * already so marked (which shouldn't actually happen).
+ */
+
+ if (handlerPtr->flags & LIMIT_HANDLER_DELETED) {
+ return;
+ }
+ handlerPtr->flags |= LIMIT_HANDLER_DELETED;
+
+ /*
+ * Splice the handler out of the doubly-linked list.
+ */
+
+ if (handlerPtr->prevPtr == NULL) {
+ switch (type) {
+ case TCL_LIMIT_COMMANDS:
+ iPtr->limit.cmdHandlers = handlerPtr->nextPtr;
+ break;
+ case TCL_LIMIT_TIME:
+ iPtr->limit.timeHandlers = handlerPtr->nextPtr;
+ break;
+ }
+ } else {
+ handlerPtr->prevPtr->nextPtr = handlerPtr->nextPtr;
+ }
+ if (handlerPtr->nextPtr != NULL) {
+ handlerPtr->nextPtr->prevPtr = handlerPtr->prevPtr;
+ }
+
+ /*
+ * If nothing is currently executing the handler, delete its
+ * client data and the overall handler structure now.
+ * Otherwise it will all go away when the handler returns.
+ */
+
+ if (!(handlerPtr->flags & LIMIT_HANDLER_ACTIVE)) {
+ if (handlerPtr->deleteProc != NULL) {
+ (handlerPtr->deleteProc)(handlerPtr->clientData);
+ }
+ ckfree((char *) handlerPtr);
+ }
+ return;
+ }
+}
+
+int
+Tcl_LimitTypeEnabled(interp, type)
+ Tcl_Interp *interp;
+ int type;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ return (iPtr->limit.active & type) != 0;
+}
+
+int
+Tcl_LimitTypeExceeded(interp, type)
+ Tcl_Interp *interp;
+ int type;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ return (iPtr->limit.exceeded & type) != 0;
+}
+
+void
+Tcl_LimitTypeSet(interp, type)
+ Tcl_Interp *interp;
+ int type;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ iPtr->limit.active |= type;
+}
+
+void
+Tcl_LimitTypeReset(interp, type)
+ Tcl_Interp *interp;
+ int type;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ iPtr->limit.active &= ~type;
+}
+
+void
+Tcl_LimitSetCommands(interp, commandLimit)
+ Tcl_Interp *interp;
+ int commandLimit;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ iPtr->limit.cmdCount = commandLimit;
+ iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+}
+
+int
+Tcl_LimitGetCommands(interp)
+ Tcl_Interp *interp;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ return iPtr->limit.cmdCount;
+}
+
+void
+Tcl_LimitSetTime(interp, timeLimitPtr)
+ Tcl_Interp *interp;
+ Tcl_Time *timeLimitPtr;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ memcpy(&iPtr->limit.time, timeLimitPtr, sizeof(Tcl_Time));
+ iPtr->limit.exceeded &= ~TCL_LIMIT_COMMANDS;
+}
+
+void
+Tcl_LimitGetTime(interp, timeLimitPtr)
+ Tcl_Interp *interp;
+ Tcl_Time *timeLimitPtr;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ memcpy(timeLimitPtr, &iPtr->limit.time, sizeof(Tcl_Time));
+}
+
+void
+Tcl_LimitSetGranularity(interp, type, granularity)
+ Tcl_Interp *interp;
+ int type;
+ int granularity;
+{
+ Interp *iPtr = (Interp *) interp;
+ if (granularity < 1) {
+ Tcl_Panic("limit granularity must be positive");
+ }
+
+ switch (type) {
+ case TCL_LIMIT_COMMANDS:
+ iPtr->limit.cmdGranularity = granularity;
+ return;
+ case TCL_LIMIT_TIME:
+ iPtr->limit.timeGranularity = granularity;
+ return;
+ }
+ Tcl_Panic("unknown type of resource limit");
+}
+
+int
+Tcl_LimitGetGranularity(interp, type)
+ Tcl_Interp *interp;
+ int type;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ switch (type) {
+ case TCL_LIMIT_COMMANDS:
+ return iPtr->limit.cmdGranularity;
+ case TCL_LIMIT_TIME:
+ return iPtr->limit.timeGranularity;
+ }
+ Tcl_Panic("unknown type of resource limit");
+ return -1; /* NOT REACHED */
+}
+
+/*
+ * Callback for when a script limit is deleted.
+ */
+static void
+DeleteScriptLimitCallback(clientData)
+ ClientData clientData;
+{
+ struct ScriptLimitCallback *limitCBPtr =
+ (struct ScriptLimitCallback *) clientData;
+
+ Tcl_DecrRefCount(limitCBPtr->scriptObj);
+ ckfree((char *) limitCBPtr);
+}
+
+/*
+ * Callback for when a script limit is invoked.
+ */
+static void
+CallScriptLimitCallback(clientData, interp)
+ ClientData clientData;
+ Tcl_Interp *interp; /* Interpreter which failed the limit */
+{
+ struct ScriptLimitCallback *limitCBPtr =
+ (struct ScriptLimitCallback *) clientData;
+ int code;
+
+ if (Tcl_InterpDeleted(limitCBPtr->interp)) {
+ return;
+ }
+ Tcl_Preserve(limitCBPtr->interp);
+ code = Tcl_EvalObjEx(limitCBPtr->interp, limitCBPtr->scriptObj,
+ TCL_EVAL_GLOBAL);
+ if (code != TCL_OK && !Tcl_InterpDeleted(limitCBPtr->interp)) {
+ Tcl_BackgroundError(limitCBPtr->interp);
+ }
+ Tcl_Release(limitCBPtr->interp);
+}
+
+/*
+ * Install (or remove, if scriptObj is NULL) a limit callback script
+ * that is called when the target interpreter exceeds the type of
+ * limit specified.
+ */
+static void
+SetLimitCallback(interp, type, targetInterp, scriptObj)
+ Tcl_Interp *interp;
+ int type;
+ Tcl_Interp *targetInterp;
+ Tcl_Obj *scriptObj;
+{
+ struct ScriptLimitCallback *limitCBPtr;
+ Tcl_HashEntry *hashPtr;
+ int isNew;
+ struct ScriptLimitCallbackKey key;
+ Interp *iPtr = (Interp *) interp;
+
+ if (interp == targetInterp) {
+ Tcl_Panic("installing limit callback to the limited interpreter");
+ }
+
+ key.interp = targetInterp;
+ key.type = type;
+
+ if (scriptObj == NULL) {
+ hashPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+ if (hashPtr != NULL) {
+ Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback,
+ Tcl_GetHashValue(hashPtr));
+ Tcl_DeleteHashEntry(hashPtr);
+ }
+ return;
+ }
+
+ limitCBPtr = (struct ScriptLimitCallback *)
+ ckalloc(sizeof(struct ScriptLimitCallback));
+ limitCBPtr->interp = interp;
+ limitCBPtr->scriptObj = scriptObj;
+ limitCBPtr->type = type;
+ Tcl_IncrRefCount(scriptObj);
+
+ hashPtr = Tcl_CreateHashEntry(&iPtr->limit.callbacks, (char *) &key,
+ &isNew);
+ if (!isNew) {
+ Tcl_LimitRemoveHandler(targetInterp, type, CallScriptLimitCallback,
+ Tcl_GetHashValue(hashPtr));
+ }
+ Tcl_LimitAddHandler(targetInterp, type, CallScriptLimitCallback,
+ (ClientData) limitCBPtr, DeleteScriptLimitCallback);
+ Tcl_SetHashValue(hashPtr, (ClientData) limitCBPtr);
+}
+
+/*
+ * Remove all limit callback scripts that make callbacks into the
+ * given interpreter.
+ */
+
+void
+TclDecommissionLimitCallbacks(interp)
+ Tcl_Interp *interp;
+{
+ Interp *iPtr = (Interp *) interp;
+ Tcl_HashEntry *hashPtr;
+ Tcl_HashSearch search;
+ struct ScriptLimitCallbackKey *keyPtr;
+
+ hashPtr = Tcl_FirstHashEntry(&iPtr->limit.callbacks, &search);
+ while (hashPtr != NULL) {
+ keyPtr = (struct ScriptLimitCallbackKey *)
+ Tcl_GetHashKey(&iPtr->limit.callbacks, hashPtr);
+ Tcl_LimitRemoveHandler(keyPtr->interp, keyPtr->type,
+ CallScriptLimitCallback, Tcl_GetHashValue(hashPtr));
+ hashPtr = Tcl_NextHashEntry(&search);
+ }
+ Tcl_DeleteHashTable(&iPtr->limit.callbacks);
+}
+
+void
+TclInitLimitSupport(interp)
+ Tcl_Interp *interp;
+{
+ Interp *iPtr = (Interp *) interp;
+
+ iPtr->limit.active = 0;
+ iPtr->limit.granularityTicker = 0;
+ iPtr->limit.exceeded = 0;
+ iPtr->limit.cmdCount = 0;
+ iPtr->limit.cmdHandlers = NULL;
+ iPtr->limit.cmdGranularity = 1;
+ memset(&iPtr->limit.time, 0, sizeof(Tcl_Time));
+ iPtr->limit.timeHandlers = NULL;
+ iPtr->limit.timeGranularity = 10;
+ Tcl_InitHashTable(&iPtr->limit.callbacks,
+ sizeof(struct ScriptLimitCallbackKey)/sizeof(ClientData));
+}
+
+static void
+InheritLimits(slaveInterp, masterInterp)
+ Tcl_Interp *slaveInterp, *masterInterp;
+{
+ Interp *slavePtr = (Interp *) slaveInterp;
+ Interp *masterPtr = (Interp *) masterInterp;
+
+ if (masterPtr->limit.active & TCL_LIMIT_COMMANDS) {
+ slavePtr->limit.active |= TCL_LIMIT_COMMANDS;
+ slavePtr->limit.cmdCount = 0;
+ slavePtr->limit.cmdGranularity = masterPtr->limit.cmdGranularity;
+ }
+ if (masterPtr->limit.active & TCL_LIMIT_TIME) {
+ slavePtr->limit.active |= TCL_LIMIT_TIME;
+ memcpy(&slavePtr->limit.time, &masterPtr->limit.time,
+ sizeof(Tcl_Time));
+ slavePtr->limit.timeGranularity = masterPtr->limit.timeGranularity;
+ }
+}
+
+static int
+SlaveCommandLimit(interp, slaveInterp, consumedObjc, objc, objv)
+ Tcl_Interp *interp; /* Current interpreter. */
+ Tcl_Interp *slaveInterp; /* Interpreter being adjusted. */
+ int consumedObjc; /* Number of args already parsed. */
+ int objc; /* Total number of arguments. */
+ Tcl_Obj *CONST objv[]; /* Argument objects. */
+{
+ static CONST char *options[] = {
+ "-command", "-granularity", "-value", NULL
+ };
+ enum Options {
+ OPT_CMD, OPT_GRAN, OPT_VAL
+ };
+ Interp *iPtr = (Interp *) interp;
+ int index;
+ struct ScriptLimitCallbackKey key;
+ struct ScriptLimitCallback *limitCBPtr;
+ Tcl_HashEntry *hPtr;
+
+ if (objc == consumedObjc) {
+ Tcl_Obj *dictPtr;
+
+ TclNewObj(dictPtr);
+ key.interp = slaveInterp;
+ key.type = TCL_LIMIT_COMMANDS;
+ hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+ if (hPtr != NULL) {
+ limitCBPtr = (struct ScriptLimitCallback *)
+ Tcl_GetHashValue(hPtr);
+ if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[0], -1),
+ limitCBPtr->scriptObj);
+ } else {
+ goto putEmptyCommandInDict;
+ }
+ } else {
+ Tcl_Obj *empty;
+ putEmptyCommandInDict:
+ TclNewObj(empty);
+ Tcl_DictObjPut(NULL, dictPtr,
+ Tcl_NewStringObj(options[0], -1), empty);
+ }
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[1], -1),
+ Tcl_NewIntObj(Tcl_LimitGetGranularity(slaveInterp,
+ TCL_LIMIT_COMMANDS)));
+
+ if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_COMMANDS)) {
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[2], -1),
+ Tcl_NewIntObj(Tcl_LimitGetCommands(slaveInterp)));
+ } else {
+ Tcl_Obj *empty;
+
+ TclNewObj(empty);
+ Tcl_DictObjPut(NULL, dictPtr,
+ Tcl_NewStringObj(options[2], -1), empty);
+ }
+ Tcl_SetObjResult(interp, dictPtr);
+ return TCL_OK;
+ } else if (objc == consumedObjc+1) {
+ if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option",
+ 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum Options) index) {
+ case OPT_CMD:
+ key.interp = slaveInterp;
+ key.type = TCL_LIMIT_COMMANDS;
+ hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+ if (hPtr != NULL) {
+ limitCBPtr = (struct ScriptLimitCallback *)
+ Tcl_GetHashValue(hPtr);
+ if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+ Tcl_SetObjResult(interp, limitCBPtr->scriptObj);
+ }
+ }
+ break;
+ case OPT_GRAN:
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(
+ Tcl_LimitGetGranularity(slaveInterp, TCL_LIMIT_COMMANDS)));
+ break;
+ case OPT_VAL:
+ if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_COMMANDS)) {
+ Tcl_SetObjResult(interp,
+ Tcl_NewIntObj(Tcl_LimitGetCommands(slaveInterp)));
+ }
+ break;
+ }
+ return TCL_OK;
+ } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) {
+ Tcl_WrongNumArgs(interp, consumedObjc, objv,
+ "?-option? ?value? ?-option value ...?");
+ return TCL_ERROR;
+ } else {
+ int i, scriptLen = 0, limitLen = 0;
+ Tcl_Obj *scriptObj = NULL, *granObj = NULL, *limitObj = NULL;
+ int gran = 0, limit = 0;
+
+ for (i=consumedObjc ; i<objc ; i+=2) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
+ &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum Options) index) {
+ case OPT_CMD:
+ scriptObj = objv[i+1];
+ (void) Tcl_GetStringFromObj(objv[i+1], &scriptLen);
+ break;
+ case OPT_GRAN:
+ granObj = objv[i+1];
+ if (Tcl_GetIntFromObj(interp, objv[i+1], &gran) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (gran < 1) {
+ Tcl_AppendResult(interp, "granularity must be at ",
+ "least 1", NULL);
+ return TCL_ERROR;
+ }
+ break;
+ case OPT_VAL:
+ limitObj = objv[i+1];
+ (void) Tcl_GetStringFromObj(objv[i+1], &limitLen);
+ if (limitLen == 0) {
+ break;
+ }
+ if (Tcl_GetIntFromObj(interp, objv[i+1], &limit) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (limit < 0) {
+ Tcl_AppendResult(interp, "command limit value must be at ",
+ "least 0", NULL);
+ return TCL_ERROR;
+ }
+ break;
+ }
+ }
+ if (scriptObj != NULL) {
+ SetLimitCallback(interp, TCL_LIMIT_COMMANDS, slaveInterp,
+ (scriptLen > 0 ? scriptObj : NULL));
+ }
+ if (granObj != NULL) {
+ Tcl_LimitSetGranularity(slaveInterp, TCL_LIMIT_COMMANDS, gran);
+ }
+ if (limitObj != NULL) {
+ if (limitLen > 0) {
+ Tcl_LimitSetCommands(slaveInterp, limit);
+ Tcl_LimitTypeSet(slaveInterp, TCL_LIMIT_COMMANDS);
+ } else {
+ Tcl_LimitTypeReset(slaveInterp, TCL_LIMIT_COMMANDS);
+ }
+ }
+ return TCL_OK;
+ }
+}
+
+static int
+SlaveTimeLimit(interp, slaveInterp, consumedObjc, objc, objv)
+ Tcl_Interp *interp; /* Current interpreter. */
+ Tcl_Interp *slaveInterp; /* Interpreter being adjusted. */
+ int consumedObjc; /* Number of args already parsed. */
+ int objc; /* Total number of arguments. */
+ Tcl_Obj *CONST objv[]; /* Argument objects. */
+{
+ static CONST char *options[] = {
+ "-command", "-granularity", "-milliseconds", "-seconds", NULL
+ };
+ enum Options {
+ OPT_CMD, OPT_GRAN, OPT_MILLI, OPT_SEC
+ };
+ Interp *iPtr = (Interp *) interp;
+ int index;
+ struct ScriptLimitCallbackKey key;
+ struct ScriptLimitCallback *limitCBPtr;
+ Tcl_HashEntry *hPtr;
+
+ if (objc == consumedObjc) {
+ Tcl_Obj *dictPtr;
+
+ TclNewObj(dictPtr);
+ key.interp = slaveInterp;
+ key.type = TCL_LIMIT_TIME;
+ hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+ if (hPtr != NULL) {
+ limitCBPtr = (struct ScriptLimitCallback *)
+ Tcl_GetHashValue(hPtr);
+ if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[0], -1),
+ limitCBPtr->scriptObj);
+ } else {
+ goto putEmptyCommandInDict;
+ }
+ } else {
+ Tcl_Obj *empty;
+ putEmptyCommandInDict:
+ TclNewObj(empty);
+ Tcl_DictObjPut(NULL, dictPtr,
+ Tcl_NewStringObj(options[0], -1), empty);
+ }
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[1], -1),
+ Tcl_NewIntObj(Tcl_LimitGetGranularity(slaveInterp,
+ TCL_LIMIT_TIME)));
+
+ if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+ Tcl_Time limitMoment;
+
+ Tcl_LimitGetTime(slaveInterp, &limitMoment);
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[2], -1),
+ Tcl_NewLongObj(limitMoment.usec/1000));
+ Tcl_DictObjPut(NULL, dictPtr, Tcl_NewStringObj(options[3], -1),
+ Tcl_NewLongObj(limitMoment.sec));
+ } else {
+ Tcl_Obj *empty;
+
+ TclNewObj(empty);
+ Tcl_DictObjPut(NULL, dictPtr,
+ Tcl_NewStringObj(options[2], -1), empty);
+ Tcl_DictObjPut(NULL, dictPtr,
+ Tcl_NewStringObj(options[3], -1), empty);
+ }
+ Tcl_SetObjResult(interp, dictPtr);
+ return TCL_OK;
+ } else if (objc == consumedObjc+1) {
+ if (Tcl_GetIndexFromObj(interp, objv[consumedObjc], options, "option",
+ 0, &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum Options) index) {
+ case OPT_CMD:
+ key.interp = slaveInterp;
+ key.type = TCL_LIMIT_TIME;
+ hPtr = Tcl_FindHashEntry(&iPtr->limit.callbacks, (char *) &key);
+ if (hPtr != NULL) {
+ limitCBPtr = (struct ScriptLimitCallback *)
+ Tcl_GetHashValue(hPtr);
+ if (limitCBPtr != NULL && limitCBPtr->scriptObj != NULL) {
+ Tcl_SetObjResult(interp, limitCBPtr->scriptObj);
+ }
+ }
+ break;
+ case OPT_GRAN:
+ Tcl_SetObjResult(interp, Tcl_NewIntObj(
+ Tcl_LimitGetGranularity(slaveInterp, TCL_LIMIT_TIME)));
+ break;
+ case OPT_MILLI:
+ if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+ Tcl_Time limitMoment;
+
+ Tcl_LimitGetTime(slaveInterp, &limitMoment);
+ Tcl_SetObjResult(interp,
+ Tcl_NewLongObj(limitMoment.usec/1000));
+ }
+ break;
+ case OPT_SEC:
+ if (Tcl_LimitTypeEnabled(slaveInterp, TCL_LIMIT_TIME)) {
+ Tcl_Time limitMoment;
+
+ Tcl_LimitGetTime(slaveInterp, &limitMoment);
+ Tcl_SetObjResult(interp, Tcl_NewLongObj(limitMoment.sec));
+ }
+ break;
+ }
+ return TCL_OK;
+ } else if ((objc-consumedObjc) & 1 /* isOdd(objc-consumedObjc) */) {
+ Tcl_WrongNumArgs(interp, consumedObjc, objv,
+ "?-option? ?value? ?-option value ...?");
+ return TCL_ERROR;
+ } else {
+ int i, scriptLen = 0, milliLen = 0, secLen = 0;
+ Tcl_Obj *scriptObj = NULL, *granObj = NULL;
+ Tcl_Obj *milliObj = NULL, *secObj = NULL;
+ int gran = 0;
+ Tcl_Time limitMoment;
+ int tmp;
+
+ Tcl_LimitGetTime(slaveInterp, &limitMoment);
+ for (i=consumedObjc ; i<objc ; i+=2) {
+ if (Tcl_GetIndexFromObj(interp, objv[i], options, "option", 0,
+ &index) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ switch ((enum Options) index) {
+ case OPT_CMD:
+ scriptObj = objv[i+1];
+ (void) Tcl_GetStringFromObj(objv[i+1], &scriptLen);
+ break;
+ case OPT_GRAN:
+ granObj = objv[i+1];
+ if (Tcl_GetIntFromObj(interp, objv[i+1], &gran) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (gran < 1) {
+ Tcl_AppendResult(interp, "granularity must be at ",
+ "least 1", NULL);
+ return TCL_ERROR;
+ }
+ break;
+ case OPT_MILLI:
+ milliObj = objv[i+1];
+ (void) Tcl_GetStringFromObj(objv[i+1], &milliLen);
+ if (milliLen == 0) {
+ break;
+ }
+ if (Tcl_GetIntFromObj(interp, objv[i+1], &tmp) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (tmp < 0) {
+ Tcl_AppendResult(interp, "milliseconds must be at least 0",
+ NULL);
+ return TCL_ERROR;
+ }
+ limitMoment.usec = ((long)tmp)*1000;
+ break;
+ case OPT_SEC:
+ secObj = objv[i+1];
+ (void) Tcl_GetStringFromObj(objv[i+1], &secLen);
+ if (secLen == 0) {
+ break;
+ }
+ if (Tcl_GetIntFromObj(interp, objv[i+1], &tmp) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ if (tmp < 0) {
+ Tcl_AppendResult(interp, "seconds must be at least 0",
+ NULL);
+ return TCL_ERROR;
+ }
+ limitMoment.sec = tmp;
+ break;
+ }
+ }
+ if (milliObj != NULL || secObj != NULL) {
+ if (milliObj != NULL) {
+ /*
+ * Setting -milliseconds but clearing -seconds, or
+ * resetting -milliseconds but not resetting -seconds?
+ * Bad voodoo!
+ */
+ if (secObj != NULL && secLen == 0 && milliLen > 0) {
+ Tcl_AppendResult(interp, "may only set -milliseconds ",
+ "if -seconds is not also being reset", NULL);
+ return TCL_ERROR;
+ }
+ if (milliLen == 0 && (secObj == NULL || secLen > 0)) {
+ Tcl_AppendResult(interp, "may only reset -milliseconds ",
+ "if -seconds is also being reset", NULL);
+ return TCL_ERROR;
+ }
+ }
+
+ if (milliLen > 0 || secLen > 0) {
+ /*
+ * Force usec to be in range [0..1000000), possibly
+ * incrementing sec in the process. This makes it
+ * much easier for people to write scripts that do
+ * small time increments.
+ */
+ limitMoment.sec += limitMoment.usec / 1000000;
+ limitMoment.usec %= 1000000;
+
+ Tcl_LimitSetTime(slaveInterp, &limitMoment);
+ Tcl_LimitTypeSet(slaveInterp, TCL_LIMIT_TIME);
+ } else {
+ Tcl_LimitTypeReset(slaveInterp, TCL_LIMIT_TIME);
+ }
+ }
+ if (scriptObj != NULL) {
+ SetLimitCallback(interp, TCL_LIMIT_TIME, slaveInterp,
+ (scriptLen > 0 ? scriptObj : NULL));
+ }
+ if (granObj != NULL) {
+ Tcl_LimitSetGranularity(slaveInterp, TCL_LIMIT_TIME, gran);
+ }
+ return TCL_OK;
+ }
+}
Index: generic/tclMain.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclMain.c,v
retrieving revision 1.25
diff -u -b -B -r1.25 tclMain.c
--- generic/tclMain.c 6 Apr 2004 22:25:54 -0000 1.25
+++ generic/tclMain.c 19 Apr 2004 22:18:35 -0000
@@ -363,6 +363,9 @@
if (Tcl_InterpDeleted(interp)) {
goto done;
}
+ if (Tcl_LimitExceeded(interp)) {
+ goto done;
+ }
/*
* If a script file was specified then just source that file
@@ -399,6 +402,9 @@
*/
Tcl_SourceRCFile(interp);
+ if (Tcl_LimitExceeded(interp)) {
+ goto done;
+ }
/*
* Process commands from stdin until there's an end-of-file. Note
@@ -421,6 +427,9 @@
if (Tcl_InterpDeleted(interp)) {
break;
}
+ if (Tcl_LimitExceeded(interp)) {
+ break;
+ }
inChannel = Tcl_GetStdChannel(TCL_STDIN);
if (inChannel == (Tcl_Channel) NULL) {
break;
@@ -557,7 +566,8 @@
}
done:
- if ((exitCode == 0) && (mainLoopProc != NULL)) {
+ if ((exitCode == 0) && (mainLoopProc != NULL)
+ && !Tcl_LimitExceeded(interp)) {
/*
* If everything has gone OK so far, call the main loop proc,
@@ -578,7 +588,7 @@
* cleanup on exit. The Tcl_Eval call should never return.
*/
- if (!Tcl_InterpDeleted(interp)) {
+ if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) {
sprintf(buffer, "exit %d", exitCode);
Tcl_Eval(interp, buffer);
Index: generic/tclStubInit.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclStubInit.c,v
retrieving revision 1.94
diff -u -b -B -r1.94 tclStubInit.c
--- generic/tclStubInit.c 6 Apr 2004 22:25:55 -0000 1.94
+++ generic/tclStubInit.c 19 Apr 2004 22:18:35 -0000
@@ -898,6 +898,21 @@
Tcl_GetCommandFullName, /* 517 */
Tcl_FSEvalFileEx, /* 518 */
Tcl_SetExitProc, /* 519 */
+ Tcl_LimitAddHandler, /* 520 */
+ Tcl_LimitRemoveHandler, /* 521 */
+ Tcl_LimitReady, /* 522 */
+ Tcl_LimitCheck, /* 523 */
+ Tcl_LimitExceeded, /* 524 */
+ Tcl_LimitSetCommands, /* 525 */
+ Tcl_LimitSetTime, /* 526 */
+ Tcl_LimitSetGranularity, /* 527 */
+ Tcl_LimitTypeEnabled, /* 528 */
+ Tcl_LimitTypeExceeded, /* 529 */
+ Tcl_LimitTypeSet, /* 530 */
+ Tcl_LimitTypeReset, /* 531 */
+ Tcl_LimitGetCommands, /* 532 */
+ Tcl_LimitGetTime, /* 533 */
+ Tcl_LimitGetGranularity, /* 534 */
};
/* !END!: Do not edit above this line. */
Index: generic/tclTrace.c
===================================================================
RCS file: /cvsroot/tcl/tcl/generic/tclTrace.c,v
retrieving revision 1.9
diff -u -b -B -r1.9 tclTrace.c
--- generic/tclTrace.c 1 Mar 2004 17:33:45 -0000 1.9
+++ generic/tclTrace.c 19 Apr 2004 22:18:35 -0000
@@ -1295,7 +1295,8 @@
tcmdPtr->refCount++;
- if ((tcmdPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)) {
+ if ((tcmdPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)
+ && !Tcl_LimitExceeded(interp)) {
/*
* Generate a command to execute by appending list elements
* for the old and new command name and the operation.
@@ -1333,6 +1334,7 @@
Tcl_DStringLength(&cmd), 0);
if (code != TCL_OK) {
/* We ignore errors in these traced commands */
+ /*** QUESTION: Use Tcl_BackgroundError(interp); instead? ***/
}
Tcl_RestoreResult(interp, &state);
@@ -1728,7 +1730,7 @@
return traceCode;
}
- if (!(flags & TCL_INTERP_DESTROYED)) {
+ if (!(flags & TCL_INTERP_DESTROYED) && !Tcl_LimitExceeded(interp)) {
/*
* Check whether the current call is going to eval arbitrary
* Tcl code with a generated trace, or whether we are only
@@ -1938,7 +1940,8 @@
Tcl_Preserve((ClientData) tvarPtr);
result = NULL;
- if ((tvarPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)) {
+ if ((tvarPtr->flags & flags) && !(flags & TCL_INTERP_DESTROYED)
+ && !Tcl_LimitExceeded(interp)) {
if (tvarPtr->length != (size_t) 0) {
/*
* Generate a command to execute by appending list elements