/* * tclXdebug.c -- * * Tcl command execution trace command. *----------------------------------------------------------------------------- * Copyright 1991-1999 Karl Lehenbauer and Mark Diekhans. * * Permission to use, copy, modify, and distribute this software and its * documentation for any purpose and without fee is hereby granted, provided * that the above copyright notice appear in all copies. Karl Lehenbauer and * Mark Diekhans make no representations about the suitability of this * software for any purpose. It is provided "as is" without express or * implied warranty. *----------------------------------------------------------------------------- * $Id: tclXdebug.c,v 1.2 2002/04/03 02:50:35 hobbs Exp $ *----------------------------------------------------------------------------- */ #include "tclExtdInt.h" /* * Client data structure for the cmdtrace command. */ #define ARG_TRUNCATE_SIZE 40 #define CMD_TRUNCATE_SIZE 60 typedef struct traceInfo_t { Tcl_Interp *interp; Tcl_Trace traceId; int inTrace; int noEval; int noTruncate; int procCalls; int depth; char *callback; Tcl_Obj *errorStatePtr; Tcl_AsyncHandler errorAsyncHandler; Tcl_Channel channel; } traceInfo_t, *traceInfo_pt; /* * Prototypes of internal functions. */ static void TraceDelete (Tcl_Interp *interp, traceInfo_pt infoPtr); static void PrintStr (Tcl_Channel channel, const char *string, int numChars, int quoted); static void PrintArg (Tcl_Channel channel, const char *argStr, int noTruncate); static void TraceCode (traceInfo_pt infoPtr, int level, char *command, int argc, const char **argv); static int TraceCallbackErrorHandler (ClientData clientData, Tcl_Interp *interp, int code); static void TraceCallBack (Tcl_Interp *interp, traceInfo_pt infoPtr, int level, char *command, int argc, const char **argv); static void CmdTraceRoutine (ClientData clientData, Tcl_Interp *interp, int level, char *command, Tcl_CmdProc *cmdProc, ClientData cmdClientData, int argc, const char **argv); static int TclX_CmdtraceObjCmd (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]); static void DebugCleanUp (ClientData clientData, Tcl_Interp *interp); /*----------------------------------------------------------------------------- * TraceDelete -- * * Delete the trace if active, reseting the structure. *----------------------------------------------------------------------------- */ static void TraceDelete (Tcl_Interp *interp, traceInfo_pt infoPtr) { if (infoPtr->traceId != NULL) { Tcl_DeleteTrace (interp, infoPtr->traceId); infoPtr->depth = 0; infoPtr->traceId = NULL; if (infoPtr->callback != NULL) { ckfree (infoPtr->callback); infoPtr->callback = NULL; } } if (infoPtr->errorAsyncHandler != NULL) { Tcl_AsyncDelete (infoPtr->errorAsyncHandler); infoPtr->errorAsyncHandler = NULL; } } /*----------------------------------------------------------------------------- * PrintStr -- * * Print an string, truncating it to the specified number of characters. * If the string contains newlines, \n is substituted. *----------------------------------------------------------------------------- */ static void PrintStr (Tcl_Channel channel, const char *string, int numChars, int quoted) { int idx; if (quoted) Tcl_Write (channel, "{", 1); for (idx = 0; idx < numChars; idx++) { if (string [idx] == '\n') { Tcl_Write (channel, "\\n", 2); } else { Tcl_Write (channel, &(string [idx]), 1); } } if (numChars < (int) strlen (string)) Tcl_Write (channel, "...", 3); if (quoted) Tcl_Write (channel, "}", 1); } /*----------------------------------------------------------------------------- * PrintArg -- * * Print an argument string, truncating and adding "..." if its longer * then ARG_TRUNCATE_SIZE. If the string contains white spaces, quote * it with braces. *----------------------------------------------------------------------------- */ static void PrintArg (Tcl_Channel channel, const char *argStr, int noTruncate) { int idx, argLen, printLen; int quoted; argLen = strlen (argStr); printLen = argLen; if ((!noTruncate) && (printLen > ARG_TRUNCATE_SIZE)) printLen = ARG_TRUNCATE_SIZE; quoted = (printLen == 0); for (idx = 0; idx < printLen; idx++) if (ISSPACE (argStr [idx])) { quoted = TRUE; break; } PrintStr (channel, argStr, printLen, quoted); } /*----------------------------------------------------------------------------- * TraceCode -- * * Print out a trace of a code line. Level is used for indenting * and marking lines and may be eval or procedure level. *----------------------------------------------------------------------------- */ static void TraceCode (traceInfo_pt infoPtr, int level, char *command, int argc, const char **argv) { int idx, printLen; char buf [32]; sprintf (buf, "%2d:", level); Tcl_Write(infoPtr->channel, buf, -1); if (level > 20) level = 20; for (idx = 0; idx < level; idx++) Tcl_Write (infoPtr->channel, " ", 2); if (infoPtr->noEval) { printLen = strlen (command); if ((!infoPtr->noTruncate) && (printLen > CMD_TRUNCATE_SIZE)) printLen = CMD_TRUNCATE_SIZE; PrintStr (infoPtr->channel, (const char *) command, printLen, FALSE); } else { for (idx = 0; idx < argc; idx++) { if (idx > 0) Tcl_Write (infoPtr->channel, " ", 1); PrintArg (infoPtr->channel, argv [idx], infoPtr->noTruncate); } } TclX_WriteNL (infoPtr->channel); Tcl_Flush (infoPtr->channel); } /*----------------------------------------------------------------------------- * TraceCallbackErrorHandler -- * * Async handler that processes an callback error. Generates either an * immediate or background error. *----------------------------------------------------------------------------- */ static int TraceCallbackErrorHandler (ClientData clientData, Tcl_Interp *interp, int code) { traceInfo_pt infoPtr = (traceInfo_pt) clientData; /* * Put back error message and state. If not interp passed in, the error * is handled in the background. */ TclX_RestoreResultErrorInfo (infoPtr->interp, infoPtr->errorStatePtr); infoPtr->errorStatePtr = NULL; if (interp == NULL) { Tcl_BackgroundError (infoPtr->interp); } TraceDelete (interp, infoPtr); return TCL_ERROR; } /*----------------------------------------------------------------------------- * TraceCallBack -- * * Build and call a callback for the command that was just executed. The * following arguments are appended to the command: * 1) command - A string containing the text of the command, before any * argument substitution. * 2) argv - A list of the final argument information that will be passed to * the command after command, variable, and backslash substitution. * 3) evalLevel - The Tcl_Eval level. * 4) procLevel - The procedure level. * The code should allow for additional substitution of arguments in future * versions (such as a procedure with args as the last argument). The value * of result, errorInfo and errorCode are preserved. All other state must be * preserved by the procedure. An error will result in an error being flagged * in the control block and async mark being called to handle the error * once the command has completed. *----------------------------------------------------------------------------- */ static void TraceCallBack (Tcl_Interp *interp, traceInfo_pt infoPtr, int level, char *command, int argc, const char **argv) { Interp *iPtr = (Interp *) interp; Tcl_DString callback; Tcl_Obj *saveObjPtr; char *cmdList; char numBuf [32]; Tcl_DStringInit (&callback); /* * Build the command to evaluate. */ Tcl_DStringAppend (&callback, infoPtr->callback, -1); Tcl_DStringStartSublist (&callback); Tcl_DStringAppendElement (&callback, command); Tcl_DStringEndSublist (&callback); Tcl_DStringStartSublist (&callback); cmdList = Tcl_Merge (argc, argv); Tcl_DStringAppendElement (&callback, cmdList); ckfree (cmdList); Tcl_DStringEndSublist (&callback); sprintf (numBuf, "%d", level); Tcl_DStringAppendElement (&callback, numBuf); sprintf (numBuf, "%d", ((iPtr->varFramePtr == NULL) ? 0 : iPtr->varFramePtr->level)); Tcl_DStringAppendElement (&callback, numBuf); saveObjPtr = TclX_SaveResultErrorInfo (interp); /* * Evaluate the command. If an error occurs, set up the handler to be * called when its possible. */ if (Tcl_Eval (interp, Tcl_DStringValue (&callback)) == TCL_ERROR) { Tcl_AddObjErrorInfo (interp, "\n (\"cmdtrace\" callback command)", -1); infoPtr->errorStatePtr = TclX_SaveResultErrorInfo (interp); Tcl_AsyncMark (infoPtr->errorAsyncHandler); } TclX_RestoreResultErrorInfo (interp, saveObjPtr); Tcl_DStringFree (&callback); } /*----------------------------------------------------------------------------- * CmdTraceRoutine -- * * Routine called by Tcl_Eval to trace a command. *----------------------------------------------------------------------------- */ static void CmdTraceRoutine (ClientData clientData, Tcl_Interp *interp, int level, char *command, Tcl_CmdProc *cmdProc, ClientData cmdClientData, int argc, const char **argv) { Interp *iPtr = (Interp *) interp; traceInfo_pt infoPtr = (traceInfo_pt) clientData; int procLevel; /* * If we are in an error. */ if (infoPtr->inTrace || (infoPtr->errorStatePtr != NULL)) { return; } infoPtr->inTrace = TRUE; if (infoPtr->procCalls) { if (TclFindProc (iPtr, argv [0]) != NULL) { if (infoPtr->callback != NULL) { TraceCallBack (interp, infoPtr, level, command, argc, argv); } else { procLevel = (iPtr->varFramePtr == NULL) ? 0 : iPtr->varFramePtr->level; TraceCode (infoPtr, procLevel, command, argc, argv); } } } else { if (infoPtr->callback != NULL) { TraceCallBack (interp, infoPtr, level, command, argc, argv); } else { TraceCode (infoPtr, level, command, argc, argv); } } infoPtr->inTrace = FALSE; } /*----------------------------------------------------------------------------- * Tcl_CmdtraceObjCmd -- * * Implements the TCL trace command: * cmdtrace level|on ?noeval? ?notruncate? ?procs? ?fileid? ?command cmd? * cmdtrace off * cmdtrace depth *----------------------------------------------------------------------------- */ static int TclX_CmdtraceObjCmd (ClientData clientData, Tcl_Interp *interp, int objc, Tcl_Obj *CONST objv[]) { traceInfo_pt infoPtr = (traceInfo_pt) clientData; int idx; char *argStr, *callback; Tcl_Obj *channelId; if (objc < 2) goto argumentError; argStr = Tcl_GetStringFromObj (objv [1], NULL); /* * Handle `depth' sub-command. */ if (STREQU (argStr, "depth")) { if (objc != 2) goto argumentError; Tcl_SetIntObj (Tcl_GetObjResult (interp), infoPtr->depth); return TCL_OK; } /* * If a trace is in progress, delete it now. */ TraceDelete (interp, infoPtr); /* * Handle off sub-command. */ if (STREQU (argStr, "off")) { if (objc != 2) goto argumentError; return TCL_OK; } infoPtr->noEval = FALSE; infoPtr->noTruncate = FALSE; infoPtr->procCalls = FALSE; infoPtr->channel = NULL; channelId = NULL; callback = NULL; if (STREQU (argStr, "on")) { infoPtr->depth = MAXINT; } else { if (Tcl_GetIntFromObj (interp, objv [1], &(infoPtr->depth)) != TCL_OK) return TCL_ERROR; } for (idx = 2; idx < objc; idx++) { argStr = Tcl_GetStringFromObj (objv [idx], NULL); if (STREQU (argStr, "notruncate")) { if (infoPtr->noTruncate) goto argumentError; infoPtr->noTruncate = TRUE; continue; } if (STREQU (argStr, "noeval")) { if (infoPtr->noEval) goto argumentError; infoPtr->noEval = TRUE; continue; } if (STREQU (argStr, "procs")) { if (infoPtr->procCalls) goto argumentError; infoPtr->procCalls = TRUE; continue; } if (STRNEQU (argStr, "std", 3) || STRNEQU (argStr, "file", 4)) { if (channelId != NULL) goto argumentError; if (callback != NULL) goto mixCommandAndFile; channelId = objv [idx]; continue; } if (STREQU (argStr, "command")) { if (callback != NULL) goto argumentError; if (channelId != NULL) goto mixCommandAndFile; if (idx == objc - 1) goto missingCommand; callback = Tcl_GetStringFromObj (objv [++idx], NULL); continue; } goto invalidOption; } if (callback != NULL) { infoPtr->callback = ckstrdup (callback); infoPtr->errorAsyncHandler = Tcl_AsyncCreate (TraceCallbackErrorHandler, (ClientData) infoPtr); } else { if (channelId == NULL) { infoPtr->channel = TclX_GetOpenChannel (interp, "stdout", TCL_WRITABLE); } else { infoPtr->channel = TclX_GetOpenChannelObj (interp, channelId, TCL_WRITABLE); } if (infoPtr->channel == NULL) return TCL_ERROR; } infoPtr->traceId = Tcl_CreateTrace (interp, infoPtr->depth, (Tcl_CmdTraceProc*) CmdTraceRoutine, (ClientData) infoPtr); return TCL_OK; argumentError: TclX_AppendObjResult (interp, tclXWrongArgs, objv [0], " level | on ?noeval? ?notruncate? ?procs?", "?fileid? ?command cmd? | off | depth", (char *) NULL); return TCL_ERROR; missingCommand: TclX_AppendObjResult (interp, "command option requires an argument", (char *) NULL); return TCL_ERROR; mixCommandAndFile: TclX_AppendObjResult (interp, "can not specify both the command option ", "and a file handle", (char *) NULL); return TCL_ERROR; invalidOption: TclX_AppendObjResult (interp, "invalid option: expected ", "one of \"noeval\", \"notruncate\", \"procs\", ", "\"command\", or a file id", (char *) NULL); return TCL_ERROR; } /*----------------------------------------------------------------------------- * DebugCleanUp -- * * Release the debug data area when the interpreter is deleted. *----------------------------------------------------------------------------- */ static void DebugCleanUp (ClientData clientData, Tcl_Interp *interp) { traceInfo_pt infoPtr = (traceInfo_pt) clientData; TraceDelete (interp, infoPtr); ckfree ((char *) infoPtr); } /*----------------------------------------------------------------------------- * TclX_DebugInit -- * * Initialize the TCL debugging commands. *----------------------------------------------------------------------------- */ void TclX_DebugInit (Tcl_Interp *interp) { traceInfo_pt infoPtr; infoPtr = (traceInfo_pt) ckalloc (sizeof (traceInfo_t)); infoPtr->interp = interp; infoPtr->traceId = NULL; infoPtr->inTrace = FALSE; infoPtr->noEval = FALSE; infoPtr->noTruncate = FALSE; infoPtr->procCalls = FALSE; infoPtr->depth = 0; infoPtr->callback = NULL; infoPtr->errorStatePtr = NULL; infoPtr->errorAsyncHandler = NULL; infoPtr->channel = NULL; Tcl_CallWhenDeleted (interp, DebugCleanUp, (ClientData) infoPtr); Tcl_CreateObjCommand (interp, "cmdtrace", TclX_CmdtraceObjCmd, (ClientData) infoPtr, (Tcl_CmdDeleteProc*) NULL); } /* vim: set ts=4 sw=4 sts=4 et : */