1 /*
2  * tclTrace.c --
3  *
4  *	This file contains code to handle most trace management.
5  *
6  * Copyright © 1987-1993 The Regents of the University of California.
7  * Copyright © 1994-1997 Sun Microsystems, Inc.
8  * Copyright © 1998-2000 Scriptics Corporation.
9  * Copyright © 2002 ActiveState Corporation.
10  *
11  * See the file "license.terms" for information on usage and redistribution of
12  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
13  */
14 
15 #include "tclInt.h"
16 
17 /*
18  * Structures used to hold information about variable traces:
19  */
20 
21 typedef struct {
22     int flags;			/* Operations for which Tcl command is to be
23 				 * invoked. */
24     size_t length;		/* Number of non-NUL chars. in command. */
25     char command[1];		/* Space for Tcl command to invoke. Actual
26 				 * size will be as large as necessary to hold
27 				 * command. This field must be the last in the
28 				 * structure, so that it can be larger than 1
29 				 * byte. */
30 } TraceVarInfo;
31 
32 typedef struct {
33     VarTrace traceInfo;
34     TraceVarInfo traceCmdInfo;
35 } CombinedTraceVarInfo;
36 
37 /*
38  * Structure used to hold information about command traces:
39  */
40 
41 typedef struct {
42     int flags;			/* Operations for which Tcl command is to be
43 				 * invoked. */
44     size_t length;		/* Number of non-NUL chars. in command. */
45     Tcl_Trace stepTrace;	/* Used for execution traces, when tracing
46 				 * inside the given command */
47     int startLevel;		/* Used for bookkeeping with step execution
48 				 * traces, store the level at which the step
49 				 * trace was invoked */
50     char *startCmd;		/* Used for bookkeeping with step execution
51 				 * traces, store the command name which
52 				 * invoked step trace */
53     int curFlags;		/* Trace flags for the current command */
54     int curCode;		/* Return code for the current command */
55     size_t refCount;		/* Used to ensure this structure is not
56 				 * deleted too early. Keeps track of how many
57 				 * pieces of code have a pointer to this
58 				 * structure. */
59     char command[1];		/* Space for Tcl command to invoke. Actual
60 				 * size will be as large as necessary to hold
61 				 * command. This field must be the last in the
62 				 * structure, so that it can be larger than 1
63 				 * byte. */
64 } TraceCommandInfo;
65 
66 /*
67  * Used by command execution traces. Note that we assume in the code that
68  * TCL_TRACE_ENTER_DURING_EXEC == 4 * TCL_TRACE_ENTER_EXEC and that
69  * TCL_TRACE_LEAVE_DURING_EXEC == 4 * TCL_TRACE_LEAVE_EXEC.
70  *
71  * TCL_TRACE_ENTER_DURING_EXEC  - Trace each command inside the command
72  *				  currently being traced, before execution.
73  * TCL_TRACE_LEAVE_DURING_EXEC  - Trace each command inside the command
74  *				  currently being traced, after execution.
75  * TCL_TRACE_ANY_EXEC		- OR'd combination of all EXEC flags.
76  * TCL_TRACE_EXEC_IN_PROGRESS   - The callback function on this trace is
77  *				  currently executing. Therefore we don't let
78  *				  further traces execute.
79  * TCL_TRACE_EXEC_DIRECT	- This execution trace is triggered directly
80  *				  by the command being traced, not because of
81  *				  an internal trace.
82  * The flag 'TCL_TRACE_DESTROYED' may also be used in command execution traces.
83  */
84 
85 #define TCL_TRACE_ENTER_DURING_EXEC	4
86 #define TCL_TRACE_LEAVE_DURING_EXEC	8
87 #define TCL_TRACE_ANY_EXEC		15
88 #define TCL_TRACE_EXEC_IN_PROGRESS	0x10
89 #define TCL_TRACE_EXEC_DIRECT		0x20
90 
91 /*
92  * Forward declarations for functions defined in this file:
93  */
94 
95 typedef int (Tcl_TraceTypeObjCmd)(Tcl_Interp *interp, int optionIndex,
96 	int objc, Tcl_Obj *const objv[]);
97 
98 static Tcl_TraceTypeObjCmd TraceVariableObjCmd;
99 static Tcl_TraceTypeObjCmd TraceCommandObjCmd;
100 static Tcl_TraceTypeObjCmd TraceExecutionObjCmd;
101 
102 /*
103  * Each subcommand has a number of 'types' to which it can apply. Currently
104  * 'execution', 'command' and 'variable' are the only types supported. These
105  * three arrays MUST be kept in sync! In the future we may provide an API to
106  * add to the list of supported trace types.
107  */
108 
109 static const char *const traceTypeOptions[] = {
110     "execution", "command", "variable", NULL
111 };
112 static Tcl_TraceTypeObjCmd *const traceSubCmds[] = {
113     TraceExecutionObjCmd,
114     TraceCommandObjCmd,
115     TraceVariableObjCmd
116 };
117 
118 /*
119  * Declarations for local functions to this file:
120  */
121 
122 static int		CallTraceFunction(Tcl_Interp *interp, Trace *tracePtr,
123 			    Command *cmdPtr, const char *command, int numChars,
124 			    int objc, Tcl_Obj *const objv[]);
125 static char *		TraceVarProc(ClientData clientData, Tcl_Interp *interp,
126 			    const char *name1, const char *name2, int flags);
127 static void		TraceCommandProc(ClientData clientData,
128 			    Tcl_Interp *interp, const char *oldName,
129 			    const char *newName, int flags);
130 static Tcl_CmdObjTraceProc TraceExecutionProc;
131 static int		StringTraceProc(ClientData clientData,
132 			    Tcl_Interp *interp, int level,
133 			    const char *command, Tcl_Command commandInfo,
134 			    int objc, Tcl_Obj *const objv[]);
135 static void		StringTraceDeleteProc(ClientData clientData);
136 static void		DisposeTraceResult(int flags, char *result);
137 static int		TraceVarEx(Tcl_Interp *interp, const char *part1,
138 			    const char *part2, VarTrace *tracePtr);
139 
140 /*
141  * The following structure holds the client data for string-based
142  * trace procs
143  */
144 
145 typedef struct {
146     ClientData clientData;	/* Client data from Tcl_CreateTrace */
147     Tcl_CmdTraceProc *proc;	/* Trace function from Tcl_CreateTrace */
148 } StringTraceData;
149 
150 /*
151  * Convenience macros for iterating over the list of traces. Note that each of
152  * these *must* be treated as a command, and *must* have a block following it.
153  */
154 
155 #define FOREACH_VAR_TRACE(interp, name, clientData) \
156     (clientData) = NULL; \
157     while (((clientData) = Tcl_VarTraceInfo2((interp), (name), NULL, \
158 	    0, TraceVarProc, (clientData))) != NULL)
159 
160 #define FOREACH_COMMAND_TRACE(interp, name, clientData) \
161     (clientData) = NULL; \
162     while ((clientData = Tcl_CommandTraceInfo(interp, name, 0, \
163 	    TraceCommandProc, clientData)) != NULL)
164 
165 /*
166  *----------------------------------------------------------------------
167  *
168  * Tcl_TraceObjCmd --
169  *
170  *	This function is invoked to process the "trace" Tcl command. See the
171  *	user documentation for details on what it does.
172  *
173  *	Standard syntax as of Tcl 8.4 is:
174  *	    trace {add|info|remove} {command|variable} name ops cmd
175  *
176  * Results:
177  *	A standard Tcl result.
178  *
179  * Side effects:
180  *	See the user documentation.
181  *----------------------------------------------------------------------
182  */
183 
184 int
Tcl_TraceObjCmd(TCL_UNUSED (void *),Tcl_Interp * interp,int objc,Tcl_Obj * const objv[])185 Tcl_TraceObjCmd(
186     TCL_UNUSED(void *),
187     Tcl_Interp *interp,		/* Current interpreter. */
188     int objc,			/* Number of arguments. */
189     Tcl_Obj *const objv[])	/* Argument objects. */
190 {
191     int optionIndex;
192 #ifndef TCL_REMOVE_OBSOLETE_TRACES
193     const char *name;
194     const char *flagOps, *p;
195 #endif
196     /* Main sub commands to 'trace' */
197     static const char *const traceOptions[] = {
198 	"add", "info", "remove",
199 #ifndef TCL_REMOVE_OBSOLETE_TRACES
200 	"variable", "vdelete", "vinfo",
201 #endif
202 	NULL
203     };
204     /* 'OLD' options are pre-Tcl-8.4 style */
205     enum traceOptionsEnum {
206 	TRACE_ADD, TRACE_INFO, TRACE_REMOVE,
207 #ifndef TCL_REMOVE_OBSOLETE_TRACES
208 	TRACE_OLD_VARIABLE, TRACE_OLD_VDELETE, TRACE_OLD_VINFO
209 #endif
210     };
211 
212     if (objc < 2) {
213 	Tcl_WrongNumArgs(interp, 1, objv, "option ?arg ...?");
214 	return TCL_ERROR;
215     }
216 
217     if (Tcl_GetIndexFromObj(interp, objv[1], traceOptions, "option", 0,
218 	    &optionIndex) != TCL_OK) {
219 	return TCL_ERROR;
220     }
221     switch ((enum traceOptionsEnum) optionIndex) {
222     case TRACE_ADD:
223     case TRACE_REMOVE: {
224 	/*
225 	 * All sub commands of trace add/remove must take at least one more
226 	 * argument. Beyond that we let the subcommand itself control the
227 	 * argument structure.
228 	 */
229 
230 	int typeIndex;
231 
232 	if (objc < 3) {
233 	    Tcl_WrongNumArgs(interp, 2, objv, "type ?arg ...?");
234 	    return TCL_ERROR;
235 	}
236 	if (Tcl_GetIndexFromObj(interp, objv[2], traceTypeOptions, "option",
237 		0, &typeIndex) != TCL_OK) {
238 	    return TCL_ERROR;
239 	}
240 	return traceSubCmds[typeIndex](interp, optionIndex, objc, objv);
241     }
242     case TRACE_INFO: {
243 	/*
244 	 * All sub commands of trace info must take exactly two more arguments
245 	 * which name the type of thing being traced and the name of the thing
246 	 * being traced.
247 	 */
248 
249 	int typeIndex;
250 	if (objc < 3) {
251 	    /*
252 	     * Delegate other complaints to the type-specific code which can
253 	     * give a better error message.
254 	     */
255 
256 	    Tcl_WrongNumArgs(interp, 2, objv, "type name");
257 	    return TCL_ERROR;
258 	}
259 	if (Tcl_GetIndexFromObj(interp, objv[2], traceTypeOptions, "option",
260 		0, &typeIndex) != TCL_OK) {
261 	    return TCL_ERROR;
262 	}
263 	return traceSubCmds[typeIndex](interp, optionIndex, objc, objv);
264 	break;
265     }
266 
267 #ifndef TCL_REMOVE_OBSOLETE_TRACES
268     case TRACE_OLD_VARIABLE:
269     case TRACE_OLD_VDELETE: {
270 	Tcl_Obj *copyObjv[6];
271 	Tcl_Obj *opsList;
272 	int code, numFlags;
273 
274 	if (objc != 5) {
275 	    Tcl_WrongNumArgs(interp, 2, objv, "name ops command");
276 	    return TCL_ERROR;
277 	}
278 
279 	TclNewObj(opsList);
280 	Tcl_IncrRefCount(opsList);
281 	flagOps = TclGetStringFromObj(objv[3], &numFlags);
282 	if (numFlags == 0) {
283 	    Tcl_DecrRefCount(opsList);
284 	    goto badVarOps;
285 	}
286 	for (p = flagOps; *p != 0; p++) {
287 	    Tcl_Obj *opObj;
288 
289 	    if (*p == 'r') {
290 		TclNewLiteralStringObj(opObj, "read");
291 	    } else if (*p == 'w') {
292 		TclNewLiteralStringObj(opObj, "write");
293 	    } else if (*p == 'u') {
294 		TclNewLiteralStringObj(opObj, "unset");
295 	    } else if (*p == 'a') {
296 		TclNewLiteralStringObj(opObj, "array");
297 	    } else {
298 		Tcl_DecrRefCount(opsList);
299 		goto badVarOps;
300 	    }
301 	    Tcl_ListObjAppendElement(NULL, opsList, opObj);
302 	}
303 	copyObjv[0] = NULL;
304 	memcpy(copyObjv+1, objv, objc*sizeof(Tcl_Obj *));
305 	copyObjv[4] = opsList;
306 	if (optionIndex == TRACE_OLD_VARIABLE) {
307 	    code = traceSubCmds[2](interp, TRACE_ADD, objc+1, copyObjv);
308 	} else {
309 	    code = traceSubCmds[2](interp, TRACE_REMOVE, objc+1, copyObjv);
310 	}
311 	Tcl_DecrRefCount(opsList);
312 	return code;
313     }
314     case TRACE_OLD_VINFO: {
315 	ClientData clientData;
316 	char ops[5];
317 	Tcl_Obj *resultListPtr, *pairObjPtr, *elemObjPtr;
318 
319 	if (objc != 3) {
320 	    Tcl_WrongNumArgs(interp, 2, objv, "name");
321 	    return TCL_ERROR;
322 	}
323 	TclNewObj(resultListPtr);
324 	name = Tcl_GetString(objv[2]);
325 	FOREACH_VAR_TRACE(interp, name, clientData) {
326 	    TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData;
327 	    char *q = ops;
328 
329 	    pairObjPtr = Tcl_NewListObj(0, NULL);
330 	    if (tvarPtr->flags & TCL_TRACE_READS) {
331 		*q = 'r';
332 		q++;
333 	    }
334 	    if (tvarPtr->flags & TCL_TRACE_WRITES) {
335 		*q = 'w';
336 		q++;
337 	    }
338 	    if (tvarPtr->flags & TCL_TRACE_UNSETS) {
339 		*q = 'u';
340 		q++;
341 	    }
342 	    if (tvarPtr->flags & TCL_TRACE_ARRAY) {
343 		*q = 'a';
344 		q++;
345 	    }
346 	    *q = '\0';
347 
348 	    /*
349 	     * Build a pair (2-item list) with the ops string as the first obj
350 	     * element and the tvarPtr->command string as the second obj
351 	     * element. Append the pair (as an element) to the end of the
352 	     * result object list.
353 	     */
354 
355 	    elemObjPtr = Tcl_NewStringObj(ops, -1);
356 	    Tcl_ListObjAppendElement(NULL, pairObjPtr, elemObjPtr);
357 	    elemObjPtr = Tcl_NewStringObj(tvarPtr->command, -1);
358 	    Tcl_ListObjAppendElement(NULL, pairObjPtr, elemObjPtr);
359 	    Tcl_ListObjAppendElement(interp, resultListPtr, pairObjPtr);
360 	}
361 	Tcl_SetObjResult(interp, resultListPtr);
362 	break;
363     }
364 #endif /* TCL_REMOVE_OBSOLETE_TRACES */
365     }
366     return TCL_OK;
367 
368 #ifndef TCL_REMOVE_OBSOLETE_TRACES
369   badVarOps:
370     Tcl_SetObjResult(interp, Tcl_ObjPrintf(
371 	    "bad operations \"%s\": should be one or more of rwua",
372 	    flagOps));
373     Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "BADOPS", NULL);
374     return TCL_ERROR;
375 #endif
376 }
377 
378 /*
379  *----------------------------------------------------------------------
380  *
381  * TraceExecutionObjCmd --
382  *
383  *	Helper function for Tcl_TraceObjCmd; implements the [trace
384  *	{add|remove|info} execution ...] subcommands. See the user
385  *	documentation for details on what these do.
386  *
387  * Results:
388  *	Standard Tcl result.
389  *
390  * Side effects:
391  *	Depends on the operation (add, remove, or info) being performed; may
392  *	add or remove command traces on a command.
393  *
394  *----------------------------------------------------------------------
395  */
396 
397 static int
TraceExecutionObjCmd(Tcl_Interp * interp,int optionIndex,int objc,Tcl_Obj * const objv[])398 TraceExecutionObjCmd(
399     Tcl_Interp *interp,		/* Current interpreter. */
400     int optionIndex,		/* Add, info or remove */
401     int objc,			/* Number of arguments. */
402     Tcl_Obj *const objv[])	/* Argument objects. */
403 {
404     int commandLength, index;
405     const char *name, *command;
406     size_t length;
407     enum traceOptions {
408 	TRACE_ADD, TRACE_INFO, TRACE_REMOVE
409     };
410     static const char *const opStrings[] = {
411 	"enter", "leave", "enterstep", "leavestep", NULL
412     };
413     enum operations {
414 	TRACE_EXEC_ENTER, TRACE_EXEC_LEAVE,
415 	TRACE_EXEC_ENTER_STEP, TRACE_EXEC_LEAVE_STEP
416     };
417 
418     switch ((enum traceOptions) optionIndex) {
419     case TRACE_ADD:
420     case TRACE_REMOVE: {
421 	int flags = 0;
422 	int i, listLen, result;
423 	Tcl_Obj **elemPtrs;
424 
425 	if (objc != 6) {
426 	    Tcl_WrongNumArgs(interp, 3, objv, "name opList command");
427 	    return TCL_ERROR;
428 	}
429 
430 	/*
431 	 * Make sure the ops argument is a list object; get its length and a
432 	 * pointer to its array of element pointers.
433 	 */
434 
435 	result = Tcl_ListObjGetElements(interp, objv[4], &listLen, &elemPtrs);
436 	if (result != TCL_OK) {
437 	    return result;
438 	}
439 	if (listLen == 0) {
440 	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
441 		    "bad operation list \"\": must be one or more of"
442 		    " enter, leave, enterstep, or leavestep", -1));
443 	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS",
444 		    NULL);
445 	    return TCL_ERROR;
446 	}
447 	for (i = 0; i < listLen; i++) {
448 	    if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings,
449 		    "operation", TCL_EXACT, &index) != TCL_OK) {
450 		return TCL_ERROR;
451 	    }
452 	    switch ((enum operations) index) {
453 	    case TRACE_EXEC_ENTER:
454 		flags |= TCL_TRACE_ENTER_EXEC;
455 		break;
456 	    case TRACE_EXEC_LEAVE:
457 		flags |= TCL_TRACE_LEAVE_EXEC;
458 		break;
459 	    case TRACE_EXEC_ENTER_STEP:
460 		flags |= TCL_TRACE_ENTER_DURING_EXEC;
461 		break;
462 	    case TRACE_EXEC_LEAVE_STEP:
463 		flags |= TCL_TRACE_LEAVE_DURING_EXEC;
464 		break;
465 	    }
466 	}
467 	command = TclGetStringFromObj(objv[5], &commandLength);
468 	length = (size_t) commandLength;
469 	if ((enum traceOptions) optionIndex == TRACE_ADD) {
470 	    TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)ckalloc(
471 		    offsetof(TraceCommandInfo, command) + 1 + length);
472 
473 	    tcmdPtr->flags = flags;
474 	    tcmdPtr->stepTrace = NULL;
475 	    tcmdPtr->startLevel = 0;
476 	    tcmdPtr->startCmd = NULL;
477 	    tcmdPtr->length = length;
478 	    tcmdPtr->refCount = 1;
479 	    flags |= TCL_TRACE_DELETE;
480 	    if (flags & (TCL_TRACE_ENTER_DURING_EXEC |
481 		    TCL_TRACE_LEAVE_DURING_EXEC)) {
482 		flags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC);
483 	    }
484 	    memcpy(tcmdPtr->command, command, length+1);
485 	    name = Tcl_GetString(objv[3]);
486 	    if (Tcl_TraceCommand(interp, name, flags, TraceCommandProc,
487 		    tcmdPtr) != TCL_OK) {
488 		ckfree(tcmdPtr);
489 		return TCL_ERROR;
490 	    }
491 	} else {
492 	    /*
493 	     * Search through all of our traces on this command to see if
494 	     * there's one with the given command. If so, then delete the
495 	     * first one that matches.
496 	     */
497 
498 	    ClientData clientData;
499 
500 	    /*
501 	     * First ensure the name given is valid.
502 	     */
503 
504 	    name = Tcl_GetString(objv[3]);
505 	    if (Tcl_FindCommand(interp,name,NULL,TCL_LEAVE_ERR_MSG) == NULL) {
506 		return TCL_ERROR;
507 	    }
508 
509 	    FOREACH_COMMAND_TRACE(interp, name, clientData) {
510 		TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
511 
512 		/*
513 		 * In checking the 'flags' field we must remove any extraneous
514 		 * flags which may have been temporarily added by various
515 		 * pieces of the trace mechanism.
516 		 */
517 
518 		if ((tcmdPtr->length == length)
519 			&& ((tcmdPtr->flags & (TCL_TRACE_ANY_EXEC |
520 				TCL_TRACE_RENAME | TCL_TRACE_DELETE)) == flags)
521 			&& (strncmp(command, tcmdPtr->command,
522 				(size_t) length) == 0)) {
523 		    flags |= TCL_TRACE_DELETE;
524 		    if (flags & (TCL_TRACE_ENTER_DURING_EXEC |
525 			    TCL_TRACE_LEAVE_DURING_EXEC)) {
526 			flags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC);
527 		    }
528 		    Tcl_UntraceCommand(interp, name, flags,
529 			    TraceCommandProc, clientData);
530 		    if (tcmdPtr->stepTrace != NULL) {
531 			/*
532 			 * We need to remove the interpreter-wide trace which
533 			 * we created to allow 'step' traces.
534 			 */
535 
536 			Tcl_DeleteTrace(interp, tcmdPtr->stepTrace);
537 			tcmdPtr->stepTrace = NULL;
538 			ckfree(tcmdPtr->startCmd);
539 		    }
540 		    if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) {
541 			/*
542 			 * Postpone deletion.
543 			 */
544 
545 			tcmdPtr->flags = 0;
546 		    }
547 		    if (tcmdPtr->refCount-- <= 1) {
548 			ckfree(tcmdPtr);
549 		    }
550 		    break;
551 		}
552 	    }
553 	}
554 	break;
555     }
556     case TRACE_INFO: {
557 	ClientData clientData;
558 	Tcl_Obj *resultListPtr;
559 
560 	if (objc != 4) {
561 	    Tcl_WrongNumArgs(interp, 3, objv, "name");
562 	    return TCL_ERROR;
563 	}
564 
565 	name = Tcl_GetString(objv[3]);
566 
567 	/*
568 	 * First ensure the name given is valid.
569 	 */
570 
571 	if (Tcl_FindCommand(interp, name, NULL, TCL_LEAVE_ERR_MSG) == NULL) {
572 	    return TCL_ERROR;
573 	}
574 
575 	resultListPtr = Tcl_NewListObj(0, NULL);
576 	FOREACH_COMMAND_TRACE(interp, name, clientData) {
577 	    int numOps = 0;
578 	    Tcl_Obj *opObj, *eachTraceObjPtr, *elemObjPtr;
579 	    TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
580 
581 	    /*
582 	     * Build a list with the ops list as the first obj element and the
583 	     * tcmdPtr->command string as the second obj element. Append this
584 	     * list (as an element) to the end of the result object list.
585 	     */
586 
587 	    elemObjPtr = Tcl_NewListObj(0, NULL);
588 	    Tcl_IncrRefCount(elemObjPtr);
589 	    if (tcmdPtr->flags & TCL_TRACE_ENTER_EXEC) {
590 		TclNewLiteralStringObj(opObj, "enter");
591 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
592 	    }
593 	    if (tcmdPtr->flags & TCL_TRACE_LEAVE_EXEC) {
594 		TclNewLiteralStringObj(opObj, "leave");
595 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
596 	    }
597 	    if (tcmdPtr->flags & TCL_TRACE_ENTER_DURING_EXEC) {
598 		TclNewLiteralStringObj(opObj, "enterstep");
599 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
600 	    }
601 	    if (tcmdPtr->flags & TCL_TRACE_LEAVE_DURING_EXEC) {
602 		TclNewLiteralStringObj(opObj, "leavestep");
603 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
604 	    }
605 	    Tcl_ListObjLength(NULL, elemObjPtr, &numOps);
606 	    if (0 == numOps) {
607 		Tcl_DecrRefCount(elemObjPtr);
608 		continue;
609 	    }
610 	    eachTraceObjPtr = Tcl_NewListObj(0, NULL);
611 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr);
612 	    Tcl_DecrRefCount(elemObjPtr);
613 	    elemObjPtr = NULL;
614 
615 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr,
616 		    Tcl_NewStringObj(tcmdPtr->command, -1));
617 	    Tcl_ListObjAppendElement(interp, resultListPtr, eachTraceObjPtr);
618 	}
619 	Tcl_SetObjResult(interp, resultListPtr);
620 	break;
621     }
622     }
623     return TCL_OK;
624 }
625 
626 /*
627  *----------------------------------------------------------------------
628  *
629  * TraceCommandObjCmd --
630  *
631  *	Helper function for Tcl_TraceObjCmd; implements the [trace
632  *	{add|info|remove} command ...] subcommands. See the user documentation
633  *	for details on what these do.
634  *
635  * Results:
636  *	Standard Tcl result.
637  *
638  * Side effects:
639  *	Depends on the operation (add, remove, or info) being performed; may
640  *	add or remove command traces on a command.
641  *
642  *----------------------------------------------------------------------
643  */
644 
645 static int
TraceCommandObjCmd(Tcl_Interp * interp,int optionIndex,int objc,Tcl_Obj * const objv[])646 TraceCommandObjCmd(
647     Tcl_Interp *interp,		/* Current interpreter. */
648     int optionIndex,		/* Add, info or remove */
649     int objc,			/* Number of arguments. */
650     Tcl_Obj *const objv[])	/* Argument objects. */
651 {
652     int commandLength, index;
653     const char *name, *command;
654     size_t length;
655     enum traceOptions { TRACE_ADD, TRACE_INFO, TRACE_REMOVE };
656     static const char *const opStrings[] = { "delete", "rename", NULL };
657     enum operations { TRACE_CMD_DELETE, TRACE_CMD_RENAME };
658 
659     switch ((enum traceOptions) optionIndex) {
660     case TRACE_ADD:
661     case TRACE_REMOVE: {
662 	int flags = 0;
663 	int i, listLen, result;
664 	Tcl_Obj **elemPtrs;
665 
666 	if (objc != 6) {
667 	    Tcl_WrongNumArgs(interp, 3, objv, "name opList command");
668 	    return TCL_ERROR;
669 	}
670 
671 	/*
672 	 * Make sure the ops argument is a list object; get its length and a
673 	 * pointer to its array of element pointers.
674 	 */
675 
676 	result = Tcl_ListObjGetElements(interp, objv[4], &listLen, &elemPtrs);
677 	if (result != TCL_OK) {
678 	    return result;
679 	}
680 	if (listLen == 0) {
681 	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
682 		    "bad operation list \"\": must be one or more of"
683 		    " delete or rename", -1));
684 	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS",
685 		    NULL);
686 	    return TCL_ERROR;
687 	}
688 
689 	for (i = 0; i < listLen; i++) {
690 	    if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings,
691 		    "operation", TCL_EXACT, &index) != TCL_OK) {
692 		return TCL_ERROR;
693 	    }
694 	    switch ((enum operations) index) {
695 	    case TRACE_CMD_RENAME:
696 		flags |= TCL_TRACE_RENAME;
697 		break;
698 	    case TRACE_CMD_DELETE:
699 		flags |= TCL_TRACE_DELETE;
700 		break;
701 	    }
702 	}
703 
704 	command = TclGetStringFromObj(objv[5], &commandLength);
705 	length = (size_t) commandLength;
706 	if ((enum traceOptions) optionIndex == TRACE_ADD) {
707 	    TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)ckalloc(
708 		    offsetof(TraceCommandInfo, command) + 1 + length);
709 
710 	    tcmdPtr->flags = flags;
711 	    tcmdPtr->stepTrace = NULL;
712 	    tcmdPtr->startLevel = 0;
713 	    tcmdPtr->startCmd = NULL;
714 	    tcmdPtr->length = length;
715 	    tcmdPtr->refCount = 1;
716 	    flags |= TCL_TRACE_DELETE;
717 	    memcpy(tcmdPtr->command, command, length+1);
718 	    name = Tcl_GetString(objv[3]);
719 	    if (Tcl_TraceCommand(interp, name, flags, TraceCommandProc,
720 		    tcmdPtr) != TCL_OK) {
721 		ckfree(tcmdPtr);
722 		return TCL_ERROR;
723 	    }
724 	} else {
725 	    /*
726 	     * Search through all of our traces on this command to see if
727 	     * there's one with the given command. If so, then delete the
728 	     * first one that matches.
729 	     */
730 
731 	    ClientData clientData;
732 
733 	    /*
734 	     * First ensure the name given is valid.
735 	     */
736 
737 	    name = Tcl_GetString(objv[3]);
738 	    if (Tcl_FindCommand(interp,name,NULL,TCL_LEAVE_ERR_MSG) == NULL) {
739 		return TCL_ERROR;
740 	    }
741 
742 	    FOREACH_COMMAND_TRACE(interp, name, clientData) {
743 		TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
744 
745 		if ((tcmdPtr->length == length) && (tcmdPtr->flags == flags)
746 			&& (strncmp(command, tcmdPtr->command,
747 				(size_t) length) == 0)) {
748 		    Tcl_UntraceCommand(interp, name, flags | TCL_TRACE_DELETE,
749 			    TraceCommandProc, clientData);
750 		    tcmdPtr->flags |= TCL_TRACE_DESTROYED;
751 		    if (tcmdPtr->refCount-- <= 1) {
752 			ckfree(tcmdPtr);
753 		    }
754 		    break;
755 		}
756 	    }
757 	}
758 	break;
759     }
760     case TRACE_INFO: {
761 	ClientData clientData;
762 	Tcl_Obj *resultListPtr;
763 
764 	if (objc != 4) {
765 	    Tcl_WrongNumArgs(interp, 3, objv, "name");
766 	    return TCL_ERROR;
767 	}
768 
769 	/*
770 	 * First ensure the name given is valid.
771 	 */
772 
773 	name = Tcl_GetString(objv[3]);
774 	if (Tcl_FindCommand(interp, name, NULL, TCL_LEAVE_ERR_MSG) == NULL) {
775 	    return TCL_ERROR;
776 	}
777 
778 	resultListPtr = Tcl_NewListObj(0, NULL);
779 	FOREACH_COMMAND_TRACE(interp, name, clientData) {
780 	    int numOps = 0;
781 	    Tcl_Obj *opObj, *eachTraceObjPtr, *elemObjPtr;
782 	    TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
783 
784 	    /*
785 	     * Build a list with the ops list as the first obj element and the
786 	     * tcmdPtr->command string as the second obj element. Append this
787 	     * list (as an element) to the end of the result object list.
788 	     */
789 
790 	    elemObjPtr = Tcl_NewListObj(0, NULL);
791 	    Tcl_IncrRefCount(elemObjPtr);
792 	    if (tcmdPtr->flags & TCL_TRACE_RENAME) {
793 		TclNewLiteralStringObj(opObj, "rename");
794 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
795 	    }
796 	    if (tcmdPtr->flags & TCL_TRACE_DELETE) {
797 		TclNewLiteralStringObj(opObj, "delete");
798 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObj);
799 	    }
800 	    Tcl_ListObjLength(NULL, elemObjPtr, &numOps);
801 	    if (0 == numOps) {
802 		Tcl_DecrRefCount(elemObjPtr);
803 		continue;
804 	    }
805 	    eachTraceObjPtr = Tcl_NewListObj(0, NULL);
806 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr);
807 	    Tcl_DecrRefCount(elemObjPtr);
808 
809 	    elemObjPtr = Tcl_NewStringObj(tcmdPtr->command, -1);
810 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr);
811 	    Tcl_ListObjAppendElement(interp, resultListPtr, eachTraceObjPtr);
812 	}
813 	Tcl_SetObjResult(interp, resultListPtr);
814 	break;
815     }
816     }
817     return TCL_OK;
818 }
819 
820 /*
821  *----------------------------------------------------------------------
822  *
823  * TraceVariableObjCmd --
824  *
825  *	Helper function for Tcl_TraceObjCmd; implements the [trace
826  *	{add|info|remove} variable ...] subcommands. See the user
827  *	documentation for details on what these do.
828  *
829  * Results:
830  *	Standard Tcl result.
831  *
832  * Side effects:
833  *	Depends on the operation (add, remove, or info) being performed; may
834  *	add or remove variable traces on a variable.
835  *
836  *----------------------------------------------------------------------
837  */
838 
839 static int
TraceVariableObjCmd(Tcl_Interp * interp,int optionIndex,int objc,Tcl_Obj * const objv[])840 TraceVariableObjCmd(
841     Tcl_Interp *interp,		/* Current interpreter. */
842     int optionIndex,		/* Add, info or remove */
843     int objc,			/* Number of arguments. */
844     Tcl_Obj *const objv[])	/* Argument objects. */
845 {
846     int commandLength, index;
847     const char *name, *command;
848     size_t length;
849     ClientData clientData;
850     enum traceOptions { TRACE_ADD, TRACE_INFO, TRACE_REMOVE };
851     static const char *const opStrings[] = {
852 	"array", "read", "unset", "write", NULL
853     };
854     enum operations {
855 	TRACE_VAR_ARRAY, TRACE_VAR_READ, TRACE_VAR_UNSET, TRACE_VAR_WRITE
856     };
857 
858     switch ((enum traceOptions) optionIndex) {
859     case TRACE_ADD:
860     case TRACE_REMOVE: {
861 	int flags = 0;
862 	int i, listLen, result;
863 	Tcl_Obj **elemPtrs;
864 
865 	if (objc != 6) {
866 	    Tcl_WrongNumArgs(interp, 3, objv, "name opList command");
867 	    return TCL_ERROR;
868 	}
869 
870 	/*
871 	 * Make sure the ops argument is a list object; get its length and a
872 	 * pointer to its array of element pointers.
873 	 */
874 
875 	result = Tcl_ListObjGetElements(interp, objv[4], &listLen, &elemPtrs);
876 	if (result != TCL_OK) {
877 	    return result;
878 	}
879 	if (listLen == 0) {
880 	    Tcl_SetObjResult(interp, Tcl_NewStringObj(
881 		    "bad operation list \"\": must be one or more of"
882 		    " array, read, unset, or write", -1));
883 	    Tcl_SetErrorCode(interp, "TCL", "OPERATION", "TRACE", "NOOPS",
884 		    NULL);
885 	    return TCL_ERROR;
886 	}
887 	for (i = 0; i < listLen ; i++) {
888 	    if (Tcl_GetIndexFromObj(interp, elemPtrs[i], opStrings,
889 		    "operation", TCL_EXACT, &index) != TCL_OK) {
890 		return TCL_ERROR;
891 	    }
892 	    switch ((enum operations) index) {
893 	    case TRACE_VAR_ARRAY:
894 		flags |= TCL_TRACE_ARRAY;
895 		break;
896 	    case TRACE_VAR_READ:
897 		flags |= TCL_TRACE_READS;
898 		break;
899 	    case TRACE_VAR_UNSET:
900 		flags |= TCL_TRACE_UNSETS;
901 		break;
902 	    case TRACE_VAR_WRITE:
903 		flags |= TCL_TRACE_WRITES;
904 		break;
905 	    }
906 	}
907 	command = TclGetStringFromObj(objv[5], &commandLength);
908 	length = (size_t) commandLength;
909 	if ((enum traceOptions) optionIndex == TRACE_ADD) {
910 	    CombinedTraceVarInfo *ctvarPtr = (CombinedTraceVarInfo *)ckalloc(
911 		    offsetof(CombinedTraceVarInfo, traceCmdInfo.command)
912 		    + 1 + length);
913 
914 	    ctvarPtr->traceCmdInfo.flags = flags;
915 #ifndef TCL_REMOVE_OBSOLETE_TRACES
916 	    if (objv[0] == NULL) {
917 		ctvarPtr->traceCmdInfo.flags |= TCL_TRACE_OLD_STYLE;
918 	    }
919 #endif
920 	    ctvarPtr->traceCmdInfo.length = length;
921 	    flags |= TCL_TRACE_UNSETS | TCL_TRACE_RESULT_OBJECT;
922 	    memcpy(ctvarPtr->traceCmdInfo.command, command, length+1);
923 	    ctvarPtr->traceInfo.traceProc = TraceVarProc;
924 	    ctvarPtr->traceInfo.clientData = &ctvarPtr->traceCmdInfo;
925 	    ctvarPtr->traceInfo.flags = flags;
926 	    name = Tcl_GetString(objv[3]);
927 	    if (TraceVarEx(interp, name, NULL, (VarTrace *) ctvarPtr)
928 		    != TCL_OK) {
929 		ckfree(ctvarPtr);
930 		return TCL_ERROR;
931 	    }
932 	} else {
933 	    /*
934 	     * Search through all of our traces on this variable to see if
935 	     * there's one with the given command. If so, then delete the
936 	     * first one that matches.
937 	     */
938 
939 	    name = Tcl_GetString(objv[3]);
940 	    FOREACH_VAR_TRACE(interp, name, clientData) {
941 		TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData;
942 
943 		if ((tvarPtr->length == length)
944 			&& ((tvarPtr->flags
945 #ifndef TCL_REMOVE_OBSOLETE_TRACES
946 & ~TCL_TRACE_OLD_STYLE
947 #endif
948 						)==flags)
949 			&& (strncmp(command, tvarPtr->command,
950 				(size_t) length) == 0)) {
951 		    Tcl_UntraceVar2(interp, name, NULL,
952 			    flags | TCL_TRACE_UNSETS | TCL_TRACE_RESULT_OBJECT,
953 			    TraceVarProc, clientData);
954 		    break;
955 		}
956 	    }
957 	}
958 	break;
959     }
960     case TRACE_INFO: {
961 	Tcl_Obj *resultListPtr;
962 
963 	if (objc != 4) {
964 	    Tcl_WrongNumArgs(interp, 3, objv, "name");
965 	    return TCL_ERROR;
966 	}
967 
968 	TclNewObj(resultListPtr);
969 	name = Tcl_GetString(objv[3]);
970 	FOREACH_VAR_TRACE(interp, name, clientData) {
971 	    Tcl_Obj *opObjPtr, *eachTraceObjPtr, *elemObjPtr;
972 	    TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData;
973 
974 	    /*
975 	     * Build a list with the ops list as the first obj element and the
976 	     * tcmdPtr->command string as the second obj element. Append this
977 	     * list (as an element) to the end of the result object list.
978 	     */
979 
980 	    elemObjPtr = Tcl_NewListObj(0, NULL);
981 	    if (tvarPtr->flags & TCL_TRACE_ARRAY) {
982 		TclNewLiteralStringObj(opObjPtr, "array");
983 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr);
984 	    }
985 	    if (tvarPtr->flags & TCL_TRACE_READS) {
986 		TclNewLiteralStringObj(opObjPtr, "read");
987 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr);
988 	    }
989 	    if (tvarPtr->flags & TCL_TRACE_WRITES) {
990 		TclNewLiteralStringObj(opObjPtr, "write");
991 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr);
992 	    }
993 	    if (tvarPtr->flags & TCL_TRACE_UNSETS) {
994 		TclNewLiteralStringObj(opObjPtr, "unset");
995 		Tcl_ListObjAppendElement(NULL, elemObjPtr, opObjPtr);
996 	    }
997 	    eachTraceObjPtr = Tcl_NewListObj(0, NULL);
998 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr);
999 
1000 	    elemObjPtr = Tcl_NewStringObj(tvarPtr->command, -1);
1001 	    Tcl_ListObjAppendElement(NULL, eachTraceObjPtr, elemObjPtr);
1002 	    Tcl_ListObjAppendElement(interp, resultListPtr,
1003 		    eachTraceObjPtr);
1004 	}
1005 	Tcl_SetObjResult(interp, resultListPtr);
1006 	break;
1007     }
1008     }
1009     return TCL_OK;
1010 }
1011 
1012 /*
1013  *----------------------------------------------------------------------
1014  *
1015  * Tcl_CommandTraceInfo --
1016  *
1017  *	Return the clientData value associated with a trace on a command.
1018  *	This function can also be used to step through all of the traces on a
1019  *	particular command that have the same trace function.
1020  *
1021  * Results:
1022  *	The return value is the clientData value associated with a trace on
1023  *	the given command. Information will only be returned for a trace with
1024  *	proc as trace function. If the clientData argument is NULL then the
1025  *	first such trace is returned; otherwise, the next relevant one after
1026  *	the one given by clientData will be returned. If the command doesn't
1027  *	exist then an error message is left in the interpreter and NULL is
1028  *	returned. Also, if there are no (more) traces for the given command,
1029  *	NULL is returned.
1030  *
1031  * Side effects:
1032  *	None.
1033  *
1034  *----------------------------------------------------------------------
1035  */
1036 
1037 ClientData
Tcl_CommandTraceInfo(Tcl_Interp * interp,const char * cmdName,TCL_UNUSED (int),Tcl_CommandTraceProc * proc,ClientData prevClientData)1038 Tcl_CommandTraceInfo(
1039     Tcl_Interp *interp,		/* Interpreter containing command. */
1040     const char *cmdName,	/* Name of command. */
1041     TCL_UNUSED(int) /*flags*/,
1042     Tcl_CommandTraceProc *proc,	/* Function assocated with trace. */
1043     ClientData prevClientData)	/* If non-NULL, gives last value returned by
1044 				 * this function, so this call will return the
1045 				 * next trace after that one. If NULL, this
1046 				 * call will return the first trace. */
1047 {
1048     Command *cmdPtr;
1049     CommandTrace *tracePtr;
1050 
1051     cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL,
1052 	    TCL_LEAVE_ERR_MSG);
1053     if (cmdPtr == NULL) {
1054 	return NULL;
1055     }
1056 
1057     /*
1058      * Find the relevant trace, if any, and return its clientData.
1059      */
1060 
1061     tracePtr = cmdPtr->tracePtr;
1062     if (prevClientData != NULL) {
1063 	for (; tracePtr!=NULL ; tracePtr=tracePtr->nextPtr) {
1064 	    if ((tracePtr->clientData == prevClientData)
1065 		    && (tracePtr->traceProc == proc)) {
1066 		tracePtr = tracePtr->nextPtr;
1067 		break;
1068 	    }
1069 	}
1070     }
1071     for (; tracePtr!=NULL ; tracePtr=tracePtr->nextPtr) {
1072 	if (tracePtr->traceProc == proc) {
1073 	    return tracePtr->clientData;
1074 	}
1075     }
1076     return NULL;
1077 }
1078 
1079 /*
1080  *----------------------------------------------------------------------
1081  *
1082  * Tcl_TraceCommand --
1083  *
1084  *	Arrange for rename/deletes to a command to cause a function to be
1085  *	invoked, which can monitor the operations.
1086  *
1087  *	Also optionally arrange for execution of that command to cause a
1088  *	function to be invoked.
1089  *
1090  * Results:
1091  *	A standard Tcl return value.
1092  *
1093  * Side effects:
1094  *	A trace is set up on the command given by cmdName, such that future
1095  *	changes to the command will be intermediated by proc. See the manual
1096  *	entry for complete details on the calling sequence for proc.
1097  *
1098  *----------------------------------------------------------------------
1099  */
1100 
1101 int
Tcl_TraceCommand(Tcl_Interp * interp,const char * cmdName,int flags,Tcl_CommandTraceProc * proc,ClientData clientData)1102 Tcl_TraceCommand(
1103     Tcl_Interp *interp,		/* Interpreter in which command is to be
1104 				 * traced. */
1105     const char *cmdName,	/* Name of command. */
1106     int flags,			/* OR-ed collection of bits, including any of
1107 				 * TCL_TRACE_RENAME, TCL_TRACE_DELETE, and any
1108 				 * of the TRACE_*_EXEC flags */
1109     Tcl_CommandTraceProc *proc,	/* Function to call when specified ops are
1110 				 * invoked upon cmdName. */
1111     ClientData clientData)	/* Arbitrary argument to pass to proc. */
1112 {
1113     Command *cmdPtr;
1114     CommandTrace *tracePtr;
1115 
1116     cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL,
1117 	    TCL_LEAVE_ERR_MSG);
1118     if (cmdPtr == NULL) {
1119 	return TCL_ERROR;
1120     }
1121 
1122     /*
1123      * Set up trace information.
1124      */
1125 
1126     tracePtr = (CommandTrace *)ckalloc(sizeof(CommandTrace));
1127     tracePtr->traceProc = proc;
1128     tracePtr->clientData = clientData;
1129     tracePtr->flags = flags &
1130 	    (TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC);
1131     tracePtr->nextPtr = cmdPtr->tracePtr;
1132     tracePtr->refCount = 1;
1133     cmdPtr->tracePtr = tracePtr;
1134     if (tracePtr->flags & TCL_TRACE_ANY_EXEC) {
1135 	/*
1136 	 * Bug 3484621: up the interp's epoch if this is a BC'ed command
1137 	 */
1138 
1139 	if ((cmdPtr->compileProc != NULL) && !(cmdPtr->flags & CMD_HAS_EXEC_TRACES)){
1140 	    Interp *iPtr = (Interp *) interp;
1141 	    iPtr->compileEpoch++;
1142 	}
1143 	cmdPtr->flags |= CMD_HAS_EXEC_TRACES;
1144     }
1145 
1146 
1147     return TCL_OK;
1148 }
1149 
1150 /*
1151  *----------------------------------------------------------------------
1152  *
1153  * Tcl_UntraceCommand --
1154  *
1155  *	Remove a previously-created trace for a command.
1156  *
1157  * Results:
1158  *	None.
1159  *
1160  * Side effects:
1161  *	If there exists a trace for the command given by cmdName with the
1162  *	given flags, proc, and clientData, then that trace is removed.
1163  *
1164  *----------------------------------------------------------------------
1165  */
1166 
1167 void
Tcl_UntraceCommand(Tcl_Interp * interp,const char * cmdName,int flags,Tcl_CommandTraceProc * proc,ClientData clientData)1168 Tcl_UntraceCommand(
1169     Tcl_Interp *interp,		/* Interpreter containing command. */
1170     const char *cmdName,	/* Name of command. */
1171     int flags,			/* OR-ed collection of bits, including any of
1172 				 * TCL_TRACE_RENAME, TCL_TRACE_DELETE, and any
1173 				 * of the TRACE_*_EXEC flags */
1174     Tcl_CommandTraceProc *proc,	/* Function assocated with trace. */
1175     ClientData clientData)	/* Arbitrary argument to pass to proc. */
1176 {
1177     CommandTrace *tracePtr;
1178     CommandTrace *prevPtr;
1179     Command *cmdPtr;
1180     Interp *iPtr = (Interp *)interp;
1181     ActiveCommandTrace *activePtr;
1182     int hasExecTraces = 0;
1183 
1184     cmdPtr = (Command *) Tcl_FindCommand(interp, cmdName, NULL,
1185 	    TCL_LEAVE_ERR_MSG);
1186     if (cmdPtr == NULL) {
1187 	return;
1188     }
1189 
1190     flags &= (TCL_TRACE_RENAME | TCL_TRACE_DELETE | TCL_TRACE_ANY_EXEC);
1191 
1192     for (tracePtr = cmdPtr->tracePtr, prevPtr = NULL; ;
1193 	    prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) {
1194 	if (tracePtr == NULL) {
1195 	    return;
1196 	}
1197 	if ((tracePtr->traceProc == proc)
1198 		&& ((tracePtr->flags & (TCL_TRACE_RENAME | TCL_TRACE_DELETE |
1199 			TCL_TRACE_ANY_EXEC)) == flags)
1200 		&& (tracePtr->clientData == clientData)) {
1201 	    if (tracePtr->flags & TCL_TRACE_ANY_EXEC) {
1202 		hasExecTraces = 1;
1203 	    }
1204 	    break;
1205 	}
1206     }
1207 
1208     /*
1209      * The code below makes it possible to delete traces while traces are
1210      * active: it makes sure that the deleted trace won't be processed by
1211      * CallCommandTraces.
1212      */
1213 
1214     for (activePtr = iPtr->activeCmdTracePtr;  activePtr != NULL;
1215 	    activePtr = activePtr->nextPtr) {
1216 	if (activePtr->nextTracePtr == tracePtr) {
1217 	    if (activePtr->reverseScan) {
1218 		activePtr->nextTracePtr = prevPtr;
1219 	    } else {
1220 		activePtr->nextTracePtr = tracePtr->nextPtr;
1221 	    }
1222 	}
1223     }
1224     if (prevPtr == NULL) {
1225 	cmdPtr->tracePtr = tracePtr->nextPtr;
1226     } else {
1227 	prevPtr->nextPtr = tracePtr->nextPtr;
1228     }
1229     tracePtr->flags = 0;
1230 
1231     if (tracePtr->refCount-- <= 1) {
1232 	ckfree(tracePtr);
1233     }
1234 
1235     if (hasExecTraces) {
1236 	for (tracePtr = cmdPtr->tracePtr, prevPtr = NULL; tracePtr != NULL ;
1237 		prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) {
1238 	    if (tracePtr->flags & TCL_TRACE_ANY_EXEC) {
1239 		return;
1240 	    }
1241 	}
1242 
1243 	/*
1244 	 * None of the remaining traces on this command are execution traces.
1245 	 * We therefore remove this flag:
1246 	 */
1247 
1248 	cmdPtr->flags &= ~CMD_HAS_EXEC_TRACES;
1249 
1250         /*
1251 	 * Bug 3484621: up the interp's epoch if this is a BC'ed command
1252 	 */
1253 
1254 	if (cmdPtr->compileProc != NULL) {
1255 	    iPtr->compileEpoch++;
1256 	}
1257     }
1258 }
1259 
1260 /*
1261  *----------------------------------------------------------------------
1262  *
1263  * TraceCommandProc --
1264  *
1265  *	This function is called to handle command changes that have been
1266  *	traced using the "trace" command, when using the 'rename' or 'delete'
1267  *	options.
1268  *
1269  * Results:
1270  *	None.
1271  *
1272  * Side effects:
1273  *	Depends on the command associated with the trace.
1274  *
1275  *----------------------------------------------------------------------
1276  */
1277 
1278 static void
TraceCommandProc(ClientData clientData,Tcl_Interp * interp,const char * oldName,const char * newName,int flags)1279 TraceCommandProc(
1280     ClientData clientData,	/* Information about the command trace. */
1281     Tcl_Interp *interp,		/* Interpreter containing command. */
1282     const char *oldName,	/* Name of command being changed. */
1283     const char *newName,	/* New name of command. Empty string or NULL
1284 				 * means command is being deleted (renamed to
1285 				 * ""). */
1286     int flags)			/* OR-ed bits giving operation and other
1287 				 * information. */
1288 {
1289     TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
1290     int code;
1291     Tcl_DString cmd;
1292 
1293     tcmdPtr->refCount++;
1294 
1295     if ((tcmdPtr->flags & flags) && !Tcl_InterpDeleted(interp)
1296 	    && !Tcl_LimitExceeded(interp)) {
1297 	/*
1298 	 * Generate a command to execute by appending list elements for the
1299 	 * old and new command name and the operation.
1300 	 */
1301 
1302 	Tcl_DStringInit(&cmd);
1303 	Tcl_DStringAppend(&cmd, tcmdPtr->command, (int) tcmdPtr->length);
1304 	Tcl_DStringAppendElement(&cmd, oldName);
1305 	Tcl_DStringAppendElement(&cmd, (newName ? newName : ""));
1306 	if (flags & TCL_TRACE_RENAME) {
1307 	    TclDStringAppendLiteral(&cmd, " rename");
1308 	} else if (flags & TCL_TRACE_DELETE) {
1309 	    TclDStringAppendLiteral(&cmd, " delete");
1310 	}
1311 
1312 	/*
1313 	 * Execute the command. We discard any object result the command
1314 	 * returns.
1315 	 *
1316 	 * Add the TCL_TRACE_DESTROYED flag to tcmdPtr to indicate to other
1317 	 * areas that this will be destroyed by us, otherwise a double-free
1318 	 * might occur depending on what the eval does.
1319 	 */
1320 
1321 	if (flags & TCL_TRACE_DESTROYED) {
1322 	    tcmdPtr->flags |= TCL_TRACE_DESTROYED;
1323 	}
1324 	code = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd),
1325 		Tcl_DStringLength(&cmd), 0);
1326 	if (code != TCL_OK) {
1327 	    /* We ignore errors in these traced commands */
1328 	    /*** QUESTION: Use Tcl_BackgroundException(interp, code); instead? ***/
1329 	}
1330 	Tcl_DStringFree(&cmd);
1331     }
1332 
1333     /*
1334      * We delete when the trace was destroyed or if this is a delete trace,
1335      * because command deletes are unconditional, so the trace must go away.
1336      */
1337 
1338     if (flags & (TCL_TRACE_DESTROYED | TCL_TRACE_DELETE)) {
1339 	int untraceFlags = tcmdPtr->flags;
1340 	Tcl_InterpState state;
1341 
1342 	if (tcmdPtr->stepTrace != NULL) {
1343 	    Tcl_DeleteTrace(interp, tcmdPtr->stepTrace);
1344 	    tcmdPtr->stepTrace = NULL;
1345 	    ckfree(tcmdPtr->startCmd);
1346 	}
1347 	if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) {
1348 	    /*
1349 	     * Postpone deletion, until exec trace returns.
1350 	     */
1351 
1352 	    tcmdPtr->flags = 0;
1353 	}
1354 
1355 	/*
1356 	 * We need to construct the same flags for Tcl_UntraceCommand as were
1357 	 * passed to Tcl_TraceCommand. Reproduce the processing of [trace add
1358 	 * execution/command]. Be careful to keep this code in sync with that.
1359 	 */
1360 
1361 	if (untraceFlags & TCL_TRACE_ANY_EXEC) {
1362 	    untraceFlags |= TCL_TRACE_DELETE;
1363 	    if (untraceFlags & (TCL_TRACE_ENTER_DURING_EXEC
1364 		    | TCL_TRACE_LEAVE_DURING_EXEC)) {
1365 		untraceFlags |= (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC);
1366 	    }
1367 	} else if (untraceFlags & TCL_TRACE_RENAME) {
1368 	    untraceFlags |= TCL_TRACE_DELETE;
1369 	}
1370 
1371 	/*
1372 	 * Remove the trace since TCL_TRACE_DESTROYED tells us to, or the
1373 	 * command we're tracing has just gone away. Then decrement the
1374 	 * clientData refCount that was set up by trace creation.
1375 	 *
1376 	 * Note that we save the (return) state of the interpreter to prevent
1377 	 * bizarre error messages.
1378 	 */
1379 
1380 	state = Tcl_SaveInterpState(interp, TCL_OK);
1381 	Tcl_UntraceCommand(interp, oldName, untraceFlags,
1382 		TraceCommandProc, clientData);
1383 	Tcl_RestoreInterpState(interp, state);
1384 	tcmdPtr->refCount--;
1385     }
1386     if (tcmdPtr->refCount-- <= 1) {
1387 	ckfree(tcmdPtr);
1388     }
1389 }
1390 
1391 /*
1392  *----------------------------------------------------------------------
1393  *
1394  * TclCheckExecutionTraces --
1395  *
1396  *	Checks on all current command execution traces, and invokes functions
1397  *	which have been registered. This function can be used by other code
1398  *	which performs execution to unify the tracing system, so that
1399  *	execution traces will function for that other code.
1400  *
1401  *	For instance extensions like [incr Tcl] which use their own execution
1402  *	technique can make use of Tcl's tracing.
1403  *
1404  *	This function is called by 'TclEvalObjvInternal'
1405  *
1406  * Results:
1407  *	The return value is a standard Tcl completion code such as TCL_OK or
1408  *	TCL_ERROR, etc.
1409  *
1410  * Side effects:
1411  *	Those side effects made by any trace functions called.
1412  *
1413  *----------------------------------------------------------------------
1414  */
1415 
1416 int
TclCheckExecutionTraces(Tcl_Interp * interp,const char * command,TCL_UNUSED (int),Command * cmdPtr,int code,int traceFlags,int objc,Tcl_Obj * const objv[])1417 TclCheckExecutionTraces(
1418     Tcl_Interp *interp,		/* The current interpreter. */
1419     const char *command,	/* Pointer to beginning of the current command
1420 				 * string. */
1421     TCL_UNUSED(int) /*numChars*/,
1422     Command *cmdPtr,		/* Points to command's Command struct. */
1423     int code,			/* The current result code. */
1424     int traceFlags,		/* Current tracing situation. */
1425     int objc,			/* Number of arguments for the command. */
1426     Tcl_Obj *const objv[])	/* Pointers to Tcl_Obj of each argument. */
1427 {
1428     Interp *iPtr = (Interp *) interp;
1429     CommandTrace *tracePtr, *lastTracePtr;
1430     ActiveCommandTrace active;
1431     int curLevel;
1432     int traceCode = TCL_OK;
1433     Tcl_InterpState state = NULL;
1434 
1435     if (cmdPtr->tracePtr == NULL) {
1436 	return traceCode;
1437     }
1438 
1439     curLevel = iPtr->varFramePtr->level;
1440 
1441     active.nextPtr = iPtr->activeCmdTracePtr;
1442     iPtr->activeCmdTracePtr = &active;
1443 
1444     active.cmdPtr = cmdPtr;
1445     lastTracePtr = NULL;
1446     for (tracePtr = cmdPtr->tracePtr;
1447 	    (traceCode == TCL_OK) && (tracePtr != NULL);
1448 	    tracePtr = active.nextTracePtr) {
1449 	if (traceFlags & TCL_TRACE_LEAVE_EXEC) {
1450 	    /*
1451 	     * Execute the trace command in order of creation for "leave".
1452 	     */
1453 
1454 	    active.reverseScan = 1;
1455 	    active.nextTracePtr = NULL;
1456 	    tracePtr = cmdPtr->tracePtr;
1457 	    while (tracePtr->nextPtr != lastTracePtr) {
1458 		active.nextTracePtr = tracePtr;
1459 		tracePtr = tracePtr->nextPtr;
1460 	    }
1461 	} else {
1462 	    active.reverseScan = 0;
1463 	    active.nextTracePtr = tracePtr->nextPtr;
1464 	}
1465 	if (tracePtr->traceProc == TraceCommandProc) {
1466 	    TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)tracePtr->clientData;
1467 
1468 	    if (tcmdPtr->flags != 0) {
1469 		tcmdPtr->curFlags = traceFlags | TCL_TRACE_EXEC_DIRECT;
1470 		tcmdPtr->curCode  = code;
1471 		tcmdPtr->refCount++;
1472 		if (state == NULL) {
1473 		    state = Tcl_SaveInterpState(interp, code);
1474 		}
1475 		traceCode = TraceExecutionProc(tcmdPtr, interp, curLevel,
1476 			command, (Tcl_Command) cmdPtr, objc, objv);
1477 		if (tcmdPtr->refCount-- <= 1) {
1478 		    ckfree(tcmdPtr);
1479 		}
1480 	    }
1481 	}
1482 	if (active.nextTracePtr) {
1483 	    lastTracePtr = active.nextTracePtr->nextPtr;
1484 	}
1485     }
1486     iPtr->activeCmdTracePtr = active.nextPtr;
1487     if (state) {
1488 	if (traceCode == TCL_OK) {
1489 	    (void) Tcl_RestoreInterpState(interp, state);
1490 	} else {
1491 	    Tcl_DiscardInterpState(state);
1492 	}
1493     }
1494 
1495     return traceCode;
1496 }
1497 
1498 /*
1499  *----------------------------------------------------------------------
1500  *
1501  * TclCheckInterpTraces --
1502  *
1503  *	Checks on all current traces, and invokes functions which have been
1504  *	registered. This function can be used by other code which performs
1505  *	execution to unify the tracing system. For instance extensions like
1506  *	[incr Tcl] which use their own execution technique can make use of
1507  *	Tcl's tracing.
1508  *
1509  *	This function is called by 'TclEvalObjvInternal'
1510  *
1511  * Results:
1512  *	The return value is a standard Tcl completion code such as TCL_OK or
1513  *	TCL_ERROR, etc.
1514  *
1515  * Side effects:
1516  *	Those side effects made by any trace functions called.
1517  *
1518  *----------------------------------------------------------------------
1519  */
1520 
1521 int
TclCheckInterpTraces(Tcl_Interp * interp,const char * command,int numChars,Command * cmdPtr,int code,int traceFlags,int objc,Tcl_Obj * const objv[])1522 TclCheckInterpTraces(
1523     Tcl_Interp *interp,		/* The current interpreter. */
1524     const char *command,	/* Pointer to beginning of the current command
1525 				 * string. */
1526     int numChars,		/* The number of characters in 'command' which
1527 				 * are part of the command string. */
1528     Command *cmdPtr,		/* Points to command's Command struct. */
1529     int code,			/* The current result code. */
1530     int traceFlags,		/* Current tracing situation. */
1531     int objc,			/* Number of arguments for the command. */
1532     Tcl_Obj *const objv[])	/* Pointers to Tcl_Obj of each argument. */
1533 {
1534     Interp *iPtr = (Interp *) interp;
1535     Trace *tracePtr, *lastTracePtr;
1536     ActiveInterpTrace active;
1537     int curLevel;
1538     int traceCode = TCL_OK;
1539     Tcl_InterpState state = NULL;
1540 
1541     if ((iPtr->tracePtr == NULL)
1542 	    || (iPtr->flags & INTERP_TRACE_IN_PROGRESS)) {
1543 	return(traceCode);
1544     }
1545 
1546     curLevel = iPtr->numLevels;
1547 
1548     active.nextPtr = iPtr->activeInterpTracePtr;
1549     iPtr->activeInterpTracePtr = &active;
1550 
1551     lastTracePtr = NULL;
1552     for (tracePtr = iPtr->tracePtr;
1553 	    (traceCode == TCL_OK) && (tracePtr != NULL);
1554 	    tracePtr = active.nextTracePtr) {
1555 	if (traceFlags & TCL_TRACE_ENTER_EXEC) {
1556 	    /*
1557 	     * Execute the trace command in reverse order of creation for
1558 	     * "enterstep" operation. The order is changed for "enterstep"
1559 	     * instead of for "leavestep" as was done in
1560 	     * TclCheckExecutionTraces because for step traces,
1561 	     * Tcl_CreateObjTrace creates one more linked list of traces which
1562 	     * results in one more reversal of trace invocation.
1563 	     */
1564 
1565 	    active.reverseScan = 1;
1566 	    active.nextTracePtr = NULL;
1567 	    tracePtr = iPtr->tracePtr;
1568 	    while (tracePtr->nextPtr != lastTracePtr) {
1569 		active.nextTracePtr = tracePtr;
1570 		tracePtr = tracePtr->nextPtr;
1571 	    }
1572 	    if (active.nextTracePtr) {
1573 		lastTracePtr = active.nextTracePtr->nextPtr;
1574 	    }
1575 	} else {
1576 	    active.reverseScan = 0;
1577 	    active.nextTracePtr = tracePtr->nextPtr;
1578 	}
1579 
1580 	if (tracePtr->level > 0 && curLevel > tracePtr->level) {
1581 	    continue;
1582 	}
1583 
1584 	if (!(tracePtr->flags & TCL_TRACE_EXEC_IN_PROGRESS)) {
1585 	    /*
1586 	     * The proc invoked might delete the traced command which which
1587 	     * might try to free tracePtr. We want to use tracePtr until the
1588 	     * end of this if section, so we use Tcl_Preserve() and
1589 	     * Tcl_Release() to be sure it is not freed while we still need
1590 	     * it.
1591 	     */
1592 
1593 	    Tcl_Preserve(tracePtr);
1594 	    tracePtr->flags |= TCL_TRACE_EXEC_IN_PROGRESS;
1595 	    if (state == NULL) {
1596 		state = Tcl_SaveInterpState(interp, code);
1597 	    }
1598 
1599 	    if (tracePtr->flags &
1600 		    (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC)) {
1601 		/*
1602 		 * New style trace.
1603 		 */
1604 
1605 		if (tracePtr->flags & traceFlags) {
1606 		    if (tracePtr->proc == TraceExecutionProc) {
1607 			TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)tracePtr->clientData;
1608 
1609 			tcmdPtr->curFlags = traceFlags;
1610 			tcmdPtr->curCode = code;
1611 		    }
1612 		    traceCode = tracePtr->proc(tracePtr->clientData, interp,
1613 			    curLevel, command, (Tcl_Command) cmdPtr, objc,
1614 			    objv);
1615 		}
1616 	    } else {
1617 		/*
1618 		 * Old-style trace.
1619 		 */
1620 
1621 		if (traceFlags & TCL_TRACE_ENTER_EXEC) {
1622 		    /*
1623 		     * Old-style interpreter-wide traces only trigger before
1624 		     * the command is executed.
1625 		     */
1626 
1627 		    traceCode = CallTraceFunction(interp, tracePtr, cmdPtr,
1628 			    command, numChars, objc, objv);
1629 		}
1630 	    }
1631 	    tracePtr->flags &= ~TCL_TRACE_EXEC_IN_PROGRESS;
1632 	    Tcl_Release(tracePtr);
1633 	}
1634     }
1635     iPtr->activeInterpTracePtr = active.nextPtr;
1636     if (state) {
1637 	if (traceCode == TCL_OK) {
1638 	    Tcl_RestoreInterpState(interp, state);
1639 	} else {
1640 	    Tcl_DiscardInterpState(state);
1641 	}
1642     }
1643 
1644     return traceCode;
1645 }
1646 
1647 /*
1648  *----------------------------------------------------------------------
1649  *
1650  * CallTraceFunction --
1651  *
1652  *	Invokes a trace function registered with an interpreter. These
1653  *	functions trace command execution. Currently this trace function is
1654  *	called with the address of the string-based Tcl_CmdProc for the
1655  *	command, not the Tcl_ObjCmdProc.
1656  *
1657  * Results:
1658  *	None.
1659  *
1660  * Side effects:
1661  *	Those side effects made by the trace function.
1662  *
1663  *----------------------------------------------------------------------
1664  */
1665 
1666 static int
CallTraceFunction(Tcl_Interp * interp,Trace * tracePtr,Command * cmdPtr,const char * command,int numChars,int objc,Tcl_Obj * const objv[])1667 CallTraceFunction(
1668     Tcl_Interp *interp,		/* The current interpreter. */
1669     Trace *tracePtr,	/* Describes the trace function to call. */
1670     Command *cmdPtr,		/* Points to command's Command struct. */
1671     const char *command,	/* Points to the first character of the
1672 				 * command's source before substitutions. */
1673     int numChars,		/* The number of characters in the command's
1674 				 * source. */
1675     int objc,		/* Number of arguments for the command. */
1676     Tcl_Obj *const objv[])	/* Pointers to Tcl_Obj of each argument. */
1677 {
1678     Interp *iPtr = (Interp *) interp;
1679     char *commandCopy;
1680     int traceCode;
1681 
1682     /*
1683      * Copy the command characters into a new string.
1684      */
1685 
1686     commandCopy = (char *)TclStackAlloc(interp, numChars + 1);
1687     memcpy(commandCopy, command, numChars);
1688     commandCopy[numChars] = '\0';
1689 
1690     /*
1691      * Call the trace function then free allocated storage.
1692      */
1693 
1694     traceCode = tracePtr->proc(tracePtr->clientData, (Tcl_Interp *) iPtr,
1695 	    iPtr->numLevels, commandCopy, (Tcl_Command) cmdPtr, objc, objv);
1696 
1697     TclStackFree(interp, commandCopy);
1698     return traceCode;
1699 }
1700 
1701 /*
1702  *----------------------------------------------------------------------
1703  *
1704  * CommandObjTraceDeleted --
1705  *
1706  *	Ensure the trace is correctly deleted by decrementing its refCount and
1707  *	only deleting if no other references exist.
1708  *
1709  * Results:
1710  *	None.
1711  *
1712  * Side effects:
1713  *	May release memory.
1714  *
1715  *----------------------------------------------------------------------
1716  */
1717 
1718 static void
CommandObjTraceDeleted(ClientData clientData)1719 CommandObjTraceDeleted(
1720     ClientData clientData)
1721 {
1722     TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
1723 
1724     if (tcmdPtr->refCount-- <= 1) {
1725 	ckfree(tcmdPtr);
1726     }
1727 }
1728 
1729 /*
1730  *----------------------------------------------------------------------
1731  *
1732  * TraceExecutionProc --
1733  *
1734  *	This function is invoked whenever code relevant to a 'trace execution'
1735  *	command is executed. It is called in one of two ways in Tcl's core:
1736  *
1737  *	(i) by the TclCheckExecutionTraces, when an execution trace has been
1738  *	triggered.
1739  *	(ii) by TclCheckInterpTraces, when a prior execution trace has created
1740  *	a trace of the internals of a procedure, passing in this function as
1741  *	the one to be called.
1742  *
1743  * Results:
1744  *	The return value is a standard Tcl completion code such as TCL_OK or
1745  *	TCL_ERROR, etc.
1746  *
1747  * Side effects:
1748  *	May invoke an arbitrary Tcl procedure, and may create or delete an
1749  *	interpreter-wide trace.
1750  *
1751  *----------------------------------------------------------------------
1752  */
1753 
1754 static int
TraceExecutionProc(ClientData clientData,Tcl_Interp * interp,int level,const char * command,TCL_UNUSED (Tcl_Command),int objc,struct Tcl_Obj * const objv[])1755 TraceExecutionProc(
1756     ClientData clientData,
1757     Tcl_Interp *interp,
1758     int level,
1759     const char *command,
1760     TCL_UNUSED(Tcl_Command),
1761     int objc,
1762     struct Tcl_Obj *const objv[])
1763 {
1764     int call = 0;
1765     Interp *iPtr = (Interp *) interp;
1766     TraceCommandInfo *tcmdPtr = (TraceCommandInfo *)clientData;
1767     int flags = tcmdPtr->curFlags;
1768     int code = tcmdPtr->curCode;
1769     int traceCode = TCL_OK;
1770 
1771     if (tcmdPtr->flags & TCL_TRACE_EXEC_IN_PROGRESS) {
1772 	/*
1773 	 * Inside any kind of execution trace callback, we do not allow any
1774 	 * further execution trace callbacks to be called for the same trace.
1775 	 */
1776 
1777 	return traceCode;
1778     }
1779 
1780     if (!Tcl_InterpDeleted(interp) && !Tcl_LimitExceeded(interp)) {
1781 	/*
1782 	 * Check whether the current call is going to eval arbitrary Tcl code
1783 	 * with a generated trace, or whether we are only going to setup
1784 	 * interpreter-wide traces to implement the 'step' traces. This latter
1785 	 * situation can happen if we create a command trace without either
1786 	 * before or after operations, but with either of the step operations.
1787 	 */
1788 
1789 	if (flags & TCL_TRACE_EXEC_DIRECT) {
1790 	    call = flags & tcmdPtr->flags &
1791 		    (TCL_TRACE_ENTER_EXEC | TCL_TRACE_LEAVE_EXEC);
1792 	} else {
1793 	    call = 1;
1794 	}
1795 
1796 	/*
1797 	 * First, if we have returned back to the level at which we created an
1798 	 * interpreter trace for enterstep and/or leavestep execution traces,
1799 	 * we remove it here.
1800 	 */
1801 
1802 	if ((flags & TCL_TRACE_LEAVE_EXEC) && (tcmdPtr->stepTrace != NULL)
1803 		&& (level == tcmdPtr->startLevel)
1804 		&& (strcmp(command, tcmdPtr->startCmd) == 0)) {
1805 	    Tcl_DeleteTrace(interp, tcmdPtr->stepTrace);
1806 	    tcmdPtr->stepTrace = NULL;
1807 	    ckfree(tcmdPtr->startCmd);
1808 	}
1809 
1810 	/*
1811 	 * Second, create the tcl callback, if required.
1812 	 */
1813 
1814 	if (call) {
1815 	    Tcl_DString cmd, sub;
1816 	    int i, saveInterpFlags;
1817 
1818 	    Tcl_DStringInit(&cmd);
1819 	    Tcl_DStringAppend(&cmd, tcmdPtr->command, (int)tcmdPtr->length);
1820 
1821 	    /*
1822 	     * Append command with arguments.
1823 	     */
1824 
1825 	    Tcl_DStringInit(&sub);
1826 	    for (i = 0; i < objc; i++) {
1827 		Tcl_DStringAppendElement(&sub, Tcl_GetString(objv[i]));
1828 	    }
1829 	    Tcl_DStringAppendElement(&cmd, Tcl_DStringValue(&sub));
1830 	    Tcl_DStringFree(&sub);
1831 
1832 	    if (flags & TCL_TRACE_ENTER_EXEC) {
1833 		/*
1834 		 * Append trace operation.
1835 		 */
1836 
1837 		if (flags & TCL_TRACE_EXEC_DIRECT) {
1838 		    Tcl_DStringAppendElement(&cmd, "enter");
1839 		} else {
1840 		    Tcl_DStringAppendElement(&cmd, "enterstep");
1841 		}
1842 	    } else if (flags & TCL_TRACE_LEAVE_EXEC) {
1843 		Tcl_Obj *resultCode;
1844 		const char *resultCodeStr;
1845 
1846 		/*
1847 		 * Append result code.
1848 		 */
1849 
1850 		TclNewIntObj(resultCode, code);
1851 		resultCodeStr = Tcl_GetString(resultCode);
1852 		Tcl_DStringAppendElement(&cmd, resultCodeStr);
1853 		Tcl_DecrRefCount(resultCode);
1854 
1855 		/*
1856 		 * Append result string.
1857 		 */
1858 
1859 		Tcl_DStringAppendElement(&cmd, Tcl_GetStringResult(interp));
1860 
1861 		/*
1862 		 * Append trace operation.
1863 		 */
1864 
1865 		if (flags & TCL_TRACE_EXEC_DIRECT) {
1866 		    Tcl_DStringAppendElement(&cmd, "leave");
1867 		} else {
1868 		    Tcl_DStringAppendElement(&cmd, "leavestep");
1869 		}
1870 	    } else {
1871 		Tcl_Panic("TraceExecutionProc: bad flag combination");
1872 	    }
1873 
1874 	    /*
1875 	     * Execute the command. We discard any object result the command
1876 	     * returns.
1877 	     */
1878 
1879 	    saveInterpFlags = iPtr->flags;
1880 	    iPtr->flags    |= INTERP_TRACE_IN_PROGRESS;
1881 	    tcmdPtr->flags |= TCL_TRACE_EXEC_IN_PROGRESS;
1882 	    tcmdPtr->refCount++;
1883 
1884 	    /*
1885 	     * This line can have quite arbitrary side-effects, including
1886 	     * deleting the trace, the command being traced, or even the
1887 	     * interpreter.
1888 	     */
1889 
1890 	    traceCode = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd),
1891 		    Tcl_DStringLength(&cmd), 0);
1892 	    tcmdPtr->flags &= ~TCL_TRACE_EXEC_IN_PROGRESS;
1893 
1894 	    /*
1895 	     * Restore the interp tracing flag to prevent cmd traces from
1896 	     * affecting interp traces.
1897 	     */
1898 
1899 	    iPtr->flags = saveInterpFlags;
1900 	    if (tcmdPtr->flags == 0) {
1901 		flags |= TCL_TRACE_DESTROYED;
1902 	    }
1903 	    Tcl_DStringFree(&cmd);
1904 	}
1905 
1906 	/*
1907 	 * Third, if there are any step execution traces for this proc, we
1908 	 * register an interpreter trace to invoke enterstep and/or leavestep
1909 	 * traces. We also need to save the current stack level and the proc
1910 	 * string in startLevel and startCmd so that we can delete this
1911 	 * interpreter trace when it reaches the end of this proc.
1912 	 */
1913 
1914 	if ((flags & TCL_TRACE_ENTER_EXEC) && (tcmdPtr->stepTrace == NULL)
1915 		&& (tcmdPtr->flags & (TCL_TRACE_ENTER_DURING_EXEC |
1916 			TCL_TRACE_LEAVE_DURING_EXEC))) {
1917 	    unsigned len = strlen(command) + 1;
1918 
1919 	    tcmdPtr->startLevel = level;
1920 	    tcmdPtr->startCmd = (char *)ckalloc(len);
1921 	    memcpy(tcmdPtr->startCmd, command, len);
1922 	    tcmdPtr->refCount++;
1923 	    tcmdPtr->stepTrace = Tcl_CreateObjTrace(interp, 0,
1924 		   (tcmdPtr->flags & TCL_TRACE_ANY_EXEC) >> 2,
1925 		   TraceExecutionProc, tcmdPtr, CommandObjTraceDeleted);
1926 	}
1927     }
1928     if (flags & TCL_TRACE_DESTROYED) {
1929 	if (tcmdPtr->stepTrace != NULL) {
1930 	    Tcl_DeleteTrace(interp, tcmdPtr->stepTrace);
1931 	    tcmdPtr->stepTrace = NULL;
1932 	    ckfree(tcmdPtr->startCmd);
1933 	}
1934     }
1935     if (call) {
1936 	if (tcmdPtr->refCount-- <= 1) {
1937 	    ckfree(tcmdPtr);
1938 	}
1939     }
1940     return traceCode;
1941 }
1942 
1943 /*
1944  *----------------------------------------------------------------------
1945  *
1946  * TraceVarProc --
1947  *
1948  *	This function is called to handle variable accesses that have been
1949  *	traced using the "trace" command.
1950  *
1951  * Results:
1952  *	Normally returns NULL. If the trace command returns an error, then
1953  *	this function returns an error string.
1954  *
1955  * Side effects:
1956  *	Depends on the command associated with the trace.
1957  *
1958  *----------------------------------------------------------------------
1959  */
1960 
1961 static char *
TraceVarProc(ClientData clientData,Tcl_Interp * interp,const char * name1,const char * name2,int flags)1962 TraceVarProc(
1963     ClientData clientData,	/* Information about the variable trace. */
1964     Tcl_Interp *interp,		/* Interpreter containing variable. */
1965     const char *name1,		/* Name of variable or array. */
1966     const char *name2,		/* Name of element within array; NULL means
1967 				 * scalar variable is being referenced. */
1968     int flags)			/* OR-ed bits giving operation and other
1969 				 * information. */
1970 {
1971     TraceVarInfo *tvarPtr = (TraceVarInfo *)clientData;
1972     char *result;
1973     int code, destroy = 0;
1974     Tcl_DString cmd;
1975     int rewind = ((Interp *)interp)->execEnvPtr->rewind;
1976 
1977     /*
1978      * We might call Tcl_EvalEx() below, and that might evaluate [trace vdelete]
1979      * which might try to free tvarPtr. We want to use tvarPtr until the end
1980      * of this function, so we use Tcl_Preserve() and Tcl_Release() to be sure
1981      * it is not freed while we still need it.
1982      */
1983 
1984     result = NULL;
1985     if ((tvarPtr->flags & flags) && !Tcl_InterpDeleted(interp)
1986 	    && !Tcl_LimitExceeded(interp)) {
1987 	if (tvarPtr->length != (size_t) 0) {
1988 	    /*
1989 	     * Generate a command to execute by appending list elements for
1990 	     * the two variable names and the operation.
1991 	     */
1992 
1993 	    Tcl_DStringInit(&cmd);
1994 	    Tcl_DStringAppend(&cmd, tvarPtr->command, (int) tvarPtr->length);
1995 	    Tcl_DStringAppendElement(&cmd, name1);
1996 	    Tcl_DStringAppendElement(&cmd, (name2 ? name2 : ""));
1997 #ifndef TCL_REMOVE_OBSOLETE_TRACES
1998 	    if (tvarPtr->flags & TCL_TRACE_OLD_STYLE) {
1999 		if (flags & TCL_TRACE_ARRAY) {
2000 		    TclDStringAppendLiteral(&cmd, " a");
2001 		} else if (flags & TCL_TRACE_READS) {
2002 		    TclDStringAppendLiteral(&cmd, " r");
2003 		} else if (flags & TCL_TRACE_WRITES) {
2004 		    TclDStringAppendLiteral(&cmd, " w");
2005 		} else if (flags & TCL_TRACE_UNSETS) {
2006 		    TclDStringAppendLiteral(&cmd, " u");
2007 		}
2008 	    } else {
2009 #endif
2010 		if (flags & TCL_TRACE_ARRAY) {
2011 		    TclDStringAppendLiteral(&cmd, " array");
2012 		} else if (flags & TCL_TRACE_READS) {
2013 		    TclDStringAppendLiteral(&cmd, " read");
2014 		} else if (flags & TCL_TRACE_WRITES) {
2015 		    TclDStringAppendLiteral(&cmd, " write");
2016 		} else if (flags & TCL_TRACE_UNSETS) {
2017 		    TclDStringAppendLiteral(&cmd, " unset");
2018 		}
2019 #ifndef TCL_REMOVE_OBSOLETE_TRACES
2020 	    }
2021 #endif
2022 
2023 	    /*
2024 	     * Execute the command. We discard any object result the command
2025 	     * returns.
2026 	     *
2027 	     * Add the TCL_TRACE_DESTROYED flag to tvarPtr to indicate to
2028 	     * other areas that this will be destroyed by us, otherwise a
2029 	     * double-free might occur depending on what the eval does.
2030 	     */
2031 
2032 	    if ((flags & TCL_TRACE_DESTROYED)
2033 		    && !(tvarPtr->flags & TCL_TRACE_DESTROYED)) {
2034 		destroy = 1;
2035 		tvarPtr->flags |= TCL_TRACE_DESTROYED;
2036 	    }
2037 
2038 	    /*
2039 	     * Make sure that unset traces are rune even if the execEnv is
2040 	     * rewinding (coroutine deletion, [Bug 2093947]
2041 	     */
2042 
2043 	    if (rewind && (flags & TCL_TRACE_UNSETS)) {
2044 		((Interp *)interp)->execEnvPtr->rewind = 0;
2045 	    }
2046 	    code = Tcl_EvalEx(interp, Tcl_DStringValue(&cmd),
2047 		    Tcl_DStringLength(&cmd), 0);
2048 	    if (rewind) {
2049 		((Interp *)interp)->execEnvPtr->rewind = rewind;
2050 	    }
2051 	    if (code != TCL_OK) {		/* copy error msg to result */
2052 		Tcl_Obj *errMsgObj = Tcl_GetObjResult(interp);
2053 
2054 		Tcl_IncrRefCount(errMsgObj);
2055 		result = (char *) errMsgObj;
2056 	    }
2057 	    Tcl_DStringFree(&cmd);
2058 	}
2059     }
2060     if (destroy && result != NULL) {
2061 	Tcl_Obj *errMsgObj = (Tcl_Obj *) result;
2062 
2063 	Tcl_DecrRefCount(errMsgObj);
2064 	result = NULL;
2065     }
2066     return result;
2067 }
2068 
2069 /*
2070  *----------------------------------------------------------------------
2071  *
2072  * Tcl_CreateObjTrace --
2073  *
2074  *	Arrange for a function to be called to trace command execution.
2075  *
2076  * Results:
2077  *	The return value is a token for the trace, which may be passed to
2078  *	Tcl_DeleteTrace to eliminate the trace.
2079  *
2080  * Side effects:
2081  *	From now on, proc will be called just before a command function is
2082  *	called to execute a Tcl command. Calls to proc will have the following
2083  *	form:
2084  *
2085  *	void proc(ClientData	 clientData,
2086  *		  Tcl_Interp *	 interp,
2087  *		  int		 level,
2088  *		  const char *	 command,
2089  *		  Tcl_Command	 commandInfo,
2090  *		  int		 objc,
2091  *		  Tcl_Obj *const objv[]);
2092  *
2093  *	The 'clientData' and 'interp' arguments to 'proc' will be the same as
2094  *	the arguments to Tcl_CreateObjTrace. The 'level' argument gives the
2095  *	nesting depth of command interpretation within the interpreter. The
2096  *	'command' argument is the ASCII text of the command being evaluated -
2097  *	before any substitutions are performed. The 'commandInfo' argument
2098  *	gives a handle to the command procedure that will be evaluated. The
2099  *	'objc' and 'objv' parameters give the parameter vector that will be
2100  *	passed to the command procedure. Proc does not return a value.
2101  *
2102  *	It is permissible for 'proc' to call Tcl_SetCommandTokenInfo to change
2103  *	the command procedure or client data for the command being evaluated,
2104  *	and these changes will take effect with the current evaluation.
2105  *
2106  *	The 'level' argument specifies the maximum nesting level of calls to
2107  *	be traced. If the execution depth of the interpreter exceeds 'level',
2108  *	the trace callback is not executed.
2109  *
2110  *	The 'flags' argument is either zero or the value,
2111  *	TCL_ALLOW_INLINE_COMPILATION. If the TCL_ALLOW_INLINE_COMPILATION flag
2112  *	is not present, the bytecode compiler will not generate inline code
2113  *	for Tcl's built-in commands. This behavior will have a significant
2114  *	impact on performance, but will ensure that all command evaluations
2115  *	are traced. If the TCL_ALLOW_INLINE_COMPILATION flag is present, the
2116  *	bytecode compiler will have its normal behavior of compiling in-line
2117  *	code for some of Tcl's built-in commands. In this case, the tracing
2118  *	will be imprecise - in-line code will not be traced - but run-time
2119  *	performance will be improved. The latter behavior is desired for many
2120  *	applications such as profiling of run time.
2121  *
2122  *	When the trace is deleted, the 'delProc' function will be invoked,
2123  *	passing it the original client data.
2124  *
2125  *----------------------------------------------------------------------
2126  */
2127 
2128 Tcl_Trace
Tcl_CreateObjTrace(Tcl_Interp * interp,int level,int flags,Tcl_CmdObjTraceProc * proc,ClientData clientData,Tcl_CmdObjTraceDeleteProc * delProc)2129 Tcl_CreateObjTrace(
2130     Tcl_Interp *interp,		/* Tcl interpreter */
2131     int level,			/* Maximum nesting level */
2132     int flags,			/* Flags, see above */
2133     Tcl_CmdObjTraceProc *proc,	/* Trace callback */
2134     ClientData clientData,	/* Client data for the callback */
2135     Tcl_CmdObjTraceDeleteProc *delProc)
2136 				/* Function to call when trace is deleted */
2137 {
2138     Trace *tracePtr;
2139     Interp *iPtr = (Interp *) interp;
2140 
2141     /*
2142      * Test if this trace allows inline compilation of commands.
2143      */
2144 
2145     if (!(flags & TCL_ALLOW_INLINE_COMPILATION)) {
2146 	if (iPtr->tracesForbiddingInline == 0) {
2147 	    /*
2148 	     * When the first trace forbidding inline compilation is created,
2149 	     * invalidate existing compiled code for this interpreter and
2150 	     * arrange (by setting the DONT_COMPILE_CMDS_INLINE flag) that
2151 	     * when compiling new code, no commands will be compiled inline
2152 	     * (i.e., into an inline sequence of instructions). We do this
2153 	     * because commands that were compiled inline will never result in
2154 	     * a command trace being called.
2155 	     */
2156 
2157 	    iPtr->compileEpoch++;
2158 	    iPtr->flags |= DONT_COMPILE_CMDS_INLINE;
2159 	}
2160 	iPtr->tracesForbiddingInline++;
2161     }
2162 
2163     tracePtr = (Trace *)ckalloc(sizeof(Trace));
2164     tracePtr->level = level;
2165     tracePtr->proc = proc;
2166     tracePtr->clientData = clientData;
2167     tracePtr->delProc = delProc;
2168     tracePtr->nextPtr = iPtr->tracePtr;
2169     tracePtr->flags = flags;
2170     iPtr->tracePtr = tracePtr;
2171 
2172     return (Tcl_Trace) tracePtr;
2173 }
2174 
2175 /*
2176  *----------------------------------------------------------------------
2177  *
2178  * Tcl_CreateTrace --
2179  *
2180  *	Arrange for a function to be called to trace command execution.
2181  *
2182  * Results:
2183  *	The return value is a token for the trace, which may be passed to
2184  *	Tcl_DeleteTrace to eliminate the trace.
2185  *
2186  * Side effects:
2187  *	From now on, proc will be called just before a command procedure is
2188  *	called to execute a Tcl command. Calls to proc will have the following
2189  *	form:
2190  *
2191  *	void
2192  *	proc(clientData, interp, level, command, cmdProc, cmdClientData,
2193  *		argc, argv)
2194  *	    ClientData clientData;
2195  *	    Tcl_Interp *interp;
2196  *	    int level;
2197  *	    char *command;
2198  *	    int (*cmdProc)();
2199  *	    ClientData cmdClientData;
2200  *	    int argc;
2201  *	    char **argv;
2202  *	{
2203  *	}
2204  *
2205  *	The clientData and interp arguments to proc will be the same as the
2206  *	corresponding arguments to this function. Level gives the nesting
2207  *	level of command interpretation for this interpreter (0 corresponds to
2208  *	top level). Command gives the ASCII text of the raw command, cmdProc
2209  *	and cmdClientData give the function that will be called to process the
2210  *	command and the ClientData value it will receive, and argc and argv
2211  *	give the arguments to the command, after any argument parsing and
2212  *	substitution. Proc does not return a value.
2213  *
2214  *----------------------------------------------------------------------
2215  */
2216 
2217 Tcl_Trace
Tcl_CreateTrace(Tcl_Interp * interp,int level,Tcl_CmdTraceProc * proc,ClientData clientData)2218 Tcl_CreateTrace(
2219     Tcl_Interp *interp,		/* Interpreter in which to create trace. */
2220     int level,			/* Only call proc for commands at nesting
2221 				 * level<=argument level (1=>top level). */
2222     Tcl_CmdTraceProc *proc,	/* Function to call before executing each
2223 				 * command. */
2224     ClientData clientData)	/* Arbitrary value word to pass to proc. */
2225 {
2226     StringTraceData *data = (StringTraceData *)ckalloc(sizeof(StringTraceData));
2227 
2228     data->clientData = clientData;
2229     data->proc = proc;
2230     return Tcl_CreateObjTrace(interp, level, 0, StringTraceProc,
2231 	    data, StringTraceDeleteProc);
2232 }
2233 
2234 /*
2235  *----------------------------------------------------------------------
2236  *
2237  * StringTraceProc --
2238  *
2239  *	Invoke a string-based trace function from an object-based callback.
2240  *
2241  * Results:
2242  *	None.
2243  *
2244  * Side effects:
2245  *	Whatever the string-based trace function does.
2246  *
2247  *----------------------------------------------------------------------
2248  */
2249 
2250 static int
StringTraceProc(ClientData clientData,Tcl_Interp * interp,int level,const char * command,Tcl_Command commandInfo,int objc,Tcl_Obj * const * objv)2251 StringTraceProc(
2252     ClientData clientData,
2253     Tcl_Interp *interp,
2254     int level,
2255     const char *command,
2256     Tcl_Command commandInfo,
2257     int objc,
2258     Tcl_Obj *const *objv)
2259 {
2260     StringTraceData *data = (StringTraceData *)clientData;
2261     Command *cmdPtr = (Command *) commandInfo;
2262     const char **argv;		/* Args to pass to string trace proc */
2263     int i;
2264 
2265     /*
2266      * This is a bit messy because we have to emulate the old trace interface,
2267      * which uses strings for everything.
2268      */
2269 
2270     argv = (const char **) TclStackAlloc(interp,
2271 	    (objc + 1) * sizeof(const char *));
2272     for (i = 0; i < objc; i++) {
2273 	argv[i] = Tcl_GetString(objv[i]);
2274     }
2275     argv[objc] = 0;
2276 
2277     /*
2278      * Invoke the command function. Note that we cast away const-ness on two
2279      * parameters for compatibility with legacy code; the code MUST NOT modify
2280      * either command or argv.
2281      */
2282 
2283     data->proc(data->clientData, interp, level, (char *) command,
2284 	    cmdPtr->proc, cmdPtr->clientData, objc, argv);
2285     TclStackFree(interp, (void *) argv);
2286 
2287     return TCL_OK;
2288 }
2289 
2290 /*
2291  *----------------------------------------------------------------------
2292  *
2293  * StringTraceDeleteProc --
2294  *
2295  *	Clean up memory when a string-based trace is deleted.
2296  *
2297  * Results:
2298  *	None.
2299  *
2300  * Side effects:
2301  *	Allocated memory is returned to the system.
2302  *
2303  *----------------------------------------------------------------------
2304  */
2305 
2306 static void
StringTraceDeleteProc(ClientData clientData)2307 StringTraceDeleteProc(
2308     ClientData clientData)
2309 {
2310     ckfree(clientData);
2311 }
2312 
2313 /*
2314  *----------------------------------------------------------------------
2315  *
2316  * Tcl_DeleteTrace --
2317  *
2318  *	Remove a trace.
2319  *
2320  * Results:
2321  *	None.
2322  *
2323  * Side effects:
2324  *	From now on there will be no more calls to the function given in
2325  *	trace.
2326  *
2327  *----------------------------------------------------------------------
2328  */
2329 
2330 void
Tcl_DeleteTrace(Tcl_Interp * interp,Tcl_Trace trace)2331 Tcl_DeleteTrace(
2332     Tcl_Interp *interp,		/* Interpreter that contains trace. */
2333     Tcl_Trace trace)		/* Token for trace (returned previously by
2334 				 * Tcl_CreateTrace). */
2335 {
2336     Interp *iPtr = (Interp *) interp;
2337     Trace *prevPtr, *tracePtr = (Trace *) trace;
2338     Trace **tracePtr2 = &iPtr->tracePtr;
2339     ActiveInterpTrace *activePtr;
2340 
2341     /*
2342      * Locate the trace entry in the interpreter's trace list, and remove it
2343      * from the list.
2344      */
2345 
2346     prevPtr = NULL;
2347     while (*tracePtr2 != NULL && *tracePtr2 != tracePtr) {
2348 	prevPtr = *tracePtr2;
2349 	tracePtr2 = &prevPtr->nextPtr;
2350     }
2351     if (*tracePtr2 == NULL) {
2352 	return;
2353     }
2354     *tracePtr2 = (*tracePtr2)->nextPtr;
2355 
2356     /*
2357      * The code below makes it possible to delete traces while traces are
2358      * active: it makes sure that the deleted trace won't be processed by
2359      * TclCheckInterpTraces.
2360      */
2361 
2362     for (activePtr = iPtr->activeInterpTracePtr;  activePtr != NULL;
2363 	    activePtr = activePtr->nextPtr) {
2364 	if (activePtr->nextTracePtr == tracePtr) {
2365 	    if (activePtr->reverseScan) {
2366 		activePtr->nextTracePtr = prevPtr;
2367 	    } else {
2368 		activePtr->nextTracePtr = tracePtr->nextPtr;
2369 	    }
2370 	}
2371     }
2372 
2373     /*
2374      * If the trace forbids bytecode compilation, change the interpreter's
2375      * state. If bytecode compilation is now permitted, flag the fact and
2376      * advance the compilation epoch so that procs will be recompiled to take
2377      * advantage of it.
2378      */
2379 
2380     if (!(tracePtr->flags & TCL_ALLOW_INLINE_COMPILATION)) {
2381 	iPtr->tracesForbiddingInline--;
2382 	if (iPtr->tracesForbiddingInline == 0) {
2383 	    iPtr->flags &= ~DONT_COMPILE_CMDS_INLINE;
2384 	    iPtr->compileEpoch++;
2385 	}
2386     }
2387 
2388     /*
2389      * Execute any delete callback.
2390      */
2391 
2392     if (tracePtr->delProc != NULL) {
2393 	tracePtr->delProc(tracePtr->clientData);
2394     }
2395 
2396     /*
2397      * Delete the trace object.
2398      */
2399 
2400     Tcl_EventuallyFree((char *) tracePtr, TCL_DYNAMIC);
2401 }
2402 
2403 /*
2404  *----------------------------------------------------------------------
2405  *
2406  * TclTraceVarExists --
2407  *
2408  *	This is called from info exists. We need to trigger read and/or array
2409  *	traces because they may end up creating a variable that doesn't
2410  *	currently exist.
2411  *
2412  * Results:
2413  *	A pointer to the Var structure, or NULL.
2414  *
2415  * Side effects:
2416  *	May fill in error messages in the interp.
2417  *
2418  *----------------------------------------------------------------------
2419  */
2420 
2421 Var *
TclVarTraceExists(Tcl_Interp * interp,const char * varName)2422 TclVarTraceExists(
2423     Tcl_Interp *interp,		/* The interpreter */
2424     const char *varName)	/* The variable name */
2425 {
2426     Var *varPtr, *arrayPtr;
2427 
2428     /*
2429      * The choice of "create" flag values is delicate here, and matches the
2430      * semantics of GetVar. Things are still not perfect, however, because if
2431      * you do "info exists x" you get a varPtr and therefore trigger traces.
2432      * However, if you do "info exists x(i)", then you only get a varPtr if x
2433      * is already known to be an array. Otherwise you get NULL, and no trace
2434      * is triggered. This matches Tcl 7.6 semantics.
2435      */
2436 
2437     varPtr = TclLookupVar(interp, varName, NULL, 0, "access",
2438 	    /*createPart1*/ 0, /*createPart2*/ 1, &arrayPtr);
2439 
2440     if (varPtr == NULL) {
2441 	return NULL;
2442     }
2443 
2444     if ((varPtr->flags & VAR_TRACED_READ)
2445 	    || (arrayPtr && (arrayPtr->flags & VAR_TRACED_READ))) {
2446 	TclCallVarTraces((Interp *) interp, arrayPtr, varPtr, varName, NULL,
2447 		TCL_TRACE_READS, /* leaveErrMsg */ 0);
2448     }
2449 
2450     /*
2451      * If the variable doesn't exist anymore and no-one's using it, then free
2452      * up the relevant structures and hash table entries.
2453      */
2454 
2455     if (TclIsVarUndefined(varPtr)) {
2456 	TclCleanupVar(varPtr, arrayPtr);
2457 	return NULL;
2458     }
2459 
2460     return varPtr;
2461 }
2462 
2463 /*
2464  *----------------------------------------------------------------------
2465  *
2466  * TclCheckArrayTraces --
2467  *
2468  *	This function is invoked to when we operate on an array variable,
2469  *	to allow any array traces to fire.
2470  *
2471  * Results:
2472  *	Returns TCL_OK to indicate normal operation. Returns TCL_ERROR if
2473  *	invocation of a trace function indicated an error. When TCL_ERROR is
2474  *	returned, then error information is left in interp.
2475  *
2476  * Side effects:
2477  *	Almost anything can happen, depending on trace; this function itself
2478  *	doesn't have any side effects.
2479  *
2480  *----------------------------------------------------------------------
2481  */
2482 
2483 int
TclCheckArrayTraces(Tcl_Interp * interp,Var * varPtr,Var * arrayPtr,Tcl_Obj * name,int index)2484 TclCheckArrayTraces(
2485     Tcl_Interp *interp,
2486     Var *varPtr,
2487     Var *arrayPtr,
2488     Tcl_Obj *name,
2489     int index)
2490 {
2491     int code = TCL_OK;
2492 
2493     if (varPtr && (varPtr->flags & VAR_TRACED_ARRAY)
2494 	&& (TclIsVarArray(varPtr) || TclIsVarUndefined(varPtr))) {
2495 	Interp *iPtr = (Interp *)interp;
2496 
2497 	code = TclObjCallVarTraces(iPtr, arrayPtr, varPtr, name, NULL,
2498 		(TCL_NAMESPACE_ONLY|TCL_GLOBAL_ONLY| TCL_TRACE_ARRAY),
2499 		/* leaveErrMsg */ 1, index);
2500     }
2501     return code;
2502 }
2503 
2504 /*
2505  *----------------------------------------------------------------------
2506  *
2507  * TclCallVarTraces --
2508  *
2509  *	This function is invoked to find and invoke relevant trace functions
2510  *	associated with a particular operation on a variable. This function
2511  *	invokes traces both on the variable and on its containing array (where
2512  *	relevant).
2513  *
2514  * Results:
2515  *	Returns TCL_OK to indicate normal operation. Returns TCL_ERROR if
2516  *	invocation of a trace function indicated an error. When TCL_ERROR is
2517  *	returned and leaveErrMsg is true, then the errorInfo field of iPtr has
2518  *	information about the error placed in it.
2519  *
2520  * Side effects:
2521  *	Almost anything can happen, depending on trace; this function itself
2522  *	doesn't have any side effects.
2523  *
2524  *----------------------------------------------------------------------
2525  */
2526 
2527 int
TclObjCallVarTraces(Interp * iPtr,Var * arrayPtr,Var * varPtr,Tcl_Obj * part1Ptr,Tcl_Obj * part2Ptr,int flags,int leaveErrMsg,int index)2528 TclObjCallVarTraces(
2529     Interp *iPtr,		/* Interpreter containing variable. */
2530     Var *arrayPtr,	/* Pointer to array variable that contains the
2531 				 * variable, or NULL if the variable isn't an
2532 				 * element of an array. */
2533     Var *varPtr,		/* Variable whose traces are to be invoked. */
2534     Tcl_Obj *part1Ptr,
2535     Tcl_Obj *part2Ptr,		/* Variable's two-part name. */
2536     int flags,			/* Flags passed to trace functions: indicates
2537 				 * what's happening to variable, plus maybe
2538 				 * TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY */
2539     int leaveErrMsg,		/* If true, and one of the traces indicates an
2540 				 * error, then leave an error message and
2541 				 * stack trace information in *iPTr. */
2542     int index)			/* Index into the local variable table of the
2543 				 * variable, or -1. Only used when part1Ptr is
2544 				 * NULL. */
2545 {
2546     const char *part1, *part2;
2547 
2548     if (!part1Ptr) {
2549 	part1Ptr = localName(iPtr->varFramePtr, index);
2550     }
2551     if (!part1Ptr) {
2552 	Tcl_Panic("Cannot trace a variable with no name");
2553     }
2554     part1 = TclGetString(part1Ptr);
2555     part2 = part2Ptr? TclGetString(part2Ptr) : NULL;
2556 
2557     return TclCallVarTraces(iPtr, arrayPtr, varPtr, part1, part2, flags,
2558 	    leaveErrMsg);
2559 }
2560 
2561 #undef TCL_INTERP_DESTROYED
2562 #define TCL_INTERP_DESTROYED     0x100
2563 
2564 int
TclCallVarTraces(Interp * iPtr,Var * arrayPtr,Var * varPtr,const char * part1,const char * part2,int flags,int leaveErrMsg)2565 TclCallVarTraces(
2566     Interp *iPtr,		/* Interpreter containing variable. */
2567     Var *arrayPtr,	/* Pointer to array variable that contains the
2568 				 * variable, or NULL if the variable isn't an
2569 				 * element of an array. */
2570     Var *varPtr,		/* Variable whose traces are to be invoked. */
2571     const char *part1,
2572     const char *part2,		/* Variable's two-part name. */
2573     int flags,			/* Flags passed to trace functions: indicates
2574 				 * what's happening to variable, plus maybe
2575 				 * TCL_GLOBAL_ONLY or TCL_NAMESPACE_ONLY */
2576     int leaveErrMsg)		/* If true, and one of the traces indicates an
2577 				 * error, then leave an error message and
2578 				 * stack trace information in *iPTr. */
2579 {
2580     VarTrace *tracePtr;
2581     ActiveVarTrace active;
2582     char *result;
2583     const char *openParen, *p;
2584     Tcl_DString nameCopy;
2585     int copiedName;
2586     int code = TCL_OK;
2587     int disposeFlags = 0;
2588     Tcl_InterpState state = NULL;
2589     Tcl_HashEntry *hPtr;
2590     int traceflags = flags & VAR_ALL_TRACES;
2591 
2592     /*
2593      * If there are already similar trace functions active for the variable,
2594      * don't call them again.
2595      */
2596 
2597     if (TclIsVarTraceActive(varPtr)) {
2598 	return code;
2599     }
2600     TclSetVarTraceActive(varPtr);
2601     if (TclIsVarInHash(varPtr)) {
2602 	VarHashRefCount(varPtr)++;
2603     }
2604     if (arrayPtr && TclIsVarInHash(arrayPtr)) {
2605 	VarHashRefCount(arrayPtr)++;
2606     }
2607 
2608     /*
2609      * If the variable name hasn't been parsed into array name and element, do
2610      * it here. If there really is an array element, make a copy of the
2611      * original name so that NULLs can be inserted into it to separate the
2612      * names (can't modify the name string in place, because the string might
2613      * get used by the callbacks we invoke).
2614      */
2615 
2616     copiedName = 0;
2617     if (part2 == NULL) {
2618 	for (p = part1; *p ; p++) {
2619 	    if (*p == '(') {
2620 		openParen = p;
2621 		do {
2622 		    p++;
2623 		} while (*p != '\0');
2624 		p--;
2625 		if (*p == ')') {
2626 		    int offset = (openParen - part1);
2627 		    char *newPart1;
2628 
2629 		    Tcl_DStringInit(&nameCopy);
2630 		    Tcl_DStringAppend(&nameCopy, part1, p-part1);
2631 		    newPart1 = Tcl_DStringValue(&nameCopy);
2632 		    newPart1[offset] = 0;
2633 		    part1 = newPart1;
2634 		    part2 = newPart1 + offset + 1;
2635 		    copiedName = 1;
2636 		}
2637 		break;
2638 	    }
2639 	}
2640     }
2641 
2642     /*
2643      * Ignore any caller-provided TCL_INTERP_DESTROYED flag.  Only we can
2644      * set it correctly.
2645      */
2646 
2647     flags &= ~TCL_INTERP_DESTROYED;
2648 
2649     /*
2650      * Invoke traces on the array containing the variable, if relevant.
2651      */
2652 
2653     result = NULL;
2654     active.nextPtr = iPtr->activeVarTracePtr;
2655     iPtr->activeVarTracePtr = &active;
2656     Tcl_Preserve(iPtr);
2657     if (arrayPtr && !TclIsVarTraceActive(arrayPtr)
2658 	    && (arrayPtr->flags & traceflags)) {
2659 	hPtr = Tcl_FindHashEntry(&iPtr->varTraces, (char *) arrayPtr);
2660 	active.varPtr = arrayPtr;
2661 	for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr);
2662 		tracePtr != NULL; tracePtr = active.nextTracePtr) {
2663 	    active.nextTracePtr = tracePtr->nextPtr;
2664 	    if (!(tracePtr->flags & flags)) {
2665 		continue;
2666 	    }
2667 	    Tcl_Preserve(tracePtr);
2668 	    if (state == NULL) {
2669 		state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, code);
2670 	    }
2671 	    if (Tcl_InterpDeleted((Tcl_Interp *) iPtr)) {
2672 		flags |= TCL_INTERP_DESTROYED;
2673 	    }
2674 	    result = tracePtr->traceProc(tracePtr->clientData,
2675 		    (Tcl_Interp *) iPtr, part1, part2, flags);
2676 	    if (result != NULL) {
2677 		if (flags & TCL_TRACE_UNSETS) {
2678 		    /*
2679 		     * Ignore errors in unset traces.
2680 		     */
2681 
2682 		    DisposeTraceResult(tracePtr->flags, result);
2683 		} else {
2684 		    disposeFlags = tracePtr->flags;
2685 		    code = TCL_ERROR;
2686 		}
2687 	    }
2688 	    Tcl_Release(tracePtr);
2689 	    if (code == TCL_ERROR) {
2690 		goto done;
2691 	    }
2692 	}
2693     }
2694 
2695     /*
2696      * Invoke traces on the variable itself.
2697      */
2698 
2699     if (flags & TCL_TRACE_UNSETS) {
2700 	flags |= TCL_TRACE_DESTROYED;
2701     }
2702     active.varPtr = varPtr;
2703     if (varPtr->flags & traceflags) {
2704 	hPtr = Tcl_FindHashEntry(&iPtr->varTraces, (char *) varPtr);
2705 	for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr);
2706 		tracePtr != NULL; tracePtr = active.nextTracePtr) {
2707 	    active.nextTracePtr = tracePtr->nextPtr;
2708 	    if (!(tracePtr->flags & flags)) {
2709 		continue;
2710 	    }
2711 	    Tcl_Preserve(tracePtr);
2712 	    if (state == NULL) {
2713 		state = Tcl_SaveInterpState((Tcl_Interp *) iPtr, code);
2714 	    }
2715 	    if (Tcl_InterpDeleted((Tcl_Interp *) iPtr)) {
2716 		flags |= TCL_INTERP_DESTROYED;
2717 	    }
2718 	    result = tracePtr->traceProc(tracePtr->clientData,
2719 		    (Tcl_Interp *) iPtr, part1, part2, flags);
2720 	    if (result != NULL) {
2721 		if (flags & TCL_TRACE_UNSETS) {
2722 		    /*
2723 		     * Ignore errors in unset traces.
2724 		     */
2725 
2726 		    DisposeTraceResult(tracePtr->flags, result);
2727 		} else {
2728 		    disposeFlags = tracePtr->flags;
2729 		    code = TCL_ERROR;
2730 		}
2731 	    }
2732 	    Tcl_Release(tracePtr);
2733 	    if (code == TCL_ERROR) {
2734 		goto done;
2735 	    }
2736 	}
2737     }
2738 
2739     /*
2740      * Restore the variable's flags, remove the record of our active traces,
2741      * and then return.
2742      */
2743 
2744   done:
2745     if (code == TCL_ERROR) {
2746 	if (leaveErrMsg) {
2747 	    const char *verb = "";
2748 	    const char *type = "";
2749 
2750 	    switch (flags&(TCL_TRACE_READS|TCL_TRACE_WRITES|TCL_TRACE_ARRAY)) {
2751 	    case TCL_TRACE_READS:
2752 		verb = "read";
2753 		type = verb;
2754 		break;
2755 	    case TCL_TRACE_WRITES:
2756 		verb = "set";
2757 		type = "write";
2758 		break;
2759 	    case TCL_TRACE_ARRAY:
2760 		verb = "trace array";
2761 		type = "array";
2762 		break;
2763 	    }
2764 
2765 	    if (disposeFlags & TCL_TRACE_RESULT_OBJECT) {
2766 		Tcl_SetObjResult((Tcl_Interp *)iPtr, (Tcl_Obj *) result);
2767 	    } else {
2768 		Tcl_SetObjResult((Tcl_Interp *)iPtr,
2769 			Tcl_NewStringObj(result, -1));
2770 	    }
2771 	    Tcl_AddErrorInfo((Tcl_Interp *)iPtr, "");
2772 
2773 	    Tcl_AppendObjToErrorInfo((Tcl_Interp *)iPtr, Tcl_ObjPrintf(
2774 		    "\n    (%s trace on \"%s%s%s%s\")", type, part1,
2775 		    (part2 ? "(" : ""), (part2 ? part2 : ""),
2776 		    (part2 ? ")" : "") ));
2777 	    if (disposeFlags & TCL_TRACE_RESULT_OBJECT) {
2778 		TclVarErrMsg((Tcl_Interp *) iPtr, part1, part2, verb,
2779 			Tcl_GetString((Tcl_Obj *) result));
2780 	    } else {
2781 		TclVarErrMsg((Tcl_Interp *) iPtr, part1, part2, verb, result);
2782 	    }
2783 	    iPtr->flags &= ~(ERR_ALREADY_LOGGED);
2784 	    Tcl_DiscardInterpState(state);
2785 	} else {
2786 	    Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state);
2787 	}
2788 	DisposeTraceResult(disposeFlags,result);
2789     } else if (state) {
2790 	if (code == TCL_OK) {
2791 	    code = Tcl_RestoreInterpState((Tcl_Interp *) iPtr, state);
2792 	} else {
2793 	    Tcl_DiscardInterpState(state);
2794 	}
2795     }
2796 
2797     if (arrayPtr && TclIsVarInHash(arrayPtr)) {
2798 	VarHashRefCount(arrayPtr)--;
2799     }
2800     if (copiedName) {
2801 	Tcl_DStringFree(&nameCopy);
2802     }
2803     TclClearVarTraceActive(varPtr);
2804     if (TclIsVarInHash(varPtr)) {
2805 	VarHashRefCount(varPtr)--;
2806     }
2807     iPtr->activeVarTracePtr = active.nextPtr;
2808     Tcl_Release(iPtr);
2809     return code;
2810 }
2811 
2812 /*
2813  *----------------------------------------------------------------------
2814  *
2815  * DisposeTraceResult--
2816  *
2817  *	This function is called to dispose of the result returned from a trace
2818  *	function. The disposal method appropriate to the type of result is
2819  *	determined by flags.
2820  *
2821  * Results:
2822  *	None.
2823  *
2824  * Side effects:
2825  *	The memory allocated for the trace result may be freed.
2826  *
2827  *----------------------------------------------------------------------
2828  */
2829 
2830 static void
DisposeTraceResult(int flags,char * result)2831 DisposeTraceResult(
2832     int flags,			/* Indicates type of result to determine
2833 				 * proper disposal method. */
2834     char *result)		/* The result returned from a trace function
2835 				 * to be disposed. */
2836 {
2837     if (flags & TCL_TRACE_RESULT_DYNAMIC) {
2838 	ckfree(result);
2839     } else if (flags & TCL_TRACE_RESULT_OBJECT) {
2840 	Tcl_DecrRefCount((Tcl_Obj *) result);
2841     }
2842 }
2843 
2844 /*
2845  *----------------------------------------------------------------------
2846  *
2847  * Tcl_UntraceVar --
2848  *
2849  *	Remove a previously-created trace for a variable.
2850  *
2851  * Results:
2852  *	None.
2853  *
2854  * Side effects:
2855  *	If there exists a trace for the variable given by varName with the
2856  *	given flags, proc, and clientData, then that trace is removed.
2857  *
2858  *----------------------------------------------------------------------
2859  */
2860 
2861 #ifndef TCL_NO_DEPRECATED
2862 #undef Tcl_UntraceVar
2863 void
Tcl_UntraceVar(Tcl_Interp * interp,const char * varName,int flags,Tcl_VarTraceProc * proc,ClientData clientData)2864 Tcl_UntraceVar(
2865     Tcl_Interp *interp,		/* Interpreter containing variable. */
2866     const char *varName,	/* Name of variable; may end with "(index)" to
2867 				 * signify an array reference. */
2868     int flags,			/* OR-ed collection of bits describing current
2869 				 * trace, including any of TCL_TRACE_READS,
2870 				 * TCL_TRACE_WRITES, TCL_TRACE_UNSETS,
2871 				 * TCL_GLOBAL_ONLY and TCL_NAMESPACE_ONLY. */
2872     Tcl_VarTraceProc *proc,	/* Function assocated with trace. */
2873     ClientData clientData)	/* Arbitrary argument to pass to proc. */
2874 {
2875     Tcl_UntraceVar2(interp, varName, NULL, flags, proc, clientData);
2876 }
2877 #endif /* TCL_NO_DEPRECATED */
2878 
2879 /*
2880  *----------------------------------------------------------------------
2881  *
2882  * Tcl_UntraceVar2 --
2883  *
2884  *	Remove a previously-created trace for a variable.
2885  *
2886  * Results:
2887  *	None.
2888  *
2889  * Side effects:
2890  *	If there exists a trace for the variable given by part1 and part2 with
2891  *	the given flags, proc, and clientData, then that trace is removed.
2892  *
2893  *----------------------------------------------------------------------
2894  */
2895 
2896 void
Tcl_UntraceVar2(Tcl_Interp * interp,const char * part1,const char * part2,int flags,Tcl_VarTraceProc * proc,ClientData clientData)2897 Tcl_UntraceVar2(
2898     Tcl_Interp *interp,		/* Interpreter containing variable. */
2899     const char *part1,		/* Name of variable or array. */
2900     const char *part2,		/* Name of element within array; NULL means
2901 				 * trace applies to scalar variable or array
2902 				 * as-a-whole. */
2903     int flags,			/* OR-ed collection of bits describing current
2904 				 * trace, including any of TCL_TRACE_READS,
2905 				 * TCL_TRACE_WRITES, TCL_TRACE_UNSETS,
2906 				 * TCL_GLOBAL_ONLY, and TCL_NAMESPACE_ONLY. */
2907     Tcl_VarTraceProc *proc,	/* Function assocated with trace. */
2908     ClientData clientData)	/* Arbitrary argument to pass to proc. */
2909 {
2910     VarTrace *tracePtr;
2911     VarTrace *prevPtr, *nextPtr;
2912     Var *varPtr, *arrayPtr;
2913     Interp *iPtr = (Interp *) interp;
2914     ActiveVarTrace *activePtr;
2915     int flagMask, allFlags = 0;
2916     Tcl_HashEntry *hPtr;
2917 
2918     /*
2919      * Set up a mask to mask out the parts of the flags that we are not
2920      * interested in now.
2921      */
2922 
2923     flagMask = TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY;
2924     varPtr = TclLookupVar(interp, part1, part2, flags & flagMask, /*msg*/ NULL,
2925 	    /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr);
2926     if (varPtr == NULL || !(varPtr->flags & VAR_ALL_TRACES & flags)) {
2927 	return;
2928     }
2929 
2930     /*
2931      * Set up a mask to mask out the parts of the flags that we are not
2932      * interested in now.
2933      */
2934 
2935     flagMask = TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS |
2936 	  TCL_TRACE_ARRAY | TCL_TRACE_RESULT_DYNAMIC | TCL_TRACE_RESULT_OBJECT;
2937 #ifndef TCL_REMOVE_OBSOLETE_TRACES
2938     flagMask |= TCL_TRACE_OLD_STYLE;
2939 #endif
2940     flags &= flagMask;
2941 
2942     hPtr = Tcl_FindHashEntry(&iPtr->varTraces, (char *) varPtr);
2943     for (tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr), prevPtr = NULL; ;
2944 	    prevPtr = tracePtr, tracePtr = tracePtr->nextPtr) {
2945 	if (tracePtr == NULL) {
2946 	    goto updateFlags;
2947 	}
2948 	if ((tracePtr->traceProc == proc) && (tracePtr->flags == flags)
2949 		&& (tracePtr->clientData == clientData)) {
2950 	    break;
2951 	}
2952 	allFlags |= tracePtr->flags;
2953     }
2954 
2955     /*
2956      * The code below makes it possible to delete traces while traces are
2957      * active: it makes sure that the deleted trace won't be processed by
2958      * TclCallVarTraces.
2959      *
2960      * Caveat (Bug 3062331): When an unset trace handler on a variable
2961      * tries to delete a different unset trace handler on the same variable,
2962      * the results may be surprising.  When variable unset traces fire, the
2963      * traced variable is already gone.  So the TclLookupVar() call above
2964      * will not find that variable, and not finding it will never reach here
2965      * to perform the deletion.  This means callers of Tcl_UntraceVar*()
2966      * attempting to delete unset traces from within the handler of another
2967      * unset trace have to account for the possibility that their call to
2968      * Tcl_UntraceVar*() is a no-op.
2969      */
2970 
2971     for (activePtr = iPtr->activeVarTracePtr;  activePtr != NULL;
2972 	    activePtr = activePtr->nextPtr) {
2973 	if (activePtr->nextTracePtr == tracePtr) {
2974 	    activePtr->nextTracePtr = tracePtr->nextPtr;
2975 	}
2976     }
2977     nextPtr = tracePtr->nextPtr;
2978     if (prevPtr == NULL) {
2979 	if (nextPtr) {
2980 	    Tcl_SetHashValue(hPtr, nextPtr);
2981 	} else {
2982 	    Tcl_DeleteHashEntry(hPtr);
2983 	}
2984     } else {
2985 	prevPtr->nextPtr = nextPtr;
2986     }
2987     tracePtr->nextPtr = NULL;
2988     Tcl_EventuallyFree(tracePtr, TCL_DYNAMIC);
2989 
2990     for (tracePtr = nextPtr; tracePtr != NULL;
2991 	    tracePtr = tracePtr->nextPtr) {
2992 	allFlags |= tracePtr->flags;
2993     }
2994 
2995   updateFlags:
2996     varPtr->flags &= ~VAR_ALL_TRACES;
2997     if (allFlags & VAR_ALL_TRACES) {
2998 	varPtr->flags |= (allFlags & VAR_ALL_TRACES);
2999     } else if (TclIsVarUndefined(varPtr)) {
3000 	/*
3001 	 * If this is the last trace on the variable, and the variable is
3002 	 * unset and unused, then free up the variable.
3003 	 */
3004 
3005 	TclCleanupVar(varPtr, NULL);
3006     }
3007 }
3008 
3009 /*
3010  *----------------------------------------------------------------------
3011  *
3012  * Tcl_VarTraceInfo --
3013  *
3014  *	Return the clientData value associated with a trace on a variable.
3015  *	This function can also be used to step through all of the traces on a
3016  *	particular variable that have the same trace function.
3017  *
3018  * Results:
3019  *	The return value is the clientData value associated with a trace on
3020  *	the given variable. Information will only be returned for a trace with
3021  *	proc as trace function. If the clientData argument is NULL then the
3022  *	first such trace is returned; otherwise, the next relevant one after
3023  *	the one given by clientData will be returned. If the variable doesn't
3024  *	exist, or if there are no (more) traces for it, then NULL is returned.
3025  *
3026  * Side effects:
3027  *	None.
3028  *
3029  *----------------------------------------------------------------------
3030  */
3031 
3032 #ifndef TCL_NO_DEPRECATED
3033 #undef Tcl_VarTraceInfo
3034 ClientData
Tcl_VarTraceInfo(Tcl_Interp * interp,const char * varName,int flags,Tcl_VarTraceProc * proc,ClientData prevClientData)3035 Tcl_VarTraceInfo(
3036     Tcl_Interp *interp,		/* Interpreter containing variable. */
3037     const char *varName,	/* Name of variable; may end with "(index)" to
3038 				 * signify an array reference. */
3039     int flags,			/* OR-ed combo or TCL_GLOBAL_ONLY,
3040 				 * TCL_NAMESPACE_ONLY (can be 0). */
3041     Tcl_VarTraceProc *proc,	/* Function assocated with trace. */
3042     ClientData prevClientData)	/* If non-NULL, gives last value returned by
3043 				 * this function, so this call will return the
3044 				 * next trace after that one. If NULL, this
3045 				 * call will return the first trace. */
3046 {
3047     return Tcl_VarTraceInfo2(interp, varName, NULL, flags, proc,
3048 	    prevClientData);
3049 }
3050 #endif /* TCL_NO_DEPRECATED */
3051 
3052 /*
3053  *----------------------------------------------------------------------
3054  *
3055  * Tcl_VarTraceInfo2 --
3056  *
3057  *	Same as Tcl_VarTraceInfo, except takes name in two pieces instead of
3058  *	one.
3059  *
3060  * Results:
3061  *	Same as Tcl_VarTraceInfo.
3062  *
3063  * Side effects:
3064  *	None.
3065  *
3066  *----------------------------------------------------------------------
3067  */
3068 
3069 ClientData
Tcl_VarTraceInfo2(Tcl_Interp * interp,const char * part1,const char * part2,int flags,Tcl_VarTraceProc * proc,ClientData prevClientData)3070 Tcl_VarTraceInfo2(
3071     Tcl_Interp *interp,		/* Interpreter containing variable. */
3072     const char *part1,		/* Name of variable or array. */
3073     const char *part2,		/* Name of element within array; NULL means
3074 				 * trace applies to scalar variable or array
3075 				 * as-a-whole. */
3076     int flags,			/* OR-ed combination of TCL_GLOBAL_ONLY,
3077 				 * TCL_NAMESPACE_ONLY. */
3078     Tcl_VarTraceProc *proc,	/* Function assocated with trace. */
3079     ClientData prevClientData)	/* If non-NULL, gives last value returned by
3080 				 * this function, so this call will return the
3081 				 * next trace after that one. If NULL, this
3082 				 * call will return the first trace. */
3083 {
3084     Interp *iPtr = (Interp *) interp;
3085     Var *varPtr, *arrayPtr;
3086     Tcl_HashEntry *hPtr;
3087 
3088     varPtr = TclLookupVar(interp, part1, part2,
3089 	    flags & (TCL_GLOBAL_ONLY|TCL_NAMESPACE_ONLY), /*msg*/ NULL,
3090 	    /*createPart1*/ 0, /*createPart2*/ 0, &arrayPtr);
3091     if (varPtr == NULL) {
3092 	return NULL;
3093     }
3094 
3095     /*
3096      * Find the relevant trace, if any, and return its clientData.
3097      */
3098 
3099     hPtr = Tcl_FindHashEntry(&iPtr->varTraces, (char *) varPtr);
3100 
3101     if (hPtr) {
3102 	VarTrace *tracePtr = (VarTrace *)Tcl_GetHashValue(hPtr);
3103 
3104 	if (prevClientData != NULL) {
3105 	    for (; tracePtr != NULL; tracePtr = tracePtr->nextPtr) {
3106 		if ((tracePtr->clientData == prevClientData)
3107 			&& (tracePtr->traceProc == proc)) {
3108 		    tracePtr = tracePtr->nextPtr;
3109 		    break;
3110 		}
3111 	    }
3112 	}
3113 	for (; tracePtr != NULL ; tracePtr = tracePtr->nextPtr) {
3114 	    if (tracePtr->traceProc == proc) {
3115 		return tracePtr->clientData;
3116 	    }
3117 	}
3118     }
3119     return NULL;
3120 }
3121 
3122 /*
3123  *----------------------------------------------------------------------
3124  *
3125  * Tcl_TraceVar --
3126  *
3127  *	Arrange for reads and/or writes to a variable to cause a function to
3128  *	be invoked, which can monitor the operations and/or change their
3129  *	actions.
3130  *
3131  * Results:
3132  *	A standard Tcl return value.
3133  *
3134  * Side effects:
3135  *	A trace is set up on the variable given by varName, such that future
3136  *	references to the variable will be intermediated by proc. See the
3137  *	manual entry for complete details on the calling sequence for proc.
3138  *     The variable's flags are updated.
3139  *
3140  *----------------------------------------------------------------------
3141  */
3142 
3143 #ifndef TCL_NO_DEPRECATED
3144 #undef Tcl_TraceVar
3145 int
Tcl_TraceVar(Tcl_Interp * interp,const char * varName,int flags,Tcl_VarTraceProc * proc,ClientData clientData)3146 Tcl_TraceVar(
3147     Tcl_Interp *interp,		/* Interpreter in which variable is to be
3148 				 * traced. */
3149     const char *varName,	/* Name of variable; may end with "(index)" to
3150 				 * signify an array reference. */
3151     int flags,			/* OR-ed collection of bits, including any of
3152 				 * TCL_TRACE_READS, TCL_TRACE_WRITES,
3153 				 * TCL_TRACE_UNSETS, TCL_GLOBAL_ONLY, and
3154 				 * TCL_NAMESPACE_ONLY. */
3155     Tcl_VarTraceProc *proc,	/* Function to call when specified ops are
3156 				 * invoked upon varName. */
3157     ClientData clientData)	/* Arbitrary argument to pass to proc. */
3158 {
3159     return Tcl_TraceVar2(interp, varName, NULL, flags, proc, clientData);
3160 }
3161 #endif /* TCL_NO_DEPRECATED */
3162 
3163 /*
3164  *----------------------------------------------------------------------
3165  *
3166  * Tcl_TraceVar2 --
3167  *
3168  *	Arrange for reads and/or writes to a variable to cause a function to
3169  *	be invoked, which can monitor the operations and/or change their
3170  *	actions.
3171  *
3172  * Results:
3173  *	A standard Tcl return value.
3174  *
3175  * Side effects:
3176  *	A trace is set up on the variable given by part1 and part2, such that
3177  *	future references to the variable will be intermediated by proc. See
3178  *	the manual entry for complete details on the calling sequence for
3179  *	proc. The variable's flags are updated.
3180  *
3181  *----------------------------------------------------------------------
3182  */
3183 
3184 int
Tcl_TraceVar2(Tcl_Interp * interp,const char * part1,const char * part2,int flags,Tcl_VarTraceProc * proc,ClientData clientData)3185 Tcl_TraceVar2(
3186     Tcl_Interp *interp,		/* Interpreter in which variable is to be
3187 				 * traced. */
3188     const char *part1,		/* Name of scalar variable or array. */
3189     const char *part2,		/* Name of element within array; NULL means
3190 				 * trace applies to scalar variable or array
3191 				 * as-a-whole. */
3192     int flags,			/* OR-ed collection of bits, including any of
3193 				 * TCL_TRACE_READS, TCL_TRACE_WRITES,
3194 				 * TCL_TRACE_UNSETS, TCL_GLOBAL_ONLY, and
3195 				 * TCL_NAMESPACE_ONLY. */
3196     Tcl_VarTraceProc *proc,	/* Function to call when specified ops are
3197 				 * invoked upon varName. */
3198     ClientData clientData)	/* Arbitrary argument to pass to proc. */
3199 {
3200     VarTrace *tracePtr;
3201     int result;
3202 
3203     tracePtr = (VarTrace *)ckalloc(sizeof(VarTrace));
3204     tracePtr->traceProc = proc;
3205     tracePtr->clientData = clientData;
3206     tracePtr->flags = flags;
3207 
3208     result = TraceVarEx(interp, part1, part2, tracePtr);
3209 
3210     if (result != TCL_OK) {
3211 	ckfree(tracePtr);
3212     }
3213     return result;
3214 }
3215 
3216 /*
3217  *----------------------------------------------------------------------
3218  *
3219  * TraceVarEx --
3220  *
3221  *	Arrange for reads and/or writes to a variable to cause a function to
3222  *	be invoked, which can monitor the operations and/or change their
3223  *	actions.
3224  *
3225  * Results:
3226  *	A standard Tcl return value.
3227  *
3228  * Side effects:
3229  *	A trace is set up on the variable given by part1 and part2, such that
3230  *	future references to the variable will be intermediated by the
3231  *	traceProc listed in tracePtr. See the manual entry for complete
3232  *	details on the calling sequence for proc.
3233  *
3234  *----------------------------------------------------------------------
3235  */
3236 
3237 static int
TraceVarEx(Tcl_Interp * interp,const char * part1,const char * part2,VarTrace * tracePtr)3238 TraceVarEx(
3239     Tcl_Interp *interp,		/* Interpreter in which variable is to be
3240 				 * traced. */
3241     const char *part1,		/* Name of scalar variable or array. */
3242     const char *part2,		/* Name of element within array; NULL means
3243 				 * trace applies to scalar variable or array
3244 				 * as-a-whole. */
3245     VarTrace *tracePtr)/* Structure containing flags, traceProc and
3246 				 * clientData fields. Others should be left
3247 				 * blank. Will be ckfree()d (eventually) if
3248 				 * this function returns TCL_OK, and up to
3249 				 * caller to free if this function returns
3250 				 * TCL_ERROR. */
3251 {
3252     Interp *iPtr = (Interp *) interp;
3253     Var *varPtr, *arrayPtr;
3254     int flagMask, isNew;
3255     Tcl_HashEntry *hPtr;
3256 
3257     /*
3258      * We strip 'flags' down to just the parts which are relevant to
3259      * TclLookupVar, to avoid conflicts between trace flags and internal
3260      * namespace flags such as 'TCL_FIND_ONLY_NS'. This can now occur since we
3261      * have trace flags with values 0x1000 and higher.
3262      */
3263 
3264     flagMask = TCL_GLOBAL_ONLY | TCL_NAMESPACE_ONLY;
3265     varPtr = TclLookupVar(interp, part1, part2,
3266 	    (tracePtr->flags & flagMask) | TCL_LEAVE_ERR_MSG,
3267 	    "trace", /*createPart1*/ 1, /*createPart2*/ 1, &arrayPtr);
3268     if (varPtr == NULL) {
3269 	return TCL_ERROR;
3270     }
3271 
3272     /*
3273      * Check for a nonsense flag combination. Note that this is a Tcl_Panic()
3274      * because there should be no code path that ever sets both flags.
3275      */
3276 
3277     if ((tracePtr->flags & TCL_TRACE_RESULT_DYNAMIC)
3278 	    && (tracePtr->flags & TCL_TRACE_RESULT_OBJECT)) {
3279 	Tcl_Panic("bad result flag combination");
3280     }
3281 
3282     /*
3283      * Set up trace information.
3284      */
3285 
3286     flagMask = TCL_TRACE_READS | TCL_TRACE_WRITES | TCL_TRACE_UNSETS |
3287 	  TCL_TRACE_ARRAY | TCL_TRACE_RESULT_DYNAMIC | TCL_TRACE_RESULT_OBJECT;
3288 #ifndef TCL_REMOVE_OBSOLETE_TRACES
3289     flagMask |= TCL_TRACE_OLD_STYLE;
3290 #endif
3291     tracePtr->flags = tracePtr->flags & flagMask;
3292 
3293     hPtr = Tcl_CreateHashEntry(&iPtr->varTraces, varPtr, &isNew);
3294     if (isNew) {
3295 	tracePtr->nextPtr = NULL;
3296     } else {
3297 	tracePtr->nextPtr = (VarTrace *)Tcl_GetHashValue(hPtr);
3298     }
3299     Tcl_SetHashValue(hPtr, tracePtr);
3300 
3301     /*
3302      * Mark the variable as traced so we know to call them.
3303      */
3304 
3305     varPtr->flags |= (tracePtr->flags & VAR_ALL_TRACES);
3306 
3307     return TCL_OK;
3308 }
3309 
3310 /*
3311  * Local Variables:
3312  * mode: c
3313  * c-basic-offset: 4
3314  * fill-column: 78
3315  * End:
3316  */
3317