Triggering an error on unsetting a traced variable - tcl

I'm trying to create some read-only variables to use with code evaluated in a safe interp. Using trace, I can generate an error on attempts to set them, but not when using unset:
% set foo bar
bar
% trace add variable foo {unset write} {apply {{var _ op} { error "$var $op trace triggered" }}}
% set foo bar
can't set "foo": foo write trace triggered
% unset foo
%
Indeed, I eventually noticed the documentation even says in passing:
Any errors in unset traces are ignored.
Playing around with different return codes, including custom numbers, they all seem to be ignored. It doesn't trigger an interp bgerror handler either. Is there any other way to raise an error for an attempt to unset a particular variable?

There really isn't. The key problem is that there are times when Tcl is going to unset a variable when that variable really is going to be deleted because its containing structure (a namespace, stack frame or object, and ultimately an interpreter) is also being deleted. The variable is doomed at that point and user code cannot prevent it (except by the horrible approach of never returning from the trace, of course, which infinitely postpones the death and puts everything in a weird state; don't do that). There's simply nowhere to resurrect the variable to. Command deletion traces have the same issue; they too can be firing because their storage is vanishing. (TclOO destructors are a bit more protected against this; they try to not lose errors — there's even pitching them into interp bgerror as a last resort — but still can in some edge cases.)
What's more, there's currently nothing in the API to allow an error message to bubble out of the process of deleting a namespace or call frame. I think that would be fixable (it would require changing some public APIs) but for good reasons I think the deletion would still have to happen, especially for stack frames. Additionally, I'm not sure what should happen when you delete a namespace containing two unset-traced variables whose traces both report errors. What should the error be? I really don't know. (I know that the end result has to be that the namespace is still gone, FWIW, but the details matter and I have no idea what they should be.)

I'm trying to create some read-only variables to use with code evaluated
Schelte and Donal have already offered timely and in-depth feedback. So what comes is meant as a humble addition. Now that one knows that there variables traces are executed after the fact, the below is how I use to mimick read-only (or rather keep-re_setting-to-a-one-time-value) variables using traces (note: as Donal explains, this does not extend to proc-local variables).
The below implementation allows for the following:
namespace eval ::ns2 {}
namespace eval ::ns1 {
readOnly foo 1
readOnly ::ns2::bar 2
readOnly ::faz 3
}
Inspired by variable, but only for one variable-value pair.
proc ::readOnly {var val} {
uplevel [list variable $var $val]
if {![string match "::*" $var]} {
set var [uplevel [list namespace which -variable $var]]
}
# only proceed iff namespace is not under deletion!
if {[namespace exists [namespace qualifiers $var]]} {
set readOnlyHandler {{var val _ _ op} {
if {[namespace exists [namespace qualifiers $var]]} {
if {$op eq "unset"} {
::readOnly $var $val
} else {
set $var $val
}
# optional: use stderr as err-signalling channel?
puts stderr [list $var is read-only]
}
}}
set handlerScript [list apply $readOnlyHandler $var $val]
set traces [trace info variable $var]
set varTrace [list {write unset} $handlerScript]
if {![llength $traces] || $varTrace ni $traces} {
trace add variable $var {*}$varTrace
}
}
}
Some notes:
This is meant to work only for global or otherwise namespaced variables, not for proc-local ones;
It wraps around variable;
[namespace exists ...]: These guards protect from operations when a given parent namespace is currently under deletion (namespace delete ::ns1, or child interp deletion);
In the unset case, the handler script re-adds the trace on the well re-created variable (otherwise, any subsequent write would not be caught anymore.);
[trace info variable ...]: Helps avoid adding redundant traces;
[namespace which -variable]: Makes sure to work on a fully-qualified variable name;
Some final remarks:
Ooo, maybe I can substitute the normal unset for a custom version and
do the checking in it instead of relying on trace
Certainly one option, but it does not give you coverage of the various (indirect) paths of unsetting a variable.
[...] in a safe interp.
You may want to interp alias between a variable in your safe interp to the above readOnly in the parent interp?

Related

Why does this upvar refer to the global context? Or can upvar be used in a coroutine?

The following strained example, in attempt to match my real situation, completes successfully but not the way I'd like it to; nor do I understand why or can find an answer in one of my books.
I can see that proc func was called from the global level and, in turn, initially called proc cofunc; and, perhaps, that sets what upvar will use. But proc finalize calls the coroutine again and passes the variable name nbr to cofunc--which calls add. In this respect add "seems" to be two levels from finalize.
The upvar 2 $varName n in proc add is the variable name nbr and takes its value from the global variable of that name. I'd like to get it from proc finalize (so 15, not 85) which seems to be two levels away from proc add but apparently is not even in the list of caller contexts.
I assume [yield] is the reason; but would you please explain why and, more importantly, if the value in proc finalize was not a number but a "large" value that shouldn't be passed around, is it possible to upvar or uplevel from a coroutine?
Thank you for considering my question.
proc cofunc {callback vals} {
set var [yield]
$callback $vals $var
}
proc func {callback vals} {
set nbr 22
coroutine coro cofunc $callback $vals
}
proc add {vals varName} {
upvar 2 $varName n
chan puts stdout "varName: $varName; n: $n"
# => varName: nbr; n: 85
chan puts stdout [expr {[lindex $vals 0]+[lindex $vals 1]+$n}];
# => 115
}
proc finalize {nbr} {
coro nbr
}
set nbr 85
func add {10 20}
after 500 finalize 15
after 1000 set forever 1
vwait forever
The upvar (like the uplevel command) is defined in terms of the stack. The number argument says how many steps up the stack to take (or you can prefix the number with # to count down from the other end; that's a bit less common); it defaults to 1, the caller of the current procedure. There are two sorts of stack frame, one for procedures (and related entities, such as methods and lambda expressions) and another for direct namespaces (the global scope, namespace eval, and a few other things). All upvar does is go to the indicated stack frame, look up the named variable there, and make a local variable that is a link to it. By "link", I mean that the local variable is really a tagged C pointer to the implementation of the other variable. Once a linked variable is set up, access is very fast, as the slow part of access is resolving names to implementations, and local variables are usually compiled into indexed accesses.
The global and namespace upvar commands do something very similar, but with different lookup strategies. (Also, global assumes that the global and local names should match.) That's also part of what the variable command does.
In your code sample, the upvar 2 is to get past the cofunc procedure. I think it would be clearer if it was upvar #0, or if cofunc used tailcall $callback so a simple upvar could be used. And I'm pretty sure that the nbr accessed in add is not the same one as in func, as the latter's stack frame will be gone by then.

Why curly braces allow variable substitution?

Tcl manuals say that curly braces do not allow variable substitution.
However this works only with some commands but not with others.
What is the difference and how to identify the cases where the substitution will occur and the cases where it won't occur?
% set x 3
3
% puts {$x}
$x
% expr {$x}
3
Referring to the list of standard commands: any command that takes a "body" or "script" argument will eventually evaluate that body as code. With no guarantees about exhaustiveness:
after, apply, catch, eval, expr, fileevent (and chan event), for, foreach, if, interp eval, lmap, some namespace subcommands, some oo::* commands, proc, subst, switch, try, uplevel, while
This is truly one of Tcl's greatest strengths. It gives you the power to easily write your own control structures. For example, Tcl does not provide a do-while loop, but you can do this:
proc do {body while condition} {
if {$while ni {while until}} {
error "some message about usage..."
}
while true {
uplevel 1 $body
set status [uplevel 1 [list expr $condition]]
if {$while eq "while" && !$status} then break
if {$while eq "until" && $status} then break
}
}
so that
% set i 0; while {[incr i] < 3} {puts "$i"}
1
2
% set i 0; do {puts "$i"} while {[incr i] < 3}
0
1
2
% set i 0; do {puts "$i"} until {[incr i] == 3}
0
1
2
Some commands are explicitly described as treating an argument or arguments as a script or an expression; when evaluation of the script or expression happens (which might be immediately, or might be later, depending on the command) the substitutions described inside that string that is a script or expression are performed. (The subst command is a special case that can only apply a selected subset of substitutions.)
How do you know which is which? It depends on the command. Literally. Go and read the documentation. For example, in the documentation for catch we see:
SYNOPSIS
catch script ?resultVarName? ?optionsVarName?
DESCRIPTION
The catch command may be used to prevent errors from aborting command interpretation. The catch command calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script. […]
In this case, we see that the first argument is always evaluated (immediately) as a Tcl script by calling the Tcl interpreter (or rather it's actually bytecode compiled in most cases, but that's an implementation detail).
Similarly, in the documentation for proc we see:
SYNOPSIS
proc name args body
DESCRIPTION
The proc command creates a new Tcl procedure named name, replacing any existing command or procedure there may have been by that name. Whenever the new command is invoked, the contents of body will be executed by the Tcl interpreter. […]
In this case, it's the body that is going to be evaluated as a script (“by the Tcl interpreter” is a form of language that means that) but later, when the procedure is called. (catch said nothing about that; by implication, it acts immediately.)
A third case is the documentation for while:
SYNOPSIS
while test body
DESCRIPTION
The while command evaluates test as an expression (in the same way that expr evaluates its argument). The value of the expression must a proper boolean value; if it is a true value then body is executed by passing it to the Tcl interpreter. […]
From this, we can see that the test argument is an expression (which uses expression rules) and body is a script.
If you want to create a substitution-free single-command script where you can use arbitrary values for everything (this perfect for setting up a callback) use the list command as that is defined to produce lists in canonical form, which happens (by design) to be exactly the form that single commands without substitution-surprises can take:
set xyz "123 456"
set callback [list puts $xyz]
set xyz {[crash bang wallop]}
puts "READY..."
eval $callback

invalid command name "2001:172:16:21::36"

I need to assign my final variable with the following string UDP6:[2001:172:16:21::36]
set ipAddr1 "UDP6,2001:172:16:21::36"
set ipAddrArr [split $ipAddr1 ","]
set ipAddrArr11 [lindex $ipAddrArr 0]
set ipAddrArr12 [lindex $ipAddrArr 1]
set tmp ":\["
set ipAddr1Part [join "$ipAddrArr11 $ipAddrArr12" $tmp]
set tmp1 "]"
set ipAddrFinal [join "$ipAddr1Part$tmp1"]
When I run the tcl script, it gives invalid command name as 2001:172:16:21::36.
I have printed ipAddrFinal value , it gives the expected one UDP6:[2001:172:16:21::36]
pls help me out? what am I missing
The script as you have written it works fine; it assigns the string UDP6:[2001:172:16:21::36] to the variable ipAddrFinal. However, since it contains characters that are Tcl metacharacters in some contexts, I suspect that you are then using the string in an unsafe way, most likely with eval or possibly with subst or uplevel. If you look at the stack trace of the error (in the errorInfo global variable by default) you should be told pretty exactly where the offending code is; it might give a few places you need to look, but it usually isn't too hard to hunt down where the problem originates from.
If your problem comes from uplevel, you are probably going to need to use list to construct a command; 99.99% of all problems with uplevel are handled that way. If your problems come from eval, the chance's good that you need to switch to using expansion syntax. If your problems come from subst or are otherwise still deeply confusing, check back with us (with your stack trace if you are not sure where the problem is coming from).
Example of a fix for eval:
Change:
set action "puts \"IP\\ address\\ is\\ $ipAddrFinal\""
eval $action
to:
set action [list puts "IP address is $ipAddrFinal"]
{*}$action
NB: The error from doing the eval is a reasonable example too:
invalid command name "2001:172:16:21::36"
while executing
"2001:172:16:21::36"
("eval" body line 1)
invoked from within
"eval $action"
Note that it says that it's in an eval, and that points squarely to unsafe script construction. The list command does safe script construction as one of its bonus superpowers.

Procedure tracking in log with proc renaiming

I have a custom test tool written in tcl and bash (mainly in tcl, some initial config and check were done by bash). It isn't have an exact starting point, the outside bash (and sometimes the application which is tested) call specific functions which they find with a "tclIndex" file, created by auto_mkindex.
This tool create a log file, with many "puts" function, which is directed to the file location.
Most of the functions have a "trackBegin" function call at the beginning, and one "trackEnd" function at the end of it. These two get the functions name as parameter. This help us to track where is the problem.
Sadly, this tracker was forgotten in some modification in the near past, and its not even too reliable because its not going to track if there is any abnormal exit in the function. Now, i tried to remove all of them, and create a renamed _proc to override the original and place this two tracker before and after the execution of the function itself.
But i have a lots of error (some i solved, but i dont know its the best way, some are not solved at all, so i'm stuck), these are the main ones:
Because there is no exact entry point, where should i define and how, this overrided proc, to work on all of the procedures in this execution? Some of my files had to be manually modified to _proc to work (mostly the ones where there are code outside the procedures and these files as scripts were called, not functions through the tclIndex, the function called ones are all in a utils folder, and only there, maybe it can help).
In the tracker line i placed a "clock" with format, and its always cause abnormal exit.
I had problems with the returned values (if there was one, and some time when there isn't). Even when that was a return, or Exit.
So my question is in short:
How can i solve an overrided proc function, which will write into a logfile a "begin" and "end" block before and after the procedure itself (The log file location was gained from the bash side of this tool), when there is no clear entry point in this tool for the tcl side, and use an auto_mkindex generated procedure index file?
Thanks,
Roland.
Untested
Assuming your bash script does something like
tclsh file.tcl
You could do
tclsh instrumented.tcl file.tcl
where instrumented.tcl would contain
proc trackBegin {name} {...}
proc trackEnd {name output info} {...}
rename proc _proc
_proc proc {name args body} {
set new_body [format {
trackBegin %s
catch {%s} output info
trackEnd %s $output $info
} $name $body $name]
_proc $name $args $new_body
}
source [lindex $argv 0]
See the return and catch pages for what to do with the info dictionary.
You'll have to show us some of your code to provide more specific help, particularly for your clock error.
I'd be tempted to use execution tracing for this, with the addition of the execution tracing being done in an execution trace on proc (after all, it's just a regular Tcl command). In particular, we can do this:
proc addTracking {cmd args} {
set procName [lindex $cmd 1]
uplevel 1 [list trace add execution $procName enter [list trackBegin $procName]]
uplevel 1 [list trace add execution $procName leave [list trackEnd $procName]]
}
proc trackBegin {name arguments operation} {
# ignore operation, arguments might be interesting
...
}
proc trackEnd {name arguments code output operation} {
# ignore operation, arguments might be interesting
...
}
trace add execution proc leave addTracking
It doesn't give you quite the same information, but it does allow you to staple code around the outside non-invasively.

Manipulate non-global variables from fileevent handler

Is there a way to manipulate non-global variables from a fileevent handler? Consider the following minimal server:
proc initState {stateName} {
upvar $stateName state
set state(foo) bar
set state(baz) bla
# ...
return
}
proc handleConnection {stateName newsock clientAddress clientPort} {
upvar $stateName state
fconfigure $newsock -blocking 0
fconfigure $newsock -buffering line
fileevent $newsock readable [list handleData $newsock]
return
}
proc handleData {f} {
if {[eof $f]} {
fileevent $f readable {}
close $f
return
}
gets $f line
puts $f ok
# need to modify state here...
return
}
proc runServer {port} {
array set state {}
initState state
socket -server {handleConnection state} $port
vwait forever
}
runServer 1234
Is there any possibility to manipulate the state array created in the scope of runServer or is the only way to do this making state a global variable?
I'm pretty new to Tcl, if I were using C I would simply pass a pointer to state into the event handler but unfortunately Tcl does not allow that. Am I doing anything weird here, is there a more Tcl-ish way?
That's simply not going to work. The issue is that Tcl's stack frames do not persist in the way that what you want would require.
The standard options to work around this are:
Keep the state in a global array that is indexed by a "connection token" (e.g., the name of the channel). Remember that arrays are indexed by strings; composite keys like “sock42,hostname” are quite legal.
Keep the state in a namespace named after the connection token. If you're using Tcl 8.5, the namespace upvar command makes this much easier.
Keep the state in a TclOO object (requires Tcl 8.6 or the separate TclOO package for 8.5) or use a different object system (e.g., [incr Tcl], XOTcl; these are available for many Tcl versions).
Keep the state in a coroutine (requires Tcl 8.6). This effectively gives you a named stack (and lets you write your code so it is apparently “straight line” instead of driven by callback) but its version requirement is strict.