Tcl Source Code

View Ticket
Login
Ticket UUID: da15321a5852265331fcf94de655365551b9fa90
Title: crash in variable traces when using itcl
Type: Bug Version: 8.5.15
Submitter: anonymous Created on: 2013-12-02 10:18:35
Subsystem: 46. Traces Assigned To: dgp
Priority: 7 High Severity: Severe
Status: Closed Last Modified: 2014-07-23 15:18:47
Resolution: Invalid Closed By: dgp
    Closed on: 2014-07-23 15:18:47
Description:
Tcl/Tk 8.5.15 on Linux and Windows
Itcl 3.4

The appended code crashes TCL (sometimes immediately, sometimes after
one minute):

(gdb) where
#0  0xb745fe7a in Tcl_GetString () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#1  0xb75060dc in ButtonTextVarProc () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtk8.5.so
#2  0xb74826b7 in TclCallVarTraces () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#3  0xb7482bd7 in TclObjCallVarTraces () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#4  0xb748b061 in TclPtrSetVar () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#5  0xb7425644 in TclExecuteByteCode () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#6  0xb742f491 in TclCompEvalObj () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#7  0xb73df911 in TclEvalObjEx () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#8  0xb73dfdab in Tcl_EvalObjEx () from /aegis/si++/branch.4/branch.0/baseline/lib/linux/libtcl8.5.so
#9  0xb6e233c1 in Itcl_EvalMemberCode () from /aegis/si++/branch.4/branch.0/baseline/tcltk/linux/lib/itcl3.4/libitcl3.4.so

when dereferencing a NULL-Pointer in Tcl_GetString().

The cause of the crash is a stale Tk-Button pointer in the list of
variable traces, which later is referenced when no -textvariable is
bound to the button.

As fas as I can tell, the following happens:

- a button is created and coupled via -textvariable to a variable
  in a different itcl object
- due to the -textvariable button and variable are entered in the
  tracelist of that variable
- that variable is destroyed when the itcl object is destroyed, the
  variable trace triggers and re-establishes the variable since it
  still is coupled to the button (tkButton.c, ButtonTextVarProc())
     /*
     * If the variable is unset, then immediately recreate it unless the whole
     * interpreter is going away.
     */
  
- later the button is destroyed and tries to untrace the variable,
  (tkButton.c, DestroyButton())
     if (butPtr->textVarNamePtr != NULL) {
	Tcl_UntraceVar(butPtr->interp, Tcl_GetString(butPtr->textVarNamePtr),
		TCL_GLOBAL_ONLY|TCL_TRACE_WRITES|TCL_TRACE_UNSETS,
		ButtonTextVarProc, (ClientData) butPtr);
     }
 
  but for some reason I don't understand yet, Tcl_UntraceVar() does
  not find the variable and just returns, leaving the stale button
  pointer in the global trace list.

- now Buttons and Variables get recreated, and the pointers are
  eventually reused.  Now it can happen that a button without
  -textvariable is assigned the (stale) pointer in the trace list, and
  a variable coupled to a different button is assigned the variable
  pointer which leads to the stale button pointer.  When the traces
  for that variable are executed, the stale button pointer (which in
  the meantime is a valid button again, but without -textvariable) is
  passed to ButtonTextVarProc(), which tries to get the -textvariable
  and crashes.


Here is the code, maybe someone with deeper insight can find why the
button does not find the variable when the button is destroyed.
It definetly is related to itcl (couldn't reproduce it using ordinary
namespaces) and the way itcl variables are named when using
[itcl::scope] and in the objects themselves.

The code creates 4 buttons in one itcl object and round-robin couples
them to textvariables in a different itcl object.  The buttons and
objects are destroyed and re-created using the event loop, so sooner or
later the combination of pointers described above triggers.


# ==============================

package require Itk

itcl::class bar {
    public {
	method config_textvar {w var} {
	    $w configure -textvariable [itcl::scope $var]
	    set $var [clock seconds]
	}
    }
    private {
	variable foo 0
	variable bar 0
    }    
}

itcl::class foo {
    inherit itk::Widget
    constructor args {
	create
    }

    public {
	method create {} {
	    set obj_ [uplevel \#0 bar \#auto]
	    pack [frame $itk_interior.f] -fill both -expand yes
	    pack [button $itk_interior.f.a] [button $itk_interior.f.b] [button $itk_interior.f.c] [button $itk_interior.f.d] -fill both -expand yes
	    switch -- $count_ {
		0 {
		    $obj_ config_textvar $itk_interior.f.a foo
		    $itk_interior.f.b configure -textvariable [itcl::scope count_]
		}
		1 {
		    $obj_ config_textvar $itk_interior.f.b foo
		    $itk_interior.f.c configure -textvariable [itcl::scope count_]
		}
		2 {
		    $obj_ config_textvar $itk_interior.f.c foo
		    $itk_interior.f.a configure -textvariable [itcl::scope count_]
		}
	    }
	    after $interval_ms_ [itcl::code $this assign]
	}
	method assign {} {
	    incr count_
	    if {$count_ > 2} { set count_ 0 }
	    set foo_ $count_
	    after $interval_ms_ [itcl::code $this remove]
	}
	method remove {} {
	    rename $obj_ {}
	    destroy $itk_interior.f
	    unset foo_
	    after $interval_ms_ [itcl::code $this create]
	}
    }
    private {
	variable interval_ms_ 100
	variable count_ 0
	variable foo_ 0
	variable obj_ {}
    }
}

wm geometry . 200x200
update idle
pack [foo .foo] -fill both -expand yes

# End of file
User Comments: dgp added on 2014-07-23 15:18:47:
Report moved over to Itcl.

http://core.tcl.tk/itcl/tktview/e773fdd793

dgp added on 2014-07-23 15:11:44:
Sorry, no.  That was a testing error.

Itcl 4 / Tcl 8.6 work properly.

The best way forward is to do whatever is
needed (if anything) to make Itk work with
Itcl 4 and Tcl/Tk 8.6.

dgp added on 2014-07-23 15:02:01:
This appears to demo a regression in Itcl 3
when moving from Tcl 8.4 to Tcl 8.5.  My guess
is that it directly connects to the "variable
reform" that took place back then.

When extensions like Itcl intrude in Tcl's private
internals, they have to keep up with changes like
that.  Appears this aspect of Itcl 3 never caught
up to that transition.

Even worse, it appears that Itcl 4/ Tcl 8.6 suffer
the same trouble.

dgp added on 2014-07-23 14:17:37:
Thanks to teo, here's a demo of the problem
without use of Itk:

package require Itcl 3

itcl::class bar {
    public {
	method config_textvar {w var} {
	    $w configure -textvariable [itcl::scope $var]
	    set $var [clock seconds]
	}
    }
    private {
	variable foo 0
	variable bar 0
    }    
}

itcl::class foo {
    constructor args {
	create
    }

    public {
	method create {} {
	    set obj_ [uplevel \#0 bar \#auto]
	    pack [frame .f] -fill both -expand yes
	    pack [button .f.a] [button .f.b] [button .f.c] [button .f.d] -fill both -expand yes
	    switch -- $count_ {
		0 {
		    $obj_ config_textvar .f.a foo
		    .f.b configure -textvariable [itcl::scope count_]
		}
		1 {
		    $obj_ config_textvar .f.b foo
		    .f.c configure -textvariable [itcl::scope count_]
		}
		2 {
		    $obj_ config_textvar .f.c foo
		    .f.a configure -textvariable [itcl::scope count_]
		}
	    }
	    after $interval_ms_ [itcl::code $this assign]
	}
	method assign {} {
	    incr count_
	    if {$count_ > 2} { set count_ 0 }
	    set foo_ $count_
	    after $interval_ms_ [itcl::code $this remove]
	}
	method remove {} {
	    rename $obj_ {}
	    destroy .f
	    unset foo_
	    after $interval_ms_ [itcl::code $this create]
	}
    }
    private {
	variable interval_ms_ 100
	variable count_ 0
	variable foo_ 0
	variable obj_ {}
    }
}

wm geometry . 200x200
update idle
foo .foo

dgp added on 2013-12-03 15:18:26:
Is this a regression?  If so, what release(s)
of Tcl, Tk, Itcl, Itk permitted this demo script
to function properly?  Can the change that caused
trouble be isolated?

dkf added on 2013-12-03 11:55:13:

I'm not sure if Itcl is a necessary part of this bug; if so, it may actually be a bug in Itcl rather than in Tcl. OTOH, it might still be a bug in Tcl anyway. I don't know the guts of variables and traces nearly well enough to comment.