It most certainly has security issues. In particular, it will treat the variables' contents as expression fragments rather than values, and this lets all sort of problems occur. If that's not enough, the same problems also totally slay performance because there is no way to generate reasonably optimal code for it: the bytecode generated will be far less efficient since all it can do is assemble the expression string and send it for a second round of parsing.
Let's drill down to the details
% tcl::unsupported::disassemble lambda {{} {
set a 1; set b 2
puts [expr {$a + $b}]
puts [expr $a + $b]
}}
ByteCode 0x0x50910, refCt 1, epoch 3, interp 0x0x31c10 (epoch 3)
Source "\n set a 1; set b 2\n puts [expr {$a + $b}]\n put"
Cmds 6, src 72, inst 65, litObjs 5, aux 0, stkDepth 6, code/src 0.00
Proc 0x0x6d750, refCt 1, args 0, compiled locals 2
slot 0, scalar, "a"
slot 1, scalar, "b"
Commands 6:
1: pc 0-4, src 5-11 2: pc 5-18, src 14-20
3: pc 19-37, src 26-46 4: pc 21-34, src 32-45
5: pc 38-63, src 52-70 6: pc 40-61, src 58-69
Command 1: "set a 1"
(0) push1 0 # "1"
(2) storeScalar1 %v0 # var "a"
(4) pop
Command 2: "set b 2"
(5) startCommand +13 1 # next cmd at pc 18
(14) push1 1 # "2"
(16) storeScalar1 %v1 # var "b"
(18) pop
Command 3: "puts [expr {$a + $b}]"
(19) push1 2 # "puts"
Command 4: "expr {$a + $b}"
(21) startCommand +14 1 # next cmd at pc 35
(30) loadScalar1 %v0 # var "a"
(32) loadScalar1 %v1 # var "b"
(34) add
(35) invokeStk1 2
(37) pop
Command 5: "puts [expr $a + $b]"
(38) push1 2 # "puts"
Command 6: "expr $a + $b"
(40) startCommand +22 1 # next cmd at pc 62
(49) loadScalar1 %v0 # var "a"
(51) push1 3 # " "
(53) push1 4 # "+"
(55) push1 3 # " "
(57) loadScalar1 %v1 # var "b"
(59) concat1 5
(61) exprStk
(62) invokeStk1 2
(64) done
In particular, look at the addresses 30–34 (the compilation of expr {$a + $b}
) and compare with addresses 49–61 (the compilation of expr $a + $b
). The optimal code reads the values out of the two variables and just add
s them; the unbraced code has to read the variables and concatenate with the literal parts of the expression, and then fires the result into exprStk
which is the “evaluate an expression string” operation. (The relative number of bytecodes isn't the problem; the problem is the runtime evaluation.)
For how fundamental these differences could be, consider setting a
to 1 || 0
and b
to [exit 1]
. In the case of the precompiled version, Tcl will just try to treat both sides as numbers to add (neither of which is actually numeric; you'll get an error). In the case of the dynamic version… well, can you predict it by inspection?
So what do you do?
Optimal Tcl code should always limit the amount of runtime evaluation of expressions it performs; you can usually get it down to nothing at all unless you're doing something that takes an expression defined by the user or something like that. Where you have to have it, try to generate a single expression string in a variable and then just use expr $thatVar
rather than anything more complex. If you're wanting to do adding a list of numbers (or generally applying any operator to combine them), consider using this:
set sum [tcl::mathop::+ {*}$theList]
instead of:
set sum [expr [join $theList "+"]]
(Also, never use a dynamic expression with if
, for
or while
as that will suppress a lot of compilation.)
Remember, with Tcl it's (usually) the case that safe code is fast code. You want fast and safe code, right?