liory
liory

Reputation: 1

How to protect a variable from being overwritten

How can I protect a variable when I'm sourcing a script that using the same variable name. That is, how can I keep x value in the return from the source even if it changed in the script.

example

Here is the main code that sourcing script.tcl:

set list {1 2 3 4 5}
set x 1
source $script.tcl

script.tcl:

# some script with loop on x:
foreach x $list {}

The problem is that x equal some value (in this case "1") before the source of the file, but after the source operation, x is the last iteration value from the list.

I'd like to preserve the value of x (and I don't want to change the names).

Upvotes: 0

Views: 879

Answers (4)

Peter Lewerin
Peter Lewerin

Reputation: 13252

Just for completeness: a namespace can also be used to contain the source operation and control which variables it accesses.

namespace eval TEMP {variable x}
# ... stuff ...
namespace eval TEMP {source script.tcl}

A single namespace eval invocation can be used, or separate ones as desired.

There are some subtleties. When writing to a variable inside a namespace eval (outside of a procedure scope, that is), the following priority order is used:

  1. write to an existing name in the current namespace
  2. write to an existing name in the global namespace
  3. create a variable in the current namespace and write to that

(Names can exist by having a value or by appearing in a variable command invocation.)

The order for reading a variable is

  1. read from an existing name in the current namespace (failing if it has no value)
  2. read from an existing name in the global namespace (failing if it has no value)
  3. fail

This means that you can "leak in" variables from the global namespace into the current by avoiding using variable on them. In this example, the value of the list variable is accessed that way. By making sure there is an existing name in the current namespace, the global name will be shadowed and protected from reading or writing.

You can similarly "leak out" variables by making sure there is a global name for them (and again avoiding using variable).

For instance, if the script in script.tcl is

# some script with loop on x:
foreach x $list {incr n $x}

and you source it like

set n 0
namespace eval TEMP {source script.tcl}

then

set n
# => 15

Without the set n 0, the name n is instead created as ::TEMP::n.

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137587

In general, if you've got one script sourcing another script where that other script isn't well behaved (being well behaved would involve the script doing everything in a namespace and generally being careful), your best bet is to source that script in a child interpreter.

# Here is our protected variable
set x "a very important value"

# Make the child
interp create child

# Set it up to be ready
child eval [list set list {1 2 3 4 5}]

# Run the script
catch {
    child eval [list source script.tcl]
}

# Possibly get things from that interpreter here

# Dispose of the interpreter
interp delete child

# Show that the variable is OK
puts $x

There's absolutely nothing shared between the interpreters other than commands you create as shared. Those shared commands are aliases, and let you provide whatever sort of profiled extension commands you want. Variables are not shared at all.

For truly untrusted other scripts, you can use a safe interpreter; those are child interpreters with unsafe commands (including source and everything else that touches the filesystem) removed so that there's much less that a script can do to fool with you. They might be overkill in this instance.

Upvotes: 3

mrcalvin
mrcalvin

Reputation: 3434

Use apply to evaluate the source script in a dedicated environment, without "polluting" the sourcing context:

apply [list {list} [list source $script.tcl]] $list

That said, it is not entirely clear how data or state from one script should be communicated to the other, if at all? Using apply, one would have to a rely on the return value from source $script.tcl or some scoped variables from within the apply lambda script (e.g., implicitly via global or explicitly via fully-qualified variable names: set ::x 1).

Upvotes: 0

glenn jackman
glenn jackman

Reputation: 246837

To demonstrate the problem:

% set list {1 2 3 4 5}
1 2 3 4 5
% set x 1
1
% source script.tcl
% set x
5

You could source the script in a proc to introduce a new variable scope

% proc do_script {} {
    upvar 1 list list   ;# make $list available in this scope
    source script.tcl
    puts "in proc, x=$x"
}
% set x 1
1
% do_script
in proc, x=5
% set x
1

Upvotes: 0

Related Questions