Tk Source Code

View Ticket
Login
Ticket UUID: d8f9640fcd97c952444ff4e1aa9f4066982b9d62
Title: Segfault under Windows when executing test file menu.test with "-singleproc 0"
Type: Bug Version: trunk
Submitter: erikleunissen Created on: 2025-08-11 15:53:02
Subsystem: 10. Generic Menus Assigned To: jan.nijtmans
Priority: 5 Medium Severity: Severe
Status: Closed Last Modified: 2026-09-25 08:31:15
Resolution: Fixed Closed By: nobody
    Closed on:
Description:
A segfault occurs when executing the test file menu.test with -singleproc 0.
Currently, all.tcl does not support using "-singleproc 0". Therefore, you need
to change in all.tcl:

    tcltest::configure -singleproc 1

to

    tcltest::configure -singleproc 0

The OS kills the process in which menu.test is executing. Any remaining test
files keep on executing each in their separate process without problem.
Therefore, this issue is specific for the test file menu.test. The invocation
below selects that test file for the sake of efficiency.

-  Tk version: Tk9.1a0 (but the same happens with Tk9.0.2 and Tk8.6.16)
-  Invocation 1: wish91.exe all.tcl -file menu.test
-  Invocation 2: tktest.exe all.tcl -file menu.test
-  You may add "-match NONE" to the above invocations: executing the test file
   suffices, executing any tests is not necessary.
-  Reproducibility: always
-  Back trace for tktest.exe from Dr. Mingw:

tktest.exe caused an Access Violation at location 000000006B067367 in module tcl9tk91.dll Reading from location 0000000000000068.

AddrPC           Params
000000006B067367 000000000065FAF0 0000000003746720 0000000003746838  tcl9tk91.dll!ConfigureMenu.isra.0+0x287
000000006B069D99 00000000006E4400 0000000003699240 0000000000000000  tcl9tk91.dll!Tk_MenuObjCmd+0x289
000000006E7DBF68 000000000065FAF0 000000006E87CD90 0000000003031090  tcl91.dll!TclNRRunCallbacks+0x68
000000006E7DDE11 000000000065FAF0 00000000036CEF00 000000000001F2FA  tcl91.dll!TclEvalEx+0x581
000000006E891E03 000000006B142C90 0000000000000028 00000000006A6D80  tcl91.dll!Tcl_FSEvalFileEx+0x1a3
000000006B06568B 0000000000000006 0000000000000005 0000000000401590  tcl9tk91.dll!Tk_MainExW+0x2bb
0000000000401748 000000000000007C 0000000000000005 0000000000408978  tktest.exe!wmain+0x48
00000000004013EE 0000000000000000 0000000000000000 0000000000000000  tktest.exe!__tmainCRTStartup+0x26e  [/home/abuild/rpmbuild/BUILD/mingw-w64-v7.0.0/mingw-w64-crt/crt/crtexe.c @ 334]
000000000040150B 0000000000000000 0000000000000000 0000000000000000  tktest.exe!WinMainCRTStartup+0x1b  [/home/abuild/rpmbuild/BUILD/mingw-w64-v7.0.0/mingw-w64-crt/crt/crtexe.c @ 195]
0000000077B059CD 0000000000000000 0000000000000000 0000000000000000  kernel32.dll!BaseThreadInitThunk+0xd
0000000077C6385D 0000000000000000 0000000000000000 0000000000000000  ntdll.dll!RtlUserThreadStart+0x1d
User Comments: serhiy.storchaka added on 2026-09-25 08:31:15:

Fixed in 8.6.18 and 9.0.4 by [ff1d83ab98] (merged as [5046dd9646] and [0f90da4514]): the menu code checks for a missing parent before comparing its class. Closing.


jan.nijtmans added on 2026-03-10 16:27:36:

Ashok's proposal now [f3e40c73322e1751|merged]. I could reproduce the crash. No crash is better - at least - than a crash.

Still - test-cases are failing with -singleproc 0:

menu.test

==== menu-5.7 DestroyMenuInstance - basic clones FAILED ==== Contents of test case:

menu .m1 set tearoff [tk::TearOffMenu .m1] list [destroy $tearoff] [destroy .m1]

---- Test generated error; Return code was: 1 ---- Return code should have been one of: 0 2 ---- errorInfo: bad window path name "" while executing "winfo toplevel $parent" (procedure "tk::TearOffMenu" line 24) invoked from within "tk::TearOffMenu .m1" ("uplevel" body line 3) invoked from within "uplevel 1 $script" ---- errorCode: TK LOOKUP WINDOW {} ==== menu-5.7 FAILED .... ==== menu-35.1 FAILED

Tests ended at 2026-03-10 17:19:06 CET all.tcl: Total 553 Passed 531 Skipped 15 Failed 7 Sourced 1 Test Files.

So, keeping this ticket open.


emiliano added on 2026-01-16 21:05:27:

A bit of context: The bug originates trying to fix this bug. This work leads to TIP 359 afterwards, making Tk usable in such environment.

WRT to what the code is trying to do, in words of dkf:

Actually, it wants to be one of:

  1. _NET_WM_WINDOW_TYPE_MENU - for torn off menus
  2. _NET_WM_WINDOW_TYPE_DROPDOWN_MENU - for "real" menus from the menubar
  3. _NET_WM_WINDOW_TYPE_POPUP_MENU - for menus that are neither of the other two (i.e. posted with tk_popup)

I will add that _NET_WM_WINDOW_TYPE_DROPDOWN_MENU should also apply to menubutton's associated menu.

The real fix for this bug implies a robust way to figure this out (easier said than done).


oehhar added on 2026-01-13 10:03:23:

I would be in favor to remove the "-tearoff" option in 9.1. I think, this would make the code much easier.

Harald


apnadkarni added on 2026-01-13 08:18:28:
Oops, I meant Christian's patch

apnadkarni added on 2026-01-13 08:15:34:

I am clearly over my head in Tk! No surprise. I'll leave it to folks who are actually competent to address this, whether via my branch or Emiliano's patch. That's assuming it is fixable in the first place, which based on comments from Emiliano and Christian, I'm unsure of.


chw added on 2026-01-12 19:26:16:
At least can we agree now, that this is an almost grown-up bug present since late point releases of 8.5, bummer!

emiliano added on 2026-01-12 19:05:59:
chw: yes, you are right, is the pointer which is there, not the struct itself! So the code is plain wrong.

I have to admit that menu code is non trivial, given the mix of menubars, tearoffs, popup and clones which are all named "menu". Worse than that, you can convert them to plain widgets.

menu .menu -type tearoff
.menu add command -label hello
wm forget .menu
place .menu -x 50 -y 50

Madness!

chw added on 2026-01-12 18:16:27:
@emiliano, unfortunately a Tk_Window is an opaque handle with pointer size.
And its real beef is managed elsewhere, i.e. not in the tkMenu.c module.

I believe the intention for the if-clause was something along

    int typeFlag = TK_MAKE_MENU_POPUP;
    ...
    while (1) {
        Tk_Window parent = Tk_Parent(tkwin);
        if (Tk_IsTopLevel(parent)) {
            typeFlag = TK_MAKE_MENU_DROPDOWN;
            break;
        }
        if (Tk_Class(parent) != Tk_Class(menuPtr->tkwin)) {
            break;
        }
        tkwin = parent;
    }

The typeFlag goes down to the X11 platform specific menu creation
code which uses it to set some obscure window manager hints (which
may or may not have further consequences on the user experience).
Other windowing systems are not affected by the difference between
TK_MAKE_MENU_POPUP and TK_MAKE_MENU_DROPDOWN.

emiliano added on 2026-01-12 17:53:15:
@chw: In the case of the menu widget, the first member of the widget record is a Tk_Window. In generic/tkMenu.h, the definition is

typedef struct TkMenu {
    Tk_Window tkwin;
/* all other members */
} TkMenu;

in which case

    ((TkMenu *) tkwin)

points to the widget record. Incidentally. Until someone change the order.

chw added on 2026-01-12 16:25:36:
emiliano, it is worse, since tkwin being a Tk_Window never can be casted to a TkMenu.

emiliano added on 2026-01-12 14:59:15:
A slightly semantically better fix for this issue is attached, checking in the loop if the parent is a toplevel widget. This will fix the crash but NOT fix all other issues that might arise from widgets having the "wrong" class.

There's also a further potential bug from this code. The class can be set on any frame, toplevel or ttk widget, and the line after the loop

 		if (((TkMenu *) tkwin)->menuType == MENUBAR) {

assume that tkwin is a TkMenu*, but this is not necessarily so. Consider when applied to this code

frame .m -class Menu
menu .m.m

In this case, tkwin will point to .m, which is not a menu despite being of Menu class. I think a more robust solution is needed.

oehhar added on 2026-01-12 14:09:49:

Oh, no! The old issue, that the main window class is named following the file name. Couldn't we stop this?

That is why it crashes, if the test file is named "menu.test"...

I had that once with a file "scale.tcl", which gave the class "scale", and the main Menu got everything from the scale widget...

Thanks for all, Harald


emiliano added on 2026-01-12 13:54:37:
Reading apn's commit and further errors in tests, I realized that the problem is that the main window (aka ".") is of class Menu, leading to all kinds of problems.

This explain why the loop here
https://core.tcl-lang.org/tk/artifact?name=3d58a19246bdb50e&ln=1636-1643
crashes, as it walks the window hierarchy up until the main window, and then Tk_Parent(tkwin) returns NULL.

Also explains why, after apn's modification, test fails with errors like
```
unknown option "-type"
    while executing
". cget -type"
    invoked from within ...
```
as the main window is now of the "wrong" class.

The shortest script to fire this bug (present on both Tk 8 and 9) is (spanish locale):

$ cat crash.tcl 
package require Tk
puts [winfo class .]; flush stdout
menu .m
$ wish crash.tcl -name Menu
Menu
Violación de segmento (`core' generado)

Note that this will also happen if the main script is called simply "menu"

$ mv crash.tcl menu
$ wish menu
Menu
Violación de segmento (`core' generado)

erikleunissen added on 2026-01-12 13:15:20:
Regarding:

> I committed a possible workaround for that now. 

Thanks!

I just updated and recompiled. The previous compile error is gone.
The compile now stops at another position with:

/home/erik/priv/Develop/tk-fossil/win/tkWinSysTray.c: In function 'WinSysNotifyCmd':
/home/erik/priv/Develop/tk-fossil/win/tkWinSysTray.c:1134:2: warning: implicit declaration of function 'SetCurrentProcessExplicitAppUserModelID' [-Wimplicit-function-declaration]
 1134 |  SetCurrentProcessExplicitAppUserModelID(appid);
      |  ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/erik/priv/Develop/tk-fossil/win/tkWinSysTray.c:1166:19: error: 'NOTIFYICON_VERSION_4' undeclared (first use in this function); did you mean 'NOTIFYICON_VERSION'?
 1166 |     ni.uVersion = NOTIFYICON_VERSION_4;
      |                   ^~~~~~~~~~~~~~~~~~~~
      |                   NOTIFYICON_VERSION
/home/erik/priv/Develop/tk-fossil/win/tkWinSysTray.c:1166:19: note: each undeclared identifier is reported only once for each function it appears in
make: *** [Makefile:825: tkWinSysTray.o] Error 1

jan.nijtmans added on 2026-01-12 12:49:52:

> I still get the same compile error after having merged the latest trunk (commit c269205209) into the bug-fix branch.

I committed a possible workaround for that now.


erikleunissen added on 2026-01-12 12:29:37:
Regarding:

> Feels like this indicates that there is some interdependency between files in some fashion.

That is possible (but still an issue/bug) in mode "-singleproc 1".

However, it's impossible in mode "-singleproc 0", since each test file is
evaluated in a separate process (and therefore a separate interpreter).

So, probably obvious, but nevertheless to prevent misunderstanding: could you
confirm that this was observed with "-singleproc 1" ?

apnadkarni added on 2026-01-12 12:02:54:

The other curious thing I noticed is that when I run menu.test by itself (using the -file option), I just see the test failures noted previously logged to the terminal. However, if I run the entire suite, I get a large number of error popups while menu.test is running similar to those below:

unknown option "-type"
    while executing
". cget -type"
    invoked from within
"if {[. cget -type] eq "tearoff"} {
	if {"NotifyNormal" ne "NotifyUngrab"} {
	    if {[tk windowingsystem] eq "x11"} {
		tk_menuSetFocus .
	    }
	}
  ..."
    (command bound to event)

or

bad option "index": must be cget or configure
bad option "index": must be cget or configure
    while executing
"$menu index active"
    (procedure "tk::MenuLeave" line 4)
    invoked from within
"tk::MenuLeave . 209 232 0"
    (command bound to event)

or while running radiobutton.test,

unknown option "-state"
unknown option "-state"
    while executing
"$w cget -state"
    (procedure "tk::CheckRadioEnter" line 3)
    invoked from within
"tk::CheckRadioEnter ."
    (command bound to event)

Feels like this indicates that there is some interdependency between files in some fashion. Otherwise, why the difference in behaviour where a full run shows popups but just running menu.test does not?


erikleunissen added on 2026-01-12 11:54:50:
I still get the same compile error after having merged the latest trunk (commit c269205209) into the bug-fix branch. (B.t.w. I'm using the mingw-w64 cross-toolchain  from Linux, which normally builds fine).

apnadkarni added on 2026-01-12 11:47:53:

@oehhar, I already tried creating a test case but could not ascertain exactly what conditions result in the parent being NULL other than running the test suite in -singleproc 0 mode. There is something different in initialization depending on -singleproc but could not tell what.

@erikleunissen, the failures cannot be seen on main trunk since menu.test simply crashes with -singleproc 0. The failures seen on the bug-d8f9640fcd branch are below. I'm not sure it not a consequence of my change so not filing a separate ticket as yet. (Ran with TESTFLAGS="-f menu.test -singleproc 0")

==== menu-5.7 DestroyMenuInstance - basic clones FAILED
==== Contents of test case:

    menu .m1
    set tearoff [tk::TearOffMenu .m1]
    list [destroy $tearoff] [destroy .m1]

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0 2
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .m1"
    ("uplevel" body line 3)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-5.7 FAILED



==== menu-5.8 DestroyMenuInstance - multiple clones FAILED
==== Contents of test case:

    menu .m1
    set tearoff1 [tk::TearOffMenu .m1]
    set tearoff2 [tk::TearOffMenu .m1]
    list [destroy $tearoff1] [destroy .m1]

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .m1"
    ("uplevel" body line 3)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-5.8 FAILED



==== menu-5.9 DestroyMenuInstace - main menu FAILED
==== Contents of test case:

    menu .m1
    tk::TearOffMenu .m1
    destroy .m1

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .m1"
    ("uplevel" body line 3)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-5.9 FAILED



==== menu-5.13 DestroyMenuInstance - clones when mismatched tearoffs FAILED
==== Contents of test case:

    menu .m1
    menu .m2
    .m1 add cascade -menu .m2
    set tearoff [tk::TearOffMenu .m1 40 40]
    list [destroy .m2] [destroy .m1]

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0 2
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .m1 40 40"
    ("uplevel" body line 5)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-5.13 FAILED



==== menu-16.16 MenuAddOrInsert FAILED
==== Contents of test case:

    menu .m1
    menu .m2
    set tearoff [tk::TearOffMenu .m2]
    list [.m2 add cascade -menu .m1] [$tearoff unpost]

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0 2
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .m2"
    ("uplevel" body line 4)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-16.16 FAILED



==== menu-16.17 MenuAddOrInsert FAILED
==== Contents of test case:

    menu .m1
    menu .container
    . configure -menu .container
    set tearoff [tk::TearOffMenu .container]
    list [.container add cascade -label "File" -menu .m1] [. configure -menu ""]

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0 2
---- errorInfo: bad window path name ""
    while executing
"winfo toplevel $parent"
    (procedure "tk::TearOffMenu" line 24)
    invoked from within
"tk::TearOffMenu .container"
    ("uplevel" body line 5)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP WINDOW {}
==== menu-16.17 FAILED



==== menu-35.1 menu -underline string overruns Bug 1599877 FAILED
==== Contents of test case:

    # ensure that -underline does not do string overruns [Bug 1599877]
    menu .m
    .m add command -label "File" -underline [expr {1<<30}]
    . configure -menu .m
    update
    tk::TraverseToMenu . "e"

---- Test generated error; Return code was: 1
---- Return code should have been one of: 0 2
---- errorInfo: unknown option "-type"
    while executing
"$w cget -type"
    (procedure "tk::TraverseToMenu" line 7)
    invoked from within
"tk::TraverseToMenu . "e""
    ("uplevel" body line 7)
    invoked from within
"uplevel 1 $script"
---- errorCode: TK LOOKUP OPTION -type
==== menu-35.1 FAILED

oehhar added on 2026-01-12 11:46:08:

Isn't this fixed by Jan by this commit: [b9b5d4fa0ec9f9f4] today in the main branch? Just an idea, Harald


kevin_walzer added on 2026-01-12 11:41:47:
Jan just committed a fix for the compiler error to trunk. Try updating and rebuilding.

erikleunissen added on 2026-01-12 11:21:24:
I can't test that because I get this compile error for MS Windows:

/home/erik/priv/Develop/tk-fossil/win/tkWinIco.c: In function 'GetFileIcon':
/home/erik/priv/Develop/tk-fossil/win/tkWinIco.c:283:17: error: 'SHIL_JUMBO' undeclared (first use in this function)
  283 |     else shil = SHIL_JUMBO;
      |                 ^~~~~~~~~~
/home/erik/priv/Develop/tk-fossil/win/tkWinIco.c:283:17: note: each undeclared identifier is reported only once for each function it appears in
make: *** [Makefile:825: tkWinIco.o] Error 1

oehhar added on 2026-01-12 11:17:36:

Thanks, great! Eric, you were the initial submitter, right? Is the patch effective for you?

Thanks! Harald


erikleunissen added on 2026-01-12 11:10:59:
* Regarding the option -singleproc in general:

  Since the inclusion of RFE "Simplify testfile initialization" (commit #c2d3890e3c),
  this option can be simply passed on the command line instead of editing the file
  all.tcl. (At the time of filing this ticket, that RFE wasn't merged yet.)

* Regarding:

  > Note that setting singleProc to 0 will cause other tests in menu.test to fail
  > as they expect a certain common environment to have been set up.

  This indicates a bug. However, I cannot reproduce this on Linux, and alas I
  currently cannot test this on MS Windows. I'm happy to investigate this further,
  but I'd recommend to file a separate ticket for this issue, with more specific
  information about the extra test failures.

oehhar added on 2026-01-12 09:03:14:

Thanks, Ashok, great. It should be possible to create a test case for this. Perhaps, Csaba can comment on the fix...

Thanks for all, Harald


apnadkarni added on 2026-01-12 08:02:44:

Proposed fix in [ff1d83ab]. Tested by setting singleProc to 0 in all.tcl as suggested.

Note that setting singleProc to 0 will cause other tests in menu.test to fail as they expect a certain common environment to have been set up.

In addition to being a "tactical" fix of checking for a NULL pointer, it also seems to be the logically correct fix. The loop is looking for the furthest ancestor of the same class so breaks if the class is different. If there is no further ancestor (parent == NULL) that also means the current tkwin is the furthest ancestor and should therefore exit the loop.

But then again, I do not really know Tk so someone should verify.


oehhar added on 2025-08-19 20:15:20:

My test branch is in commit [b20bb269], which starts the branch [d8f9640f-mswin-segfault-menu-test].


oehhar added on 2025-08-19 20:08:22:

Your stack trace tells, that the menu command is invoked and it crashes in the configure option. This is in the generic code and not Windows specific.

As the test file crashes without tests (and before any tests), the issue must be in the test file initialization.

We only have in menu.test:

testutils import image
imageInit
outside any test.

# commenting this out does not cure the segfault.

In line 153, there is outside of any test:

# Used for 2.1 - 2.30 tests
destroy .m1
menu .m1

This looks bogus, but commenting it out does not cure the segfault.

I tried to comment out any command outside of a test with no change.

Sorry, Harald


oehhar added on 2025-08-19 19:39:48:

I tried to reproduce with options symbols and noembed on the current main branches and MS-VS2022 on 64 bit and I get:

nmake -f makefile.vc test OPTS=noembed,symbols TCLDIR=c:\test\fossil\tcl\main TESTFLAGS="-file menu.test -match NONE"
...
Microsoft (R) Program Maintenance Utility, Version 14.44.35214.0
...
Test file error: child killed: segmentation violation

So, there is a child process started and this one segfaults.

It would be great to have the child process stopping for a while, so I may attach the VisualStudio debugger.

The stack trace looks similar to this bug: https://core.tcl-lang.org/tk/tktview/f4213443734676892385. Mostly, a script is evaluated by a callback, which removes some resources. When the call returns, the resources are gone.

I was looking to it by using the debugger and tracing through the code. But this is now not possible, as there is no possibility to attach the debugger.

Sorry, Harald


oehhar added on 2025-08-12 06:28:17:

Thanks for the catch. I have traced similar issues with the debugger. It is typically a misplaced tcl_preserve. No time at the moment, sorry.

Harald


Attachments: