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