Safe XOTcl object creation

XOTcl objects can be instantiated while giving them initial values or setup — done before the constructor is called. Here's an example:

Class Car -parameter {
    {doors 4}
}

Car instproc init {marque} {
   puts "A $marque is a fine car"
}

Car new "Land Rover"

Car new "Caterham" -doors 2

All fine so far. The problem here is that if any of the arguments you pass to the constructor begin with a dash, they can cause havoc, as they are interpreted as method calls. Picture initialising a "Message" class with the message from the user as its one and only argument. This is very unsafe as, if the user's message begins with a dash, they can execute arbitrary code through your object. Bad.

Class Message

Message instproc init {text} {
# ...
}

# ...
Message new $textFromUser  ;#  SECURITY HOLE!

Even the following is risky:

Car new "ExperimentalCar" -doors $doors

if, for example, you got the number of doors from a form. Now obviously in this particular case you should probably be checking the form well before you reach this point, but if your code has a bug, it allows the user to execute code. Quite an unexpected outcome for developers who maybe assumed, at most, an error about it being a bad value, at some point in the execution.

You can get around it like this:

Message new [list -init $msg]

But you really ought to do this all the time. You have to be very, very careful. Unless you do it in this way you are heading for trouble. A Jpeg object with some binary data, or a text file passed to a constructor, or a protocol message — all could trip you up and cause a crash or worse! And all for something which was seemingly innocent. Tcl does have similar problems elsewhere. Commands such as switch, file and others can result in misery, but they are quite well documented and not as commonplace as object instantiation.

Having to use the above style for instantiation would be quite awkward if you had to do it virtually all the time. So here is a simple piece of script, using XOTcl's mixins, which alleviates the problem. It allows safe instantiation, both when we want some preconfigured data, and when it is not necessary. It replaces the new method with a safe version called birth, and offers an extra birth-with method for pre-configuration of the object.

@ Class SafeInit {
    description {
        Provides functionality to safely instantiate objects without
        passed arguments getting confused for dash-started parameters. Without
        using the methods here, XOTcl code can easily and uninentionally
        leave big holes and lead to bugs. Ie.
        passing in "-foobar", even from a variable, would cause the 
        normal XOTcl instantiation with [new] to try and call the 
        [foobar] method,
    }
}
Class SafeInit


@ SafeInit instproc birth {args} {
    description {
        Replaces the normal [new] method with one which does not try to
        call methods for any values beginning with a dash. This can be
        used safely whenever there is no need to configure the object
        during instantiation.
    }
}

SafeInit instproc birth {args} {
    return [my new [concat [list -init] $args]]
}

@ SafeInit instproc birth-with {args} {
    description {
        This is similar to [birth] but the last argument is a script which
        is evaluated, before calling the constructor, within the context
        of the newly created object. You can use [self], [my] to call
        methods in the object and set things up. You can even directly
        set variables for the object without [self] or [my].
    }
}

SafeInit instproc birth-with {args} {    
    set script [lindex $args end]
    set args [lrange $args 0 end-1]
    set name [SafeInit autoname ::xotcl::stk__#]
    return [my create $name [list -eval $script] [concat [list -init] $args]]
}

Class instmixin SafeInit

And here is an example showing how it is used:

CClass Foo -parameter {
    {animal horse}
}

Foo instproc init {arg1 arg2} {
    puts "Constructor args: $arg1 $arg2"
}

Foo instproc hello {} {
    puts jou
}


# We do not override [create] or the auto-create from [unknown]. Ie.
# class creation still works and other stuff :-)
puts "Should say 'jou':"
Foo ob 1 2 -hello
puts "Animal: [ob animal]"

# Instantiate with -dash args
set ob [Foo birth -hellou world]
puts "Animal: [$ob animal]"

# Instantiate with some presets
puts "Show say 'jou':"
set ob [Foo birth-with 1 2 {
    my hello
    my animal cat
}]
puts "Animal: [$ob animal]"

As you can see "Foo ob 1 2 -hello" still works in the normal way, whereas new is now safe.

NEM Are you sure the use of concat here is safe, given that it is a string operation? Wouldn't it be safer to use list and {*}, e.g., [list -init {*}$args]?

Setok concat states it operates on lists, in the manpage. Or rather, it means concatenating into a single list of all args are lists. I reckoned that list and {*} would be slower as you have to open up $args and then rebuild them into a list (which they were already). Not tested, though.

Setok I updated the code to create a new method like new, but named differently (in this case birth). Previously the code replaced new completely. This was fine if everything assumed this safe object creation method, but obviously using any code that wanted to use traditional leading-dash parameterisation would then fail and would need updating. I finally decided that adding a new method like new, but named differently, was the more practical option. Now both ways can be used at the same time (although I'll probably move my own code to exclusively using birth).