Tk Source Code

View Ticket
Login
Ticket UUID: 43573999cae70c8db2b7a234dd4ba0da63adb37a
Title: Problem with tkBind.c since Tk 8.6.10 (various assertions fail)
Type: Bug Version: 8.6.10
Submitter: robgcs Created on: 2022-09-27 14:20:08
Subsystem: 01. Bindings Assigned To: fvogel
Priority: 5 Medium Severity: Critical
Status: Closed Last Modified: 2022-10-26 19:42:16
Resolution: Fixed Closed By: fvogel
    Closed on: 2022-10-26 19:42:16
Description:
Hello, everybody

We have noticed that since Tk 8.6.10 our application crashes in connection with bindings. Different kinds of assertions fail in tkBind.c, for instance:
   tkBind.c:2825: MatchPatterns: Assertion `(psPtr->object == NULL) == (physPtrPtr != NULL)'
   tkBind.c:2489: Tk_BindEvent: Assertion `i + 1 < psEntry->psPtr->numPats'
   tkBind.c:2827: MatchPatterns: Assertion `psPtr->numPats > patIndex'
   tkBind.c:2824: MatchPatterns: Assertion `TEST_PSENTRY(psPtr)'

From what I can tell at a glance, major changes were made to tkBind.c between 8.6.9 and 8.6.10.

I have attached a small script that can be used to reproduce the crashes (see below).

Could you guys please take another look at this?

Kind regards,

Robert


#!/bin/sh
#\
exec wish "$0" ${1+"$@"}

# If you draw a line, the program crashes with Tk since 8.6.10.
#
# Tested under
#  Ubuntu 18.04/Tk 8.6.8 (works)
#  Ubuntu 20.04/Tk 8.6.10 (crashes)
#  Ubuntu 22.04/Tk 8.6.12 (crashes)
#  openSUSE 15.4/Tk 8.6.12 (crashes)
# among others
#
# (8.6.9 works, 8.6.11 crashes)
#
# If Tk was compiled without debugging information, it may take several
# attempts to crash the program.

proc startLin {c} {
   unbindCan $c; # try without this (shouldn't crash then)

   # Some array (move mouse after <ButtonRelease-1>):
   set arr($c,foofoofoo) ""
   set arr($c,barbarbar) 1

   # or try this instead (and move mouse, leave canvas or draw another line after <ButtonRelease-1>):
#   set arr($c,foo) ""
#   set arr($c,bar) 1
   
   # Try this (shouldn't crash then)
#   unset arr
   
   bindCan $c [array get arr]
}

proc bindCan {c args} {
   bind $c <ButtonPress-1>    "createLin %W %x %y"
   bind $c <B1-Motion>        "drawLin %W %x %y"
   # Try without the next two lines (shouldn't crash then)
   bind $c <B1-Motion><Leave> "leaveCan %W"
   bind $c <B1-Motion><Enter> "enterCan %W"
   bind $c <ButtonRelease-1>  "startLin %W"
}

proc unbindCan {c} {
   bind $c <ButtonPress-1> {}
   bind $c <B1-Motion> {}
   # Try without the next two lines (shouldn't crash then)
   bind $c <B1-Motion><Leave> {}
   bind $c <B1-Motion><Enter> {}
   bind $c <ButtonRelease-1> {}
}

proc createLin {c x y} {
   set x0 [$c canvasx $x]
   set y0 [$c canvasy $y]

   $c delete linTag
   $c create line $x0 $y0 $x0 $y0 -tags linTag
}

proc drawLin {c x y} {
   lassign [$c coords linTag] x0 y0
   $c coords linTag [list $x0 $y0 [$c canvasx $x] [$c canvasy $y]]
}

proc leaveCan {c} {
   # nothing
}

proc enterCan {c} {
   # nothing
}

set c [canvas .c]
pack $c
startLin $c
User Comments: fvogel added on 2022-10-26 19:42:16:
Thanks ;-)

Now merged in core-8-6-branch and trunk. Closing.

marc_culler (claiming to be Marc Culler) added on 2022-10-25 13:06:14:
Well done, François!

fvogel added on 2022-10-24 22:19:30:

I have a fix, see [aac99dcf401]. All bind tests still pass in the test suite, and the new bind-37.1 stops crashing and is passing now.


fvogel added on 2022-10-24 21:29:21:

I have committed a testcase demonstrating the crash (or assertion failed in debug mode), see branch bug-43573999ca.

I'm now having an understanding of what's happening. Basically, the successive <B1-Motion><Enter> pattern sequences get all promoted because they are partially matching the <B1-Motion> event. When the binding gets deleted in proc A only one instance of the promoted <B1-Motion><Enter> pattern sequences gets deleted in RemovePatSeqFromPromotionLists(), not all promoted (identical) patterne sequences. The next <Motion> event crashes in MatchPattern because of stale pattern sequences still in the promotion array.

Stay tuned, I'm working on the fix.


fvogel added on 2022-10-23 08:34:56:
Oh, I realize I didn't give the recipe to reproduce the crash with either of my two trimmed down examples below: you have to run the code in tclsh, then, in the "." window, click with left button, move the mouse a bit while button-1 is still down, and release the button. At this point it has not yet crashed. Now move the mouse: the next <Motion> event crashes Tk (assertion failed).

fvogel added on 2022-10-22 15:30:17:

Another trimmed down version, not making use of 'after idle' this time:

  package require Tk
  proc A {} {
     bind .c <B1-Motion><Enter> {}
     set myv(a) 1
     set b [array get myv]
  #   unset b   ; # doesn't crash if present
     bind .c <B1-Motion><Enter> "puts Trigger"
  }
  pack [canvas .c]
  bind .c <ButtonRelease-1>  "A"
  A

It is a very strange bug. In proc A above, even if array 'myv' is not used this code is needed to make assertions fail in the bindings code (on Windows and Linux at least - my first experiments on macOS tend to show it's not the case on the mac with -fsanitize=address). Add "unset b" and the failed asserts in the binding code stop. This is very puzzling.

To the OP: a simple workaround for your application is to bind to a script that does nothing in proc unbindCan, e.g. bind to {#} instead of bind to {} (which internally deletes the binding). This stops the crash from happening since the binding is no longer deleted but replaced by a void script.


fvogel added on 2022-09-27 20:07:08:

Very interesting. Here is a trimmed down script triggering the bug:

  package require Tk
  proc A {} {
     bind .c <B1-Motion><Enter> {}
     after idle [list bind .c <B1-Motion><Enter> "puts Trigger"]
  }
  pack [canvas .c]
  bind .c <ButtonRelease-1>  "A"
  A


Attachments: