Fix a bug in the implementation of the synchronized updates escape code that could cause incorrect parsing if either the pending buffer capacity or the pending timeout were exceeded
Fixes #3779
This commit is contained in:
parent
fe991ee767
commit
026d200add
@ -4,6 +4,13 @@ Changelog
|
|||||||
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
|kitty| is a feature-rich, cross-platform, *fast*, GPU based terminal.
|
||||||
To update |kitty|, :doc:`follow the instructions <binary>`.
|
To update |kitty|, :doc:`follow the instructions <binary>`.
|
||||||
|
|
||||||
|
0.21.3 [future]
|
||||||
|
----------------------
|
||||||
|
|
||||||
|
- Fix a bug in the implementation of the synchronized updates escape code that
|
||||||
|
could cause incorrect parsing if either the pending buffer capacity or the
|
||||||
|
pending timeout were exceeded (:iss:`3779`)
|
||||||
|
|
||||||
0.21.2 [2021-06-28]
|
0.21.2 [2021-06-28]
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
|||||||
@ -1013,7 +1013,9 @@ dispatch_dcs(Screen *screen, PyObject DUMP_UNUSED *dump_callback) {
|
|||||||
} else {
|
} else {
|
||||||
// ignore stop without matching start, see queue_pending_bytes()
|
// ignore stop without matching start, see queue_pending_bytes()
|
||||||
// for how stop is detected while in pending mode.
|
// for how stop is detected while in pending mode.
|
||||||
REPORT_ERROR("Pending mode stop command issued while not in pending mode");
|
REPORT_ERROR("Pending mode stop command issued while not in pending mode, this can"
|
||||||
|
" be either a bug in the terminal application or caused by a timeout with no data"
|
||||||
|
" received for too long or by too much data in pending mode");
|
||||||
REPORT_COMMAND(screen_stop_pending_mode);
|
REPORT_COMMAND(screen_stop_pending_mode);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -1409,19 +1411,39 @@ end:
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void
|
static void
|
||||||
|
create_pending_space(Screen *screen, size_t needed_space) {
|
||||||
|
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + needed_space);
|
||||||
|
if (screen->pending_mode.capacity > READ_BUF_SZ) screen->pending_mode.capacity = screen->pending_mode.used + needed_space;
|
||||||
|
screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
|
||||||
|
if (!screen->pending_mode.buf) fatal("Out of memory");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
dump_partial_escape_code_to_pending(Screen *screen) {
|
||||||
|
if (screen->parser_buf_pos) {
|
||||||
|
const size_t needed_space = 4 * screen->parser_buf_pos + 8;
|
||||||
|
if (screen->pending_mode.used + needed_space >= screen->pending_mode.capacity) create_pending_space(screen, needed_space);
|
||||||
|
write_pending_char(screen, screen->parser_state);
|
||||||
|
for (unsigned i = 0; i < screen->parser_buf_pos; i++) write_pending_char(screen, screen->parser_buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) {
|
do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz, monotonic_t now, PyObject *dump_callback DUMP_UNUSED) {
|
||||||
enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING};
|
enum STATE {START, PARSE_PENDING, PARSE_READ_BUF, QUEUE_PENDING};
|
||||||
enum STATE state = START;
|
enum STATE state = START;
|
||||||
size_t read_buf_pos = 0;
|
size_t read_buf_pos = 0;
|
||||||
|
unsigned int parser_state_at_start_of_pending = 0;
|
||||||
|
|
||||||
do {
|
do {
|
||||||
switch(state) {
|
switch(state) {
|
||||||
case START:
|
case START:
|
||||||
if (screen->pending_mode.activated_at) {
|
if (screen->pending_mode.activated_at) {
|
||||||
if (screen->pending_mode.activated_at + screen->pending_mode.wait_time < now) {
|
if (screen->pending_mode.activated_at + screen->pending_mode.wait_time < now) {
|
||||||
|
dump_partial_escape_code_to_pending(screen);
|
||||||
screen->pending_mode.activated_at = 0;
|
screen->pending_mode.activated_at = 0;
|
||||||
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
state = START;
|
||||||
} else state = QUEUE_PENDING;
|
} else state = QUEUE_PENDING;
|
||||||
} else {
|
} else {
|
||||||
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
state = screen->pending_mode.used ? PARSE_PENDING : PARSE_READ_BUF;
|
||||||
@ -1429,6 +1451,8 @@ do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
case PARSE_PENDING:
|
case PARSE_PENDING:
|
||||||
|
screen->parser_state = parser_state_at_start_of_pending;
|
||||||
|
parser_state_at_start_of_pending = 0;
|
||||||
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback);
|
_parse_bytes(screen, screen->pending_mode.buf, screen->pending_mode.used, dump_callback);
|
||||||
screen->pending_mode.used = 0;
|
screen->pending_mode.used = 0;
|
||||||
screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
|
screen->pending_mode.activated_at = 0; // ignore any pending starts in the pending bytes
|
||||||
@ -1445,15 +1469,14 @@ do_parse_bytes(Screen *screen, const uint8_t *read_buf, const size_t read_buf_sz
|
|||||||
const size_t needed_space = read_buf_sz * 2;
|
const size_t needed_space = read_buf_sz * 2;
|
||||||
if (screen->pending_mode.capacity - screen->pending_mode.used < needed_space) {
|
if (screen->pending_mode.capacity - screen->pending_mode.used < needed_space) {
|
||||||
if (screen->pending_mode.capacity >= READ_BUF_SZ) {
|
if (screen->pending_mode.capacity >= READ_BUF_SZ) {
|
||||||
// Too much pending data, drain it
|
dump_partial_escape_code_to_pending(screen);
|
||||||
screen->pending_mode.activated_at = 0;
|
screen->pending_mode.activated_at = 0;
|
||||||
state = START;
|
state = START;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
screen->pending_mode.capacity = MAX(screen->pending_mode.capacity * 2, screen->pending_mode.used + needed_space);
|
create_pending_space(screen, needed_space);
|
||||||
screen->pending_mode.buf = realloc(screen->pending_mode.buf, screen->pending_mode.capacity);
|
|
||||||
if (!screen->pending_mode.buf) fatal("Out of memory");
|
|
||||||
}
|
}
|
||||||
|
if (!screen->pending_mode.used) parser_state_at_start_of_pending = screen->parser_state;
|
||||||
read_buf_pos += queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
read_buf_pos += queue_pending_bytes(screen, read_buf + read_buf_pos, read_buf_sz - read_buf_pos, dump_callback);
|
||||||
state = START;
|
state = START;
|
||||||
} break;
|
} break;
|
||||||
|
|||||||
@ -888,8 +888,11 @@ set_mode_from_const(Screen *self, unsigned int mode, bool val) {
|
|||||||
if (val) {
|
if (val) {
|
||||||
self->pending_mode.activated_at = monotonic();
|
self->pending_mode.activated_at = monotonic();
|
||||||
} else {
|
} else {
|
||||||
if (!self->pending_mode.activated_at) log_error("Pending mode stop command issued while not in pending mode");
|
if (!self->pending_mode.activated_at) log_error(
|
||||||
self->pending_mode.activated_at = 0;
|
"Pending mode stop command issued while not in pending mode, this can"
|
||||||
|
" be either a bug in the terminal application or caused by a timeout with no data"
|
||||||
|
" received for too long or by too much data in pending mode");
|
||||||
|
else self->pending_mode.activated_at = 0;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|||||||
@ -370,6 +370,21 @@ class TestParser(BaseTest):
|
|||||||
pb('\033[?2026h\033P+q544e\033\\ama\033P=2s\033\\',
|
pb('\033[?2026h\033P+q544e\033\\ama\033P=2s\033\\',
|
||||||
('screen_set_mode', 2026, 1), ('screen_stop_pending_mode',), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'))
|
('screen_set_mode', 2026, 1), ('screen_stop_pending_mode',), ('screen_request_capabilities', 43, '544e'), ('draw', 'ama'))
|
||||||
|
|
||||||
|
s.reset()
|
||||||
|
s.set_pending_timeout(timeout)
|
||||||
|
pb('\033[?2026h', ('screen_set_mode', 2026, 1),)
|
||||||
|
pb('\033P+q')
|
||||||
|
time.sleep(1.2 * timeout)
|
||||||
|
pb(
|
||||||
|
'544e' + '\033\\\033P=2s\033\\',
|
||||||
|
('screen_request_capabilities', 43, '544e'),
|
||||||
|
('Pending mode stop command issued while not in pending mode, this can be '
|
||||||
|
'either a bug in the terminal application or caused by a timeout with no '
|
||||||
|
'data received for too long or by too much data in pending mode',),
|
||||||
|
('screen_stop_pending_mode',)
|
||||||
|
)
|
||||||
|
self.assertEqual(str(s.line(0)), '')
|
||||||
|
|
||||||
def test_oth_codes(self):
|
def test_oth_codes(self):
|
||||||
s = self.create_screen()
|
s = self.create_screen()
|
||||||
pb = partial(self.parse_bytes_dump, s)
|
pb = partial(self.parse_bytes_dump, s)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user