Version 63 of eval

Updated 2013-04-18 01:00:12 by pooryorick

Summary

Interprets data as a a Tcl script and evaluate it.

Synopsis

eval arg ?arg ...?
Injection Attack
introspection
Introspection
Many Ways to eval
official reference
man page

Usage

To avoid the pitfalls of [eval] the rule of thumb is to only ever pass one argument to [eval]. Passing a single script, rather than relying on implicit concatenation helps to avoid dumb mistakes such as passing a string which contains newlines.

eval concatenates its arguments in the same fashion as [eval] concatenates its arguments in the same fashion as [concat], and hands them to the interpreter to be evaluated as a Tcl script. It then returns the result of that evaluation or any error generated by it. eval is an old, old command that's been in [Tcl from the beginning. [eval] is an old, old command, that's been in [Tcl from the beginning. eval is useful when one wishes to generate a script and then interpret it. [eval] is useful when one wishes to generate a script and then interpret it. In modern Tcl, [eval] is merely an abbreviation for [uplevel 0]. This self-modifying programs, and related esoterica. "self-modifying programs", and related esoterica. if 1 ..., which may be byte-compiled, is an efficient [eval] is used by all the commands -- [[bind], everything with

When using eval, it is very easy to leave holes which can be exploited to When using [eval], it is very easy to leave holes which can be exploited to inject malicious code, so it is best to exercise extreme care with it until one

eval can often be avoided, particularly with more modern recent versions of [eval] can often be avoided, particulary with more modern recent versions of

Is [eval] Evil?

Short answer: No. Like many commands in Tcl, eval can lead to double Short answer: No. Like many commands in Tcl, [eval can lead to double skilled Tcl programmer knows how to wield [double substitution. skilled Tcl programmer learns how to wield double substitution. Historically, eval was often used with exec, to flatten lists, allowing

[[eval] is often used with exec, to flatten input lists:

#warning: no-longer recommended

because otherwise grep would receive filelist as one long filename with embedded blanks. because otherwise grep would receive filelist as one long filename with embedded blanks. Or, if you want to append one list's elements to another:

eval lappend thislist $thatlist
set thislist [concat $thislist $thatlist] ;# can also be done as

Though the following two options are safer and faster: Another application is in building up a command in pieces (by appending to a

eval $script
eval $cmd

When the script contains only one command, [argument expansion] is preferred:
** Using `[[eval]` to Expand Lists **

Expanding Lists with [eval]

Prior to Tcl version 8.5, one use case for eval was to expand lists. Prior to Tcl version 8.5, one use case for [eval] was to expand lists. There is no longer any reason to do this. Use {*} instead. See Argument expansion.


Instead of:

#warning: not recommended
eval pack [winfo children .]

use:

pack {*}[winfo children .]

And instead of:

#warning: this is not recommended
set filepath [list path to the file]
eval file join $filepath

use:

set filepath [list path to the file]
file join {*}$filepath

Another similar example

#the old (bad) way
set f [glob *.tcl]
eval [linsert $f 0 exec lp]
#the shiny new way
exec lp {*}$f

Historical

The following describes the situation before the advent of {*}:

Naive code might look like this (an actual example from BWidget's entry.tcl:97):

#warning: bad code ahead!
eval entry $path $maps(:cmd)

An unsuccessful attempt to fix the problem: An unsuccesful attempt to fix the problem:

#warning: bad code ahead!
eval entry [list $path] $maps(:cmd)

But $maps(:cmd) might be a string with newlines:

set maps(:cmd) {
    -opt1 val1
    -opt2 val2
}

It's necessary to first convert $maps(:cmd) to a proper list using one of the list commands:

eval [linsert $maps(:cmd) 0 entry $path]

See Also

RE: TCLCORE Re: CFV: TIPs #156 and #157 , Jeff Hobbs, tcl-core mailing list, 2003-10-12
AMG: Text repeated below, please read it until you understand what trouble [eval] buys you and how helpful {*} is.
TCLCORE TIP #144: Argument Expansion Syntax , by tclguy
TCLCORE TIP #144: Argument Expansion Syntax , Jeff Hobbs, tcl-core mailing list, 2003-07-26
eval is evil (for spreading list-arguments) ,comp.lang.tcl ,2003-03-11

Re: CFV: TIPs #156 and #157

The arguments to eval are concatenated into a string to be interpreted, but The arguments to [eval] are concatenated into a string to be interpreted, but (i.e., one conforming to the Tcl parsing rules as laid out in the Tcl manual page).

The following script breaks because the concatenation keeps the newlines from the list's string representation, making eval interpret the second element the list's string representation, making [eval] interpret the second element

% set arg {a
b
c
}
a
b
c
% eval list $arg
ambiguous command name "b": bgerror binary break

To solve this, construct the argument using list primitives like lappend, To solve this, construct the argument using list primitives like lappend, list, etc. DKF says: "list and eval are truly made for each other." Another solution is to use the following idiom:

% eval [linsert $arg 0 list]
a b c

linsert converts its list argument to a well-formed list with single [[linsert] converts its list argument to a well-formed list with single of these elements contain newlines, they remain in the resulting string).

It's important to remember that eval works on strings, not lists, It's important to remember that [[eval] works on strings, not lists, for interpreting a string as a script.

Verbose Evaluation

LV: I just had to copy this over to the wiki - it is so neat!

From comp.lang.tcl, Bob Techentin writes in response to a poster:

It sounds like you're looking for something similar to /bin/sh "set -v" command which prints shell input lines as they are read, and "set -x" which prints expanded commands. Kind of like a verbose mode.

Nope. Nothing like that. But you wouldn't have to write your own shell. You could walk through a script (a list of lines), and use info complete to could walk through a script (a list of lines), and use [[info complete] to command and the command's results. This seems to work.

proc verbose_eval {script} {
    set cmd {} 
    set cmd ""
    if {$line eq {}} {continue}
    if {$line eq ""} {continue}
        if {[info complete $cmd]} {
        if { [info complete $cmd] } {
            puts -nonewline [uplevel 1 $cmd]
            set cmd ""
        }
    }
}

set script {
    puts hello
    expr {2.2 * 3}
    expr 2.2*3

verbose_eval $script 

LV: This proc is so slick! I love it! LV This proc is so slick! I love it! Lars H: Another approach to this (which also can see individual commands in if branches, loop bodies, etc.) is to use traces. Do we have that written if branches, loop bodies, etc.) is to use traces. Do we have that written

AR Here is my proposal with traces. Rem: the "noop" string is some kind of magic cookie.

wtracy 2008-06-23: I want the commands I run in eval to have their own set of variables independently of the calling script. Is there some way I can hand eval a context (maybe as an associative array?).

Lars H: Sounds like you want a lambda (or possibly a closure, which is more difficult). If you're using Tcl 8.5, then have a look at apply. more difficult). If you're using Tcl 8.5, then have a look at [apply]. [RS: Long before 8.5, you could always write a little proc to hide its local variables (x in this example):

% eval {proc {} x {expr {$x*$x}}; {} 5}
% eval {proc {} x {expr $x*$x}; {} 5}

wtracy: Thanks. It looks like apply is the closest Tcl feature to what wtracy: Thanks. It looks like [[apply] is the closest Tcl feature to what

wtracy: Actually, it looks like what I *really* want is to nest eval wtracy: Actually, it looks like what I *really* want is to nest [eval] inside of a [namespace eval] block. [wtracy: Okay, for the sake of any lost soul that comes along after me, this is what I really wanted to do all along:

$ set cmd {puts FOO}
> set cmd {puts FOO}
$ namespace eval MyNameSpace $cmd
> namespace eval MyNameSpace $cmd

NEM: As mentioned, apply is probably a better choice in this case as it NEM As mentioned, [apply] is probably a better choice in this case as it [dangers of creative writing for some related discussion. The apply version is: dangers of creative writing for some related discussion. The apply version is:

apply {{} {puts FOO} ::MyNameSpace}
apply {{} { puts FOO } ::MyNameSpace}

This has the benefit of evaluating the code within a fresh procedure context,
meaning all variables are local

to that context by default. See also `[namespace inscope]`/`[namespace code]` and
to that context by default. See also [namespace inscope]/[namespace code] and
you may also like [dict with].

** Proposal: Modify `eval` to Accept a List of Lists  **
** Proposal: Modify `[[eval]` to Accept a List of Lists  **
In some cases `eval` does work on lists - and its special.  In particular,
In some cases `[[[eval]]` does work on lists - and its special.  In particular,
if eval is passed a [pure list] then it gets evaluated directly, without
what it ''could'' be.

When you pass eval a pure list, you can only execute a single command.  What if
we were to pass eval a list of pure lists - it should directly evaluate each of
them, and return the value of the the last one evaluated.  This sounds a lot
like progn in [lisp], and it seems like it would allow for some nifty bits of
[introspection] - for example, if `[info body]` returned a pure list of lists
[introspection] - for example, if [info body] returned a pure list-of-lists
whole program becomes a list of lists.

The problem is how to signal to `eval` (or `[uplevel]`, `[namespace]`, `[bind]`, ...) that it
The problem is how to signal to eval (or uplevel, namespace, bind, ...) that it
is a list of lists rather than just a list.  Could eval determine if its input
progn?

Another place this seems like it could be useful: I have a [pkgIndex.tcl] file with
Another place this seems like it could be useful: I have a pkgIndex file with

package ifneeded tls 1.4 [
package ifneeded tls 1.4 "[list load [file join $dir libtls1.4.so]];[list source [file join $dir tls.tcl]]"

where most of the lines are like

package ifneeded tclperl 2.3 [list load [file join $dir tclperl.so]

since they only need one command; but the tls line needs two commands.  So why can't it be

package ifneeded tls 1.4 list [ package ifneeded tls 1.4 [list [list load [file join $dir libtls1.4.so] [list source [file join $dir tls.tcl]

RS 2004-02-06: As usual in Tcl, functionality you miss you can easy roll yourself. I needed a progn-like list eval in RPN again, and did it similar to this:

proc leval args {
    foreach arg $args {
        set res [uplevel 1 $arg]
    }
    set res ;# return last ("n-th") result, as Tcl evaluation does
}

eval versus bytecode

eval versus bytecode

AMG: It appears [eval]'ed code does not get bytecode-compiled, even when [eval] is passed a single brace-quoted argument. The same is true for [uplevel 0] and [time]. [[catch] and [[if {1}] seem to be "argument", rather a single command line pre-chewed into an objv list. Bytecoding cannot take place when the argument is the product of list, Bytecoding cannot take place when the argument is the product of [[list],

proc a {} {eval           {for {set i 0} {$i < 100000} {incr i} {}}}
proc b {} {uplevel 0      {for {set i 0} {$i < 100000} {incr i} {}}}
proc c {} {time           {for {set i 0} {$i < 100000} {incr i} {}}}
proc d {} {catch          {for {set i 0} {$i < 100000} {incr i} {}}}
proc e {} {if {1}         {for {set i 0} {$i < 100000} {incr i} {}}}
proc f {} {            {*}{for {set i 0} {$i < 100000} {incr i} {}}}
proc g {} {eval      [list for {set i 0} {$i < 100000} {incr i} {}]}
proc h {} {uplevel 0 [list for {set i 0} {$i < 100000} {incr i} {}]}
proc i {} {time      [list for {set i 0} {$i < 100000} {incr i} {}]}
proc j {} {catch     [list for {set i 0} {$i < 100000} {incr i} {}]}
proc k {} {if {1}    [list for {set i 0} {$i < 100000} {incr i} {}]}
proc l {} {       {*}[list for {set i 0} {$i < 100000} {incr i} {}]}
proc m {} {try            {for {set i 0} {$i < 100000} {incr i} {}}}
a;b;c;d;e;f;g;h;i;j;k;l;m
time a 10    ;#  80723.8 microseconds per iteration - slow
time b 10    ;#  65380.2 microseconds per iteration - slow
time c 10    ;#  66024.8 microseconds per iteration - slow
time d 100   ;#  18888.3 microseconds per iteration - fast
time e 100   ;#  18779.3 microseconds per iteration - fast
time f 100   ;#  19375.2 microseconds per iteration - fast
time g 10    ;# 319111.5 microseconds per iteration - very slow
time h 10    ;# 342878.4 microseconds per iteration - very slow
time i 10    ;# 322279.2 microseconds per iteration - very slow
time j 10    ;# 316939.0 microseconds per iteration - very slow
time k 10    ;# 321865.5 microseconds per iteration - very slow
time l 10    ;# 344009.5 microseconds per iteration - very slow
time m 100   ;#  19503.0 microseconds per iteration - fast

I want single-argument eval functionality in my code, but I also want I want single-argument [eval] functionality in my code, but I also want bytecoding. [[catch] has the undesirable side effect of hiding errors, so I guess I have to use [[if {1}] which is a really weird idiom. Does anyone

AMG: I reran the tests and got better numbers. The current version of Tcl must be faster than whatever I used when I first did this benchmark (I'm using the same computer). Look in the page history to see the comparison. More importantly, I think I found a bytecoded eval: single-argument try. importantly, I think I found a bytecoded [eval]: single-argument [try]. methods. It's considerably less weird than if 1, and it doesn't hide methods. It's considerably less weird than [if {1}], and it doesn't hide

DKF: We've been focusing a bit more on improving the most cripplingly-slow cases, but three execution modes still exist that have fundamentally different speeds. There's compilation to bytecode-with-local-var-table (which is the fastest; the speed comes from being able to compile in indexes into the LVT into the generated bytecode, which this test is particularly sensitive to), there's compilation to bytecode-without-LVT (slower; variables have to be looked up each time they're accessed), and there's interpreting (slowest by far). There's not much point in doing a lot of comparison between the three; they all exist for a reason. (We could make straight eval/uplevel they all exist for a reason. (We could make straight eval/uplevel 0 of constant arguments work at full speed, but we see no reason to bother given how rare they are in real code, and it's better that time is kept to get by using ::tcl::unsupported::disassemble` to look at the bytecode to see to get by using tcl::unsupported::disassemble to look at the bytecode to see done by just building arguments and invoking “cnormal” commands. done by just building arguments and invoking “normal” commands. Twylite 2012-08-24: I've been optimising some control constructs and was trying to understand the performance of various combinations of uplevel, tailcall, catch and try. Along the way I found AMG's performance figures, and have updated them with some new ones:

# Fast (prefix f)
proc f_baseline       {} {                 for {set i 0} {$i < 100000} {incr i} {} }
proc f_catch          {} {catch            {for {set i 0} {$i < 100000} {incr i} {}}}
proc f_catch          {} {catch           {for {set i 0} {$i < 100000} {incr i} {}}}
proc f_if_1           {} {if {1}          {for {set i 0} {$i < 100000} {incr i} {}}}
proc f_expand         {} {             {*}{for {set i 0} {$i < 100000} {incr i} {}}}
proc f_try            {} {try             {for {set i 0} {$i < 100000} {incr i} {}}}
proc f_time_apply     {} {time {apply {{} {for {set i 0} {$i < 100000} {incr i} {}}}}}
# Medium (prefix m)             
proc m_eval           {} {eval             {for {set i 0} {$i < 100000} {incr i} {}}}
proc m_eval           {} {eval            {for {set i 0} {$i < 100000} {incr i} {}}}
proc m_uplevel_0      {} {uplevel 0       {for {set i 0} {$i < 100000} {incr i} {}}}
proc m_time           {} {time            {for {set i 0} {$i < 100000} {incr i} {}}}
proc m_tailcall_try   {} {tailcall    try {for {set i 0} {$i < 100000} {incr i} {}}}
proc m_catch_uplvl_1  {} {       set body {for {set i 0} {$i < 100000} {incr i} {}} ; catch { uplevel 1 $body } }  
proc m_uplvl_1_catch  {} {       set body {for {set i 0} {$i < 100000} {incr i} {}} ; uplevel 1 [list catch $body] }  
# Slow (prefix s)               
proc s_eval_list      {} {eval       [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_uplevel_0_list {} {uplevel 0  [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_time_list      {} {time       [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_catch_list     {} {catch      [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_if_1_list      {} {if 1       [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_if_1_list      {} {if {1}     [list for {set i 0} {$i < 100000} {incr i} {}]}
proc s_tailcall       {} {tailcall         for {set i 0} {$i < 100000} {incr i} {} }

if 0 {
if {0} {
  set REPS {f 10 m 40 s 100} ;# reps by prefix
  set cmds {f_baseline f_catch f_if_1 f_expand f_try f_time_apply 
    m_eval m_uplevel_0 m_time m_tailcall_try m_catch_uplvl_1 m_uplvl_1_catch 
    s_eval_list s_uplevel_0_list s_time_list s_catch_list s_if_1_list 
    s_expand_list s_tailcall}
  
  foreach cmd $cmds { $cmd } ;# compile
  set cmdrepstimes {} ; foreach cmd $cmds { ;# time
    set reps [dict get $::REPS [string index $cmd 0]] 
    lappend cmdrepstimes [lindex [time $cmd $reps] 0] $cmd $reps 
  }
  
  set mintime [::tcl::mathfunc::min {*}$times]
  foreach {t cmd reps} [lsort -real -stride 3 $cmdrepstimes] {
    puts [format "time %-16s $reps\t;# %9.1f microseconds per iteration, factor %5.2f" \
      $cmd $t [expr { $t / $mintime }] ] 
  }

time f_time_apply     10        ;#    4625.8 microseconds per iteration, factor  1.00
time f_if_1           10        ;#    4641.8 microseconds per iteration, factor  1.00
time f_expand         10        ;#    4645.0 microseconds per iteration, factor  1.00
time f_catch          10        ;#    4651.3 microseconds per iteration, factor  1.00
time f_baseline       10        ;#    4658.5 microseconds per iteration, factor  1.01
time f_try            10        ;#    4735.9 microseconds per iteration, factor  1.02
time m_eval           40        ;#   18454.3 microseconds per iteration, factor  3.98
time m_uplevel_0      40        ;#   18738.3 microseconds per iteration, factor  4.04
time m_time           40        ;#   19176.8 microseconds per iteration, factor  4.14
time m_uplvl_1_catch  40        ;#   21501.1 microseconds per iteration, factor  4.64
time m_catch_uplvl_1  40        ;#   21603.1 microseconds per iteration, factor  4.66
time m_tailcall_try   40        ;#   21698.4 microseconds per iteration, factor  4.68
time s_uplevel_0_list 100       ;#   84963.3 microseconds per iteration, factor 18.34
time s_eval_list      100       ;#   85519.3 microseconds per iteration, factor 18.46
time s_catch_list     100       ;#   85919.9 microseconds per iteration, factor 18.54
time s_time_list      100       ;#   85944.2 microseconds per iteration, factor 18.55
time s_if_1_list      100       ;#   87269.0 microseconds per iteration, factor 18.84
time s_expand_list    100       ;#   93096.9 microseconds per iteration, factor 20.09
time s_tailcall       100       ;#   94473.5 microseconds per iteration, factor 20.39