Search code examples
tcltracevarproc

Check if a variable is redefined by Tcl


In my script, I will source other owner's TCL script.

I try to check all variable if redefined in their tcl script file, Here I try in my script:

proc trace_var {args} {
   set frame [info frame];
   set frame_info [info frame [expr {$frame -2}]];
   puts "$args set in $frame_info"
}
trace add execution set leave trace_var
#source other owner script file
source #FILE1_FROM_OTHERS
#file content:
set a 123
set b 123456 ..... 
#Just example, I don't know the all var name, in fact. 
source #FILE2_FROM_OTHERS
set xxx 456;
set yyy 999;
set a 2; #<- want to catch such case
#Do my remain script body

Update: There are more than 3000+ vars in #File1, #File2, #FileN.. I have no clue how to use by tracing variable. The case I want to catch is if any variable redefine in #FILE1, #FILE2, ... #FILEN


Solution

  • There are many ways a variable can be created or changed other than using the set command, really too many to be feasible to attempt to monitor like this. Just some examples:

    lassign $foo a b     ;# variables a and b set / updated
    dict set foo bar baz ;# variable foo set or updated
    foreach element $list {}   ;# variable element set
    dict with foo {}     ;# any keys in $foo become variables
    

    And this is not limited to Tcl built-in commands, for example my json command has equivalents to most of the above examples, so unless you prevent new commands being defined by the script you're sourcing it's not even possible to track it by tracing all the known commands that could affect variables.

    I'm not sure about the details of your use case, but if it's just global variables I might try something like this:

    proc var_changed {v n1 n2 op} {
        puts "variable $v $op"
    }
    
    set start_vars [info vars]
    foreach v $start_vars {
        trace add variable $v {write unset} [list var_changed $v]
    }
    
    source $other_file
    
    foreach v [info vars] {
        if {$v ni $start_vars} {
            puts "New var defined: $v"
        }
    }
    

    While the trace handler is executing, additional traces on the variable that triggered it are suppressed.

    This example doesn't handle all the cases that could happen with array variables, or variables that are unset and then later added back. It's possible to extend this technique to variables in namespaces, but it starts to feel fragile and heavy-handed. If your use case is really about containing side-effects by the sourced script with unknown contents, then I'd advise rather sourcing the script in a child interpreter (a safe child if you don't trust the source of the script), maybe like this:

    set child [interp create -safe]
    $child eval [list set foo $foo]    ;# Copy the state of selected variables
    $child eval [list set bar $bar]    ;# to the child interp
    
    $child eval [list source $other_file]
    
    # Retrieve the state of vars in the child after sourcing $other_file if needed:
    puts "foo in child: [$child eval [list set foo]]"
    
    # Clean up
    interp delete $child