Tcl Source Code

View Ticket
Login
Ticket UUID: 1998273
Title: Making a proc arg's unshared
Type: RFE Version: None
Submitter: ferrieux Created on: 2008-06-19 21:55:13
Subsystem: 10. Objects Assigned To: msofer
Priority: 5 Medium Severity:
Status: Open Last Modified: 2008-06-20 14:08:56
Resolution: None Closed By:
    Closed on:
Description:
Inside a proc, a value passed as argument typically has a refcount of three. Of the two unwanted ones, one is easily removed: the local var

         foo $x[unset x]

but the other is harder: it is in the CallFrame's objv, basically [info level 0].

The attached patch provides [info zaplevel] which prematurely neutralizes the [info level 0] data for that purpose. Thus, in

     proc pop x {
        info zaplevel
        lrange $x[unset x] 0 end-1
     }

The $x value is effectively passed unshared, provided it is passed unshared by the caller.

The patch works by downing the refcounts of all members of [info level 0] except the head (to keep the proc name visible, should someone call [info level 0] after [info zaplevel]), and replacing them with references to a shared empty Tcl_Obj. Hence an [info level 0] of "foo a b c" becomes "foo {} {} {}".
This gives us safety in the event of callers cacheing CallFrame->objc,objv in locals/registers.
User Comments: ferrieux added on 2008-06-20 14:08:56:
Logged In: YES 
user_id=496139
Originator: YES

Then shall we do ::errorStack (or [info errorStack] or ...) at last ?
I can give it a try if you green-light me.

(Notice that as a warned user, I wouldn't blame anyone for having an incomplete ::errorStack after a zaplevel. Again like gdb and -O3).

Regarding zaplevel, I'm still interested about the nasty cases you had in mind with unknown, namespaces, aliases, etc.

msofer added on 2008-06-20 07:34:22:
Logged In: YES 
user_id=148712
Originator: NO

Uhh ... right, ::errorInfo is untouched. I was thinking of substituted error stacks :}

msofer added on 2008-06-20 06:26:27:
Logged In: YES 
user_id=148712
Originator: NO

About stack traces: how does this look with the patch?


mig@cpq:~$ tclsh
% proc zap x {
    catch {info zaplevel}
    foo
}

proc foo {} {
    ouch
}

zap 1% % % % 
invalid command name "ouch"
% set errorInfo
invalid command name "ouch"
    while executing
"ouch"
    (procedure "foo" line 2)
    invoked from within
"foo"
    (procedure "zap" line 3)
    invoked from within
"zap 1"

ferrieux added on 2008-06-20 06:19:18:
Logged In: YES 
user_id=496139
Originator: YES

Thanks for the faster KFK :-)
Sorry for the misguided upload. Won't do that again.

I agree about all the losses regarding [info level], but these are mild losses, first because the name [info zaplevel] is pretty clear about it, and second because the in-place replacement by {} of the args does no harm to other copies (if someone has made an extra copy of CF->objv, by definition he should have upped the refcounts. This may happen in the future, but in any case won't interfere destructively with zaplevel, just mmake it useless ;-)

About stack traces, what do you mean ?
We both know that ::errorStack does not exist (remember ? ;-)
And ::errorInfo does not contain dynamic args like [info level 0]...

About debuggers, so be it. Gdb is not too happy with -O3 either !

About [unknown], I concur. Only an insane person would zaplevel inside unknown !
But would it go so far as to crash ? Any idea to stress-test it ?

msofer added on 2008-06-20 05:59:01:

data_type - 360894

Logged In: YES 
user_id=148712
Originator: NO

(a) even better than '$x[unset x]' is '$x[set x {}]', as it is completely bytecompiled (unset is not)

(b) I think this belongs in the FR tracker, not patches. It may even require a TIP? The implicit contract is that the caller keeps the objv valid until the callee returns, and the CallFrame keeps a copy. This patch violates the contract.

I still want to explore the full consequences of this, but is is definitely NOT 100% harmless:
  * Stack traces cease to work properly
  * Debuggers may suffer the loss of [info level], especially if they rely on leave traces. 
  * [unknown] needs a proper [info level], and there are probable more such beasts. Not sure that this breaks it, as long as [unknown] doesn't call [info zaplevel] itself
  * Scripts like the following, and other commands that inspect the CallFrame stack, also stop working:

proc foo x {
    info zaplevel
    ...
    moo
}

proc moo args {
    if {![llength $args]} {
        set args [info level -1]
    }
    boo $args
}

...

ferrieux added on 2008-06-20 05:51:49:
Logged In: YES 
user_id=496139
Originator: YES

Correction: the K-free K idea and name were given by Don, in the comments of 1890831.

ferrieux added on 2008-06-20 05:32:53:
Logged In: YES 
user_id=496139
Originator: YES

No. [take x] is exactly the same as $x[unset x], but in less efficient ;-)

To witness that you still have an extra ref:
% proc K {x y} { set x }
% proc take {v} { upvar 1 $v x ; K $x [unset x] }
% proc pop x {lrange [::tcl::unsupported::debugobj [take x]] 0 end-1}
% pop [list d f g h]
Obj:0x9F2340(2) (list):0x9F1000-0x0 <<<?>>>
d f g

Now with [info zaplevel]:
% proc pop x {info zaplevel;lrange [::tcl::unsupported::debugobj [take x]] 0 end-1}
% pop [list d f g h]
Obj:0x9DB658(1) (list):0x9EF4C8-0x0 <<<?>>>
d f g

PS: A side note: the $x[unset x] idiom is the K-free K given to me by Kevin IIRC. It is very efficient because the concatenation of the empty string has a shortcut and causes no duplication nor shimmering.

andreas_kupries added on 2008-06-20 05:05:24:
Logged In: YES 
user_id=75003
Originator: NO

Does the following do the same?

proc K {x y} { set x }
proc take {v} { upvar 1 $v x ; K $x [unset x] }
proc pop x { lrange [take x] 0 end-1 }

ferrieux added on 2008-06-20 04:55:14:

File Added - 281914: zaplevel.patch

Attachments: