Author: Donal K. Fellows <[email protected]>
This TIP proposes an extension to the Tcl_TraceVar API to cope with dynamically allocated results.
The current API for handling errors during variable accesses is based on static strings, which is perfectly adequate for the errors that Tcl generates of its own accord, but which is substantially at odds with the setting of traces on variables which may produce errors. The problem is that as those errors come from a Tcl script, they are allocated dynamically and fail to satisfy the static allocation rule mentioned previously. Normally this does not cause a problem, but under some circumstances (as set out in Bug #219393 http://sf.net/tracker/?func=detail&aid=219393&group\_id=10894&atid=110894\) it is possible for this to cause a memory fault or even memory corruption. This is because it can sometimes happen that the pointer to the supposedly static string winds up dangling as the string it was pointing to gets deleted out from underneath it (the storage area used to static-ify the string is part of the trace structure, but the trace is permitted to delete that structure...) Obviously this is not desirable!
There are several possible fixes, but the two main ones are to:
use the Tcl_Preserve mechanism to postpone deletion of the allocated memory block until it has been copied into something more permanent.
add special handling so as to mark the error result coming back from the trace mechanism as something other than a static string. The main alternatives here are:
* A dynamically-allocated C string, to be disposed of with ckfree.
* A dynamically-allocated Tcl_Obj reference, to be disposed of with a single call to Tcl_DecrRefCount.
Although option 1 is the easiest to implement, it has the disadvantage of putting a new non-obvious requirement on all variable traces, and that is that their results are all _Tcl_Preserve_d before the end of the trace. This is feasible for the Tcl core, but unreasonable to ask of extension writers.
Instead I prefer option 2, and it is possible to introduce this change in such a way that existing software does not see an API change (i.e. there are no serious backward-compatibility issues) and both styles of result listed above are supported. The advantage of supporting both of these is that dynamically allocated strings are a very easy interface for extension writers to use though not particularly efficient, and objects are a very efficient interface well-suited to the core itself but are not as easy to use. (It is far easier to adapt existing code to use dynamic strings as no understanding of lifespan management is required.)
To achieve this, the following new flags will be defined:
#define TCL_TRACE_RESULT_DYNAMIC 0x2000
#define TCL_TRACE_RESULT_OBJECT 0x4000
These flags, when passed to the flags argument of Tcl_TraceVar (and related functions) alter the interpretation of the value returned by the call to the proc parameter from the default behaviour (a static string) to be either a string to be deallocated by Tcl as and when it sees fit using ckfree (when TCL_TRACE_RESULT_DYNAMIC is specified) or to be a Tcl_Obj (which must be cast to a char * for type compatibility) to be disposed of when the error message is no longer required (when TCL_TRACE_RESULT_OBJECT is specified.) It is an error to specify both flags on the same call.
The core will then be modified to use this mechanism for variable traces as set up by the trace command.
This TIP is placed in the public domain.
For reference, the pre-TIP definition of the Tcl_TraceVar function is as follows:
Tcl_TraceVar(Tcl_Interp *interp, char *varName, int flags,
Tcl_VarTraceProc *proc, ClientData clientData)
(There is a corresponding function that takes the variable name as a pair of strings.) All parameters have the usual obvious interpretations, with the flags being an OR-ed combination of the following flags:
TCL_TRACE_READS: Invoke the callback when the variable is read.
TCL_TRACE_WRITES: Invoke the callback when the variable is written.
TCL_TRACE_UNSETS: Invoke the callback when the variable is unset.
TCL_TRACE_ARRAY: Invoke the callback when the variable is accessed as an array.
TCL_GLOBAL_ONLY: Force the lookup of the variable in the global scope, and not the current one.