ui_bridge:ui_bridge_stop() calls ui_detach_impl() last, so the check for
ui_active() in ui:ui_refresh() doesn't help: tui_main() already freed
the `ui` object.
There is a race between ui_bridge_stop (thread T0) and tui_main (thread T1).
UIBridgeData.stopped could be set while ui_bridge_stop() is in the
middle of loop_poll_events(), which may invoke tui_scheduler() on T0.
The pointers in tui_scheduler() may be invalid by then.
Solution(?): Use the `UI.data` field as a "stopped" flag and check it in
tui_scheduler().
ASAN use-after-free report observed in #7908:
= ==20066==ERROR: AddressSanitizer: heap-use-after-free on address 0x611000000cd0 at pc 0x00000182abed bp 0x7ffe23b07070 sp 0x7ffe23b07068
= READ of size 8 at 0x611000000cd0 thread T0
= 0 0x182abec in tui_scheduler /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:393:23
= 1 0x1876afd in ui_bridge_update_fg /home/travis/build/neovim/neovim/build/src/nvim/auto/ui_events_bridge.generated.h:205:3
= 2 0x186c130 in ui_resize /home/travis/build/neovim/neovim/src/nvim/ui.c:310:3
= 3 0x146b9c2 in screen_resize /home/travis/build/neovim/neovim/src/nvim/screen.c:7483:3
= 4 0x186a6f0 in ui_refresh /home/travis/build/neovim/neovim/src/nvim/ui.c:284:3
= 5 0x186bbe0 in ui_refresh_event /home/travis/build/neovim/neovim/src/nvim/ui.c:297:3
= 6 0xa2219a in multiqueue_process_events /home/travis/build/neovim/neovim/src/nvim/event/multiqueue.c:150:7
= 7 0xa1bd7f in loop_poll_events /home/travis/build/neovim/neovim/src/nvim/event/loop.c:63:3
= 8 0x1872709 in ui_bridge_stop /home/travis/build/neovim/neovim/src/nvim/ui_bridge.c:121:5
= 9 0x1864247 in ui_builtin_stop /home/travis/build/neovim/neovim/src/nvim/ui.c:143:3
= 10 0x1249ec8 in mch_exit /home/travis/build/neovim/neovim/src/nvim/os_unix.c:140:3
= 11 0xe56ba9 in getout /home/travis/build/neovim/neovim/src/nvim/main.c:671:3
= 12 0xfc4c8f in preserve_exit /home/travis/build/neovim/neovim/src/nvim/misc1.c:2653:3
= 13 0x1247c02 in deadly_signal /home/travis/build/neovim/neovim/src/nvim/os/signal.c:137:3
= 14 0x1247921 in on_signal /home/travis/build/neovim/neovim/src/nvim/os/signal.c:162:9
= 15 0xa35618 in signal_event /home/travis/build/neovim/neovim/src/nvim/event/signal.c:47:3
= 16 0xa2219a in multiqueue_process_events /home/travis/build/neovim/neovim/src/nvim/event/multiqueue.c:150:7
= 17 0xa1bd7f in loop_poll_events /home/travis/build/neovim/neovim/src/nvim/event/loop.c:63:3
= 18 0x1237bd6 in input_poll /home/travis/build/neovim/neovim/src/nvim/os/input.c:349:3
= 19 0x123334f in inbuf_poll /home/travis/build/neovim/neovim/src/nvim/os/input.c:372:24
= 20 0x123316d in os_inchar /home/travis/build/neovim/neovim/src/nvim/os/input.c:110:19
= 21 0x170d20e in state_enter /home/travis/build/neovim/neovim/src/nvim/state.c:55:13
= 22 0xbd7441 in command_line_enter /home/travis/build/neovim/neovim/src/nvim/ex_getln.c:384:3
= 23 0xbd0a60 in getcmdline /home/travis/build/neovim/neovim/src/nvim/ex_getln.c:1920:10
= 24 0xbdb365 in getexline /home/travis/build/neovim/neovim/src/nvim/ex_getln.c:2100:10
= 25 0xb00a6b in do_cmdline /home/travis/build/neovim/neovim/src/nvim/ex_docmd.c:528:47
= 26 0x10a7837 in nv_colon /home/travis/build/neovim/neovim/src/nvim/normal.c:4552:18
= 27 0x1091e15 in normal_execute /home/travis/build/neovim/neovim/src/nvim/normal.c:1136:3
= 28 0x170d439 in state_enter /home/travis/build/neovim/neovim/src/nvim/state.c:67:26
= 29 0x104ee14 in normal_enter /home/travis/build/neovim/neovim/src/nvim/normal.c:466:3
= 30 0xe4295c in main /home/travis/build/neovim/neovim/src/nvim/main.c:572:3
= 31 0x2b2ba340bf44 in __libc_start_main /build/eglibc-ripdx6/eglibc-2.19/csu/libc-start.c:287
= 32 0x44d24b in _start (/home/travis/build/neovim/neovim/build/bin/nvim+0x44d24b)
=
= 0x611000000cd0 is located 16 bytes inside of 240-byte region [0x611000000cc0,0x611000000db0)
= freed by thread T1 here:
= 0 0x4ee0e2 in __interceptor_free /local/mnt/workspace/tmp/ubuntu_rel/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:47:3
= 1 0xf4f6d4 in xfree /home/travis/build/neovim/neovim/src/nvim/memory.c:133:3
= 2 0x182a963 in tui_main /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:383:3
= 3 0x18792b0 in ui_thread_run /home/travis/build/neovim/neovim/src/nvim/ui_bridge.c:106:3
= 4 0x2b2ba2697183 in start_thread /build/eglibc-ripdx6/eglibc-2.19/nptl/pthread_create.c:312
=
= previously allocated by thread T0 here:
= 0 0x4ee61a in calloc /local/mnt/workspace/tmp/ubuntu_rel/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_malloc_linux.cc:76:3
= 1 0xf4f787 in xcalloc /home/travis/build/neovim/neovim/src/nvim/memory.c:147:15
= 2 0x182000a in tui_start /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:127:12
= 3 0x1863f7c in ui_builtin_start /home/travis/build/neovim/neovim/src/nvim/ui.c:125:3
= 4 0xe41bb9 in main /home/travis/build/neovim/neovim/src/nvim/main.c:457:5
= 5 0x2b2ba340bf44 in __libc_start_main /build/eglibc-ripdx6/eglibc-2.19/csu/libc-start.c:287
=
= Thread T1 created by T0 here:
= 0 0x4d774d in __interceptor_pthread_create /local/mnt/workspace/tmp/ubuntu_rel/llvm/utils/release/final/llvm.src/projects/compiler-rt/lib/asan/asan_interceptors.cc:317:3
= 1 0x1aae6b0 in uv_thread_create /home/travis/nvim-deps/build/src/libuv/src/unix/thread.c:75
= 2 0x18217fa in tui_start /home/travis/build/neovim/neovim/src/nvim/tui/tui.c:159:10
= 3 0x1863f7c in ui_builtin_start /home/travis/build/neovim/neovim/src/nvim/ui.c:125:3
= 4 0xe41bb9 in main /home/travis/build/neovim/neovim/src/nvim/main.c:457:5
= 5 0x2b2ba340bf44 in __libc_start_main /build/eglibc-ripdx6/eglibc-2.19/csu/libc-start.c:287
---
Alternative attempt:
commit 6ad9c02491606a0c31e907f38c9931f324327aa5
Author: Justin M. Keyes <justinkz@gmail.com>
Date: Sat Jan 27 15:12:58 2018 +0100
tui: fix use-after-free: swap in empty scheduler
This should make life easier for UIs like VimR which implement their own
in-process bridged UI: they don't need to worry that their `scheduler`
might receive an invalid pointer.
To avoid that, ui_bridge_stopped() swaps in an empty scheduler. Note
that this requires the call to loop_poll_events() to be moved into the
critical section.
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
index 779585416f80..491052d19d3b 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -93,10 +93,18 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
return &rv->bridge;
}
+static void ui_bridge_null_scheduler(Event event, void *d)
+{
+ WLOG("ignoring event (bridge stopped)");
+}
+
void ui_bridge_stopped(UIBridgeData *bridge)
{
uv_mutex_lock(&bridge->mutex);
bridge->stopped = true;
+ // Replace with an empty scheduler, so that the UI internal scheduler does
+ // not get invoked with an invalid pointer. #7922
+ bridge->scheduler = ui_bridge_null_scheduler;
uv_mutex_unlock(&bridge->mutex);
}
@@ -111,14 +119,11 @@ static void ui_bridge_stop(UI *b)
UIBridgeData *bridge = (UIBridgeData *)b;
bool stopped = bridge->stopped = false;
UI_BRIDGE_CALL(b, stop, 1, b);
- for (;;) {
+ while (!stopped) {
uv_mutex_lock(&bridge->mutex);
stopped = bridge->stopped;
- uv_mutex_unlock(&bridge->mutex);
- if (stopped) {
- break;
- }
loop_poll_events(&main_loop, 10); // Process one event (at most).
+ uv_mutex_unlock(&bridge->mutex);
}
uv_thread_join(&bridge->ui_thread);
uv_mutex_destroy(&bridge->mutex);
...in order to retrieve highlights.
Added test/functional/api/highlight_spec.lua
HL_NORMAL is not really a good name, since it's more like an empty attribute than the normal's one.
If one pays attention, syn_cterm_attr2entry is never called with attr=0 because it's always special cased before.
I suggest in subsequent PRs we remove the ATTR_OFF and just insert an EMPTY ATTR/RESET_ATTR/UNINITIALIZED for id 0.
Closes#1234
multiqueue:
- Implement multiqueue_size()
- Rename MultiQueueItem.parent to MultiQueueItem.parent_item, to avoid confusion
with MultiQueue.parent.
`utf_ambiguous_width` expects the Unicode character, but in 9e1c6596 I
just passed the first UTF-8 byte to the function. This led to various
display problems because now many multi-cell characters weren't falling
into that part of the branch.
Also, to better align with the existing Vim code, remove the forced
cursor update. Setting the flag will cause it to happen in the next
UI_CALL.
Thanks to qvacua for all the help investigating the issue!
Closes#5448
Problem: Display problems when the 'ambiwidth' and 'emoji' options are not
set properly or the terminal doesn't behave as expected.
Solution: After drawing an ambiguous width character always position the
cursor.
cb0700844c
move `call_shell` to misc1.c
Move some fns to state.c
Move some fns to option.c
Move some fns to memline.c
Move `vim_chdir*` fns to file_search.c
Move some fns to new module, bytes.c
Move some fns to fileio.c
The TUI can be enabled/disabled at build time with -DFEAT_TUI, default is ON for
UNIX, and OFF for non UNIX. When off, Neovim prints a message to stderr, along
with a list of the server endpoints.
The UI_CALL macro uses variadic macros and relies on the non standard
GCC extension. MSVC already applies similar behavior for the regular
__VA_ARGS__ removing the comma.
In MSVC UI_CALL can be implemented by calling UI_CALL_MORE directly.
- Improve the implementation of deferred/immediate events.
- Use the new queue module to change how/when events are queued/processed by
giving a private queue to each emitter.
- Immediate events(which only exist to break uv_run recursion) are now
represented in the `loop->fast_events` queue.
- Events pushed to child queues are propagated to the event loop main queue and
processed as K_EVENT keys.
This is a port of my original contribution to Vim, added in 7.4.687
(https://github.com/vim/vim/commit/v7-4-687). The TUI code has been
heavily refactored (see esp. 25ceadab37),
so this required some translation, but the logic is the same.
Currently, there are two functions in the UI API that are called when
the mode changes: insert_mode() and normal_mode(). These can be folded
into a single mode_change() entrypoint which can do whatever it wants
based on the mode it is passed, limited to INSERT and NORMAL for now.
- Add event loop abstraction module under src/nvim/event. The
src/nvim/event/loop module replaces src/nvim/os/event
- Remove direct dependency on libuv signal/timer API and use the new abstraction
instead.
- Replace all references to uv_default_loop() by &loop.uv, a new global variable
that wraps libuv main event loop but allows the event loop functions to be
reused in other contexts.
Even though assuming nvim is busy most times is simpler, it has a problem: A lot
of unnecessary busy_start/busy_stop notifications are sent to the UI. That's
because in the majority of scenarios almost no time is spent between
`event_poll` calls.
This restores the normal behavior which is to call busy_start only when nvim is
going to perform some task that can take a significant amount of time. Also
improve the usage of buffering in the TUI when changing the cursor state.