Don't enqueue a repaint in the middle of one

This can easily lead to an infinite loop, if a variable handler
triggers a repaint and the variable is set in the prompt, e.g. some of
the git variables.

A simple way to reproduce:

    function fish_mode_prompt
        commandline -f repaint
    end

Repainting executes the mode prompt, which triggers a repaint, which
triggers the mode prompt, ....

So we just set a flag and check it.

Fixes #7324.
This commit is contained in:
Fabian Homborg 2020-09-11 19:23:26 +02:00
parent c6cdc06a5b
commit 30b2dc2b97
4 changed files with 26 additions and 0 deletions

View file

@ -294,8 +294,14 @@ maybe_t<int> builtin_commandline(parser_t &parser, io_streams_t &streams, wchar_
return STATUS_INVALID_ARGS; return STATUS_INVALID_ARGS;
} }
using rl = readline_cmd_t;
for (i = w.woptind; i < argc; i++) { for (i = w.woptind; i < argc; i++) {
if (auto mc = input_function_get_code(argv[i])) { if (auto mc = input_function_get_code(argv[i])) {
// Don't enqueue a repaint if we're currently in the middle of one,
// because that's an infinite loop.
if (mc == rl::repaint_mode || mc == rl::force_repaint || mc == rl::repaint) {
if (ld.is_repaint) continue;
}
// Inserts the readline function at the back of the queue. // Inserts the readline function at the back of the queue.
reader_queue_ch(*mc); reader_queue_ch(*mc);
} else { } else {

View file

@ -151,6 +151,10 @@ struct library_data_t {
/// Number of recursive calls to builtin_complete(). /// Number of recursive calls to builtin_complete().
uint32_t builtin_complete_recursion_level{0}; uint32_t builtin_complete_recursion_level{0};
/// If we're currently repainting the commandline.
/// Useful to stop infinite loops.
bool is_repaint{false};
/// Whether we called builtin_complete -C without parameter. /// Whether we called builtin_complete -C without parameter.
bool builtin_complete_current_commandline{false}; bool builtin_complete_current_commandline{false};

View file

@ -2781,10 +2781,17 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
// //
// Because some users set `fish_mode_prompt` to an empty function and display the mode // Because some users set `fish_mode_prompt` to an empty function and display the mode
// elsewhere, we detect if the mode output is empty. // elsewhere, we detect if the mode output is empty.
// Don't go into an infinite loop of repainting.
// This can happen e.g. if a variable triggers a repaint,
// and the variable is set inside the prompt (#7324).
// builtin commandline will refuse to enqueue these.
parser().libdata().is_repaint = true;
exec_mode_prompt(); exec_mode_prompt();
if (!mode_prompt_buff.empty()) { if (!mode_prompt_buff.empty()) {
s_reset_line(&screen, true /* redraw prompt */); s_reset_line(&screen, true /* redraw prompt */);
if (this->is_repaint_needed()) this->layout_and_repaint(L"mode"); if (this->is_repaint_needed()) this->layout_and_repaint(L"mode");
parser().libdata().is_repaint = false;
break; break;
} }
// Else we repaint as normal. // Else we repaint as normal.
@ -2792,10 +2799,12 @@ void reader_data_t::handle_readline_command(readline_cmd_t c, readline_loop_stat
} }
case rl::force_repaint: case rl::force_repaint:
case rl::repaint: { case rl::repaint: {
parser().libdata().is_repaint = true;
exec_prompt(); exec_prompt();
s_reset_line(&screen, true /* redraw prompt */); s_reset_line(&screen, true /* redraw prompt */);
this->layout_and_repaint(L"readline"); this->layout_and_repaint(L"readline");
force_exec_prompt_and_repaint = false; force_exec_prompt_and_repaint = false;
parser().libdata().is_repaint = false;
break; break;
} }
case rl::complete: case rl::complete:

View file

@ -23,3 +23,10 @@ sendline(
expect_prompt() expect_prompt()
sendline("echo one \"two three\" four'five six'{7} 'eight~") sendline("echo one \"two three\" four'five six'{7} 'eight~")
expect_prompt("\r\n@GUARD:2@\r\n(.*)\r\n@/GUARD:2@\r\n") expect_prompt("\r\n@GUARD:2@\r\n(.*)\r\n@/GUARD:2@\r\n")
# Check that we don't infinitely loop here.
sendline("function fish_mode_prompt; commandline -f repaint; end")
expect_prompt()
sendline("echo foo")
expect_prompt("foo")