Author: Donal K. Fellows <[email protected]>
State: Draft
Type: Project
Vote: Pending
Created: 08-Nov-2018
Tcl-Version: 9.1
Post-History:
Tcl-Branch: tip-526
Abstract
This TIP proposes that expr
should only accept a single argument and stop
accepting and concatenating arguments.
Rationale and Discussion
A very common fault of expr
usage is to fail to brace expressions. This was
semi-good practice in Tcl 7.6 and before as Tcl's substitution phase was
faster than the expression evaluation engine's, but it was always a little
tricky even then as it depended on you being sure that the substituted values
were numeric for safety. When that critical side condition didn't hold, it
resulted in unsafe code:
proc addFive {x} {
expr $x + 5
}
puts [addFive 17]; # ==> 22
puts [addFive {[exec format C:]}]; # OOPS!
From Tcl 8.0 onwards, this was no longer a good idea. The bytecode compiler is
able to generate compiled code ahead of time for braced expressions, but is
not able to do so (usefullly) if any of the arguments to expr
are unbraced.
Of course, we want to be able to use an unbraced argument occasionally, such
as when we take an expression as an argument from elsewhere, but those are
very much the unusual case: almost always, a call to expr
with any argument
unbraced is both a performance bug and a security bug.
Because this is so strongly the case, there are uses of Tcl that are almost always a problem; for example:
expr $x >= "inf"; # where $x is expected to be a string
Proposal
To reduce the incidence of this, I propose that in Tcl 9.0 the expr
command
should only take a single argument. It most definitely isn't a total fix for
this — a total fix isn't really possible without making the expr
command
special in the Tcl base language, and that goes against the general principle
that nothing is special there — but it will at least reduce the incidence a
bit by encouraging people to either brace their expressions or be very clear
that they are not doing so.
This will leave virtually all correct code working, and yet force code that has a problem to acknowledge the fact.
Supported cases:
Braced expressions:
expr {$x * 5}
Expressions in variables:
set theExpression {$x * 5} expr $theExpression
Expressions from commands:
expr [dict get $myExpressions thisCase]
Cases that work because we can't stop them without breaking the language:
Expressions in double quotes:
expr "$x * 5"
Expressions with unquoted compound substitutions:
expr $x*5+[llength $y]
Cases that are outright rejected:
Expressions with multiple arguments:
expr $x * 5
Expressions with multiple literal arguments (uncommon):
expr {$x} *5
Workarounds for Broken Old Code
The cases where someone genuinely needs the old behaviour can be handled by
them using concat
(or string cat
, depending on exact requirements)
explicitly to make the expression:
set x {2 + 3}
expr [concat $x * 5]
That still has the faults, but now at least looks like it is doing something tricky. Which it is, given that the actual behaviour of the equivalent code in Tcl 8 is so messy. Another alternative (and a better one in this particular case) is:
set x {2 + 3}
expr {[expr $x] * 5}
That also looks weird, but is better in that it only has the potential for trouble in one place and allows for better compilation of the outer parts of the expression. (Yes, it produces a different result from the other code. This is likely to be a good thing in almost all cases.)
Copyright
This document has been placed in the public domain.