I have a better understanding of what's going on now, but I still think my
original patch is basically correct. Below
is a simple program that will reliably make vim unresponsive. Just compile/run
the program then do :reg in vim.
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <X11/Xlib.h>
#include <X11/Xatom.h>
int
main()
{
Display *const dpy = XOpenDisplay(NULL);
if (!dpy) {
fprintf(stderr, "XOpenDisplay failed!\n");
abort();
}
Window w;
XGetInputFocus(dpy, &w, &(int){0}); // see man
if(w == None) {
fprintf(stderr, "no focus window\n");
XCloseDisplay(dpy);
return 1;
}
// Own clipboards
XSetSelectionOwner(dpy, XA_PRIMARY, w, CurrentTime);
XSetSelectionOwner(dpy, XInternAtom(dpy, "CLIPBOARD", 0), w, CurrentTime);
XFlush(dpy);
// --
while (1);
return 0;
}
> Hmm, strange. I had always thought, and the man page seems to suggest
> this as well, that when XtAppPending() returns non-zero, then
> XtAppNextEvent() will get the next event. The manual is unclear, but
> perhaps an XtIMTimer event is handled inside XtAppPending() and then it
> blocks on waiting for another event?
When a timer goes off, XtAppPending returns with mask XtIMTimer then the event
is handled in XtAppNextEvent.
Unfortunately, XtAppNextEvent won't return until it processes an XtIMXEvent.
Looking at vim _without_ my patch. Consider this gdb session where I started
the above program, put a
breakpoint on the selection callback function and then did :reg in vim. Vim is
locked up after the second callback, so
I sent an abort signal to vim that dropped us back into gdb. From there we can
see that we are still stuck in XtAppNextEvent because there are no XtIMXEvents
to process.
Breakpoint 2, clip_x11_request_selection_cb (w=0xa9d7b0, success=0x87a820
<success>, sel_atom=0x89b930, type=0x7fffffffdb08, value=0x0,
length=0x7fffffffdb00,
format=0x7fffffffdafc) at ui.c:2130
2130 {
(gdb) bt
#0 clip_x11_request_selection_cb (w=0xa9d7b0, success=0x87a820 <success>,
sel_atom=0x89b930, type=0x7fffffffdb08, value=0x0, length=0x7fffffffdb00,
format=0x7fffffffdafc)
at ui.c:2130
#1 0x00007ffff64b9be7 in ?? () from /usr/lib/libXt.so.6
#2 0x00007ffff64b34f6 in ?? () from /usr/lib/libXt.so.6
#3 0x00007ffff64b405f in XtAppNextEvent () from /usr/lib/libXt.so.6
#4 0x0000000000521191 in xterm_update () at os_unix.c:7101
#5 0x00000000005248ec in RealWaitForChar (fd=0, msec=msec@entry=0,
check_for_gpm=check_for_gpm@entry=0x0) at os_unix.c:5469
#6 0x00000000005273e3 in mch_breakcheck () at os_unix.c:5111
#7 0x000000000059cacf in ui_breakcheck () at ui.c:367
#8 0x0000000000511c31 in ex_display (eap=<optimized out>) at ops.c:4195
#9 0x0000000000492b7d in do_one_cmd (cookie=0x0, fgetline=0x4a4a10
<getexline>, cstack=0x7fffffffe1b0, sourcing=0, cmdlinep=0x7fffffffe038) at
ex_docmd.c:2940
#10 do_cmdline (cmdline=cmdline@entry=0x0, fgetline=<optimized out>,
cookie=cookie@entry=0x0, flags=<optimized out>) at ex_docmd.c:1133
#11 0x00000000004fe195 in nv_colon (cap=0x7fffffffe6e0) at normal.c:5373
#12 0x00000000005076ce in normal_cmd (oap=oap@entry=0x7fffffffe7a0,
toplevel=toplevel@entry=1) at normal.c:1160
#13 0x00000000005d63ed in main_loop (cmdwin=0, noexmode=0) at main.c:1334
#14 0x000000000043776f in main (argc=0, argv=0x1) at main.c:1034
(gdb) c
Continuing.
Breakpoint 2, clip_x11_request_selection_cb (w=0xa9d7b0, success=0x87a820
<success>, sel_atom=0xaa6200, type=0x7fffffffdb08, value=0x0,
length=0x7fffffffdb00,
format=0x7fffffffdafc) at ui.c:2130
2130 {
(gdb) bt
#0 clip_x11_request_selection_cb (w=0xa9d7b0, success=0x87a820 <success>,
sel_atom=0xaa6200, type=0x7fffffffdb08, value=0x0, length=0x7fffffffdb00,
format=0x7fffffffdafc)
at ui.c:2130
#1 0x00007ffff64b9be7 in ?? () from /usr/lib/libXt.so.6
#2 0x00007ffff64b34f6 in ?? () from /usr/lib/libXt.so.6
#3 0x00007ffff64b405f in XtAppNextEvent () from /usr/lib/libXt.so.6
#4 0x0000000000521191 in xterm_update () at os_unix.c:7101
#5 0x00000000005248ec in RealWaitForChar (fd=0, msec=msec@entry=0,
check_for_gpm=check_for_gpm@entry=0x0) at os_unix.c:5469
#6 0x00000000005273e3 in mch_breakcheck () at os_unix.c:5111
<snip>
(gdb) c
Continuing.
Program received signal SIGABRT, Aborted.
0x00007ffff489b4d0 in __poll_nocancel () from /usr/lib/libc.so.6
(gdb) bt
#0 0x00007ffff489b4d0 in __poll_nocancel () from /usr/lib/libc.so.6
#1 0x00007ffff64b2c68 in _XtWaitForSomething () from /usr/lib/libXt.so.6
#2 0x00007ffff64b40a7 in XtAppNextEvent () from /usr/lib/libXt.so.6
#3 0x0000000000521191 in xterm_update () at os_unix.c:7101
#4 0x00000000005248ec in RealWaitForChar (fd=0, msec=msec@entry=0,
check_for_gpm=check_for_gpm@entry=0x0) at os_unix.c:5469
#5 0x00000000005273e3 in mch_breakcheck () at os_unix.c:5111
#6 0x000000000059cacf in ui_breakcheck () at ui.c:367
<snip>
> After your change, wouldn't XtIMTimer events be sitting in the queue
> until a character is typed?
Probably not, but the truth is racy. Looking at vim _with_ my patch and again
I'm running the clipboard
hijacking program at the beginning of this email. Frame 2 is the function call
I introduced that will process
a timer event and return.
(gdb) bt
#0 clip_x11_request_selection_cb (w=0xa9d7b0, success=0x87a860 <success>,
sel_atom=0xa78580, type=0x7fffffffda48, value=0x0, length=0x7fffffffda40,
format=0x7fffffffda3c)
at ui.c:2130
#1 0x00007ffff64b9be7 in ?? () from /usr/lib/libXt.so.6
#2 0x00007ffff64b4334 in XtAppProcessEvent () from /usr/lib/libXt.so.6
#3 0x00000000005211e9 in xterm_update () at os_unix.c:7114
#4 0x000000000052496c in RealWaitForChar (fd=0, msec=msec@entry=0,
check_for_gpm=check_for_gpm@entry=0x0) at os_unix.c:5469
#5 0x0000000000527463 in mch_breakcheck () at os_unix.c:5111
#6 0x000000000059cb4f in ui_breakcheck () at ui.c:367
#7 0x0000000000511c81 in ex_display (eap=<optimized out>) at ops.c:4195
#8 0x0000000000492bcd in do_one_cmd (cookie=0x0, fgetline=0x4a4a60
<getexline>, cstack=0x7fffffffe1b0, sourcing=0, cmdlinep=0x7fffffffe038) at
ex_docmd.c:2940
#9 do_cmdline (cmdline=cmdline@entry=0x0, fgetline=<optimized out>,
cookie=cookie@entry=0x0, flags=<optimized out>) at ex_docmd.c:1133
#10 0x00000000004fe1e5 in nv_colon (cap=0x7fffffffe6e0) at normal.c:5373
#11 0x000000000050771e in normal_cmd (oap=oap@entry=0x7fffffffe7a0,
toplevel=toplevel@entry=1) at normal.c:1160
#12 0x00000000005d646d in main_loop (cmdwin=0, noexmode=0) at main.c:1334
#13 0x00000000004377bf in main (argc=0, argv=0x1) at main.c:1034
Looking at line 4195 in ops.c (frame 7), this function call is supposed to be
giving us a chance to catch ctrl+c interrupts. RealWaitForChar calls
xterm_update (where my patch makes it possible to handle the timer event)
before we even call the select() that should let us handle breaks. So in this
sense we are definitely not waiting on a keystroke in order to handle timer
events.
If one were to comment out the ui_breakcheck line in ex_display() you can see
that the multiple dis_msg() calls are also capable of getting us into
xterm_update thus handling the expired timer. It's possible that the timer
won't expire until after we're done with the ex_display(). In this case we
should end up in the high level input loop whose backtrace looks like this.
#0 0x00007ffff489d193 in __select_nocancel () from /usr/lib/libc.so.6
#1 0x00000000005247fb in RealWaitForChar (fd=0, msec=msec@entry=4000,
check_for_gpm=check_for_gpm@entry=0x7fffffffe40c) at os_unix.c:5499
#2 0x0000000000524cff in WaitForChar (msec=<optimized out>) at os_unix.c:5168
#3 0x0000000000524ff4 in mch_inchar (buf=buf@entry=0x8751c4 <typebuf_init+68>
"", maxlen=maxlen@entry=65, wtime=wtime@entry=-1,
tb_change_cnt=tb_change_cnt@entry=38)
at os_unix.c:421
#4 0x000000000059e8d8 in ui_inchar (buf=buf@entry=0x8751c4 <typebuf_init+68>
"", maxlen=maxlen@entry=65, wtime=wtime@entry=-1,
tb_change_cnt=tb_change_cnt@entry=38)
at ui.c:199
#5 0x00000000004b972f in inchar (buf=0x8751c4 <typebuf_init+68> "",
maxlen=196, wait_time=-1, tb_change_cnt=38) at getchar.c:3098
#6 0x00000000004bb715 in vgetorpeek (advance=advance@entry=1) at
getchar.c:2873
#7 0x00000000004bbe8e in vgetc () at getchar.c:1638
#8 0x00000000004bc2e9 in safe_vgetc () at getchar.c:1843
#9 0x0000000000506c1e in normal_cmd (oap=oap@entry=0x7fffffffe7a0,
toplevel=toplevel@entry=1) at normal.c:638
#10 0x00000000005d63ed in main_loop (cmdwin=0, noexmode=0) at main.c:1334
#11 0x000000000043776f in main (argc=0, argv=0x1) at main.c:1034
In this case yes, we're waiting for either a keystroke or for 'msec'
milliseconds to pass (which is the global variable p_ut aka updatetime in
option.c). When we eventually process the callback, this is what it will do.
clip_x11_request_selection_cb(...)
{
// ....
if (*sel_atom == clip_plus.sel_atom)
cbd = &clip_plus;
else
cbd = &clip_star;
if (value == NULL || *length == 0)
{
clip_free_selection(cbd); /* nothing received, clear register */
*(int *)success = FALSE;
return;
}
// ...
}
It looks like there is probably a race condition where someone yanks into */+
(clip_star/clip_plus) then the timer goes off and the callback does
clip_free_selection() which eventually calls free_yank() on the register that
someone just yanked into. So my patch introduces the possibility of data
disappearing from the */+ registers in a racy way.
There is the possibility of a worse outcome if a piece of code does something
like this:
0. stick data in */+ register
1. call xterm_update
2. assume data is still in */+ register, start dereferencing things
This could cause a crash if we process an expired timer @ 1. But such an idiom
should probably be considered incorrect?
###########################################################
###########################################################
At a high level, there are 3 selection owner states.
+ Well behaving owner: vim sends SelectionRequests, owner responds with
SelectionNotify
+ No owner: vim sends SelectionRequests, xserver responds with empty
SelectionNotify
+ Negligent owner: vim sends SelectionRequests, no response is sent
This third case results in vim locking up. We are introducing a race where
data can disappear from a register in order to prevent vim from locking up when
the selection owner is misbehaving.
There is of course the possibility of introducing the fix in some other way,
but I don't currently see a simplish way to do so.
--
Aaron Burrow
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/d/optout.