From f9844ba3b01f35171930fc35b6ac7c4302d96da0 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Thu, 21 Jan 2021 07:06:43 +0530 Subject: [PATCH] Allow negative numbers in CSI codes --- kitty/cursor.c | 10 ++++---- kitty/data-types.h | 4 ++-- kitty/parser.c | 56 ++++++++++++++++++++++++++++++++----------- kitty/screen.c | 6 ++--- kitty/screen.h | 2 +- kitty_tests/parser.py | 3 +++ 6 files changed, 56 insertions(+), 25 deletions(-) diff --git a/kitty/cursor.c b/kitty/cursor.c index 53713a0fe..4480a014a 100644 --- a/kitty/cursor.c +++ b/kitty/cursor.c @@ -47,7 +47,7 @@ cursor_reset_display_attrs(Cursor *self) { static inline void -parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t *result) { +parse_color(int *params, unsigned int *i, unsigned int count, uint32_t *result) { unsigned int attr; uint8_t r, g, b; if (*i < count) { @@ -73,7 +73,7 @@ parse_color(unsigned int *params, unsigned int *i, unsigned int count, uint32_t void -cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count) { +cursor_from_sgr(Cursor *self, int *params, unsigned int count) { #define SET_COLOR(which) { parse_color(params, &i, count, &self->which); } break; START_ALLOW_CASE_RANGE unsigned int i = 0, attr; @@ -90,7 +90,7 @@ START_ALLOW_CASE_RANGE case 3: self->italic = true; break; case 4: - if (i < count) { self->decoration = MIN(3u, params[i]); i++; } + if (i < count) { self->decoration = MIN(3, params[i]); i++; } else self->decoration = 1; break; case 7: @@ -136,7 +136,7 @@ END_ALLOW_CASE_RANGE } void -apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count) { +apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count) { #define RANGE for(unsigned c = 0; c < cell_count; c++, cell++) #define SET(shift) RANGE { cell->attrs |= (1 << shift); } break; #define RESET(shift) RANGE { cell->attrs &= ~(1 << shift); } break; @@ -161,7 +161,7 @@ apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *p case 3: SET(ITALIC_SHIFT); case 4: - if (i < count) { uint8_t val = MIN(3u, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); } + if (i < count) { uint8_t val = MIN(3, params[i]); i++; SETM(val, DECORATION_MASK, DECORATION_SHIFT); } else { SETM(1, DECORATION_MASK, DECORATION_SHIFT); } case 7: SET(REVERSE_SHIFT); diff --git a/kitty/data-types.h b/kitty/data-types.h index 953eedcc8..889c7ed12 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -299,8 +299,8 @@ void cursor_reset(Cursor*); Cursor* cursor_copy(Cursor*); void cursor_copy_to(Cursor *src, Cursor *dest); void cursor_reset_display_attrs(Cursor*); -void cursor_from_sgr(Cursor *self, unsigned int *params, unsigned int count); -void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, unsigned int *params, unsigned int count); +void cursor_from_sgr(Cursor *self, int *params, unsigned int count); +void apply_sgr_to_cells(GPUCell *first_cell, unsigned int cell_count, int *params, unsigned int count); const char* cell_as_sgr(const GPUCell *, const GPUCell *); const char* cursor_as_sgr(const Cursor *); diff --git a/kitty/parser.c b/kitty/parser.c index 2534663c3..6c201208e 100644 --- a/kitty/parser.c +++ b/kitty/parser.c @@ -22,10 +22,14 @@ static uint64_t pow10_array[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000 }; -static inline uint64_t -utoi(uint32_t *buf, unsigned int sz) { - uint64_t ans = 0; - uint32_t *p = buf; +static inline int64_t +utoi(const uint32_t *buf, unsigned int sz) { + int64_t ans = 0; + const uint32_t *p = buf; + int mult = 1; + if (sz && *p == '-') { + mult = -1; p++; sz--; + } // Ignore leading zeros while(sz > 0) { if (*p == '0') { p++; sz--; } @@ -36,7 +40,7 @@ utoi(uint32_t *buf, unsigned int sz) { ans += (p[i] - '0') * pow10_array[j]; } } - return ans; + return ans * mult; } @@ -79,12 +83,12 @@ _report_error(PyObject *dump_callback, const char *fmt, ...) { } static void -_report_params(PyObject *dump_callback, const char *name, unsigned int *params, unsigned int count, Region *r) { +_report_params(PyObject *dump_callback, const char *name, int *params, unsigned int count, Region *r) { static char buf[MAX_PARAMS*3] = {0}; unsigned int i, p=0; if (r) p += snprintf(buf + p, sizeof(buf) - 2, "%u %u %u %u ", r->top, r->left, r->bottom, r->right); for(i = 0; i < count && p < MAX_PARAMS*3-20; i++) { - int n = snprintf(buf + p, MAX_PARAMS*3 - p, "%u ", params[i]); + int n = snprintf(buf + p, MAX_PARAMS*3 - p, "%i ", params[i]); if (n < 0) break; p += n; } @@ -360,7 +364,8 @@ dispatch_osc(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { #define END_DISPATCH Py_CLEAR(string); } PyErr_Clear(); break; } const unsigned int limit = screen->parser_buf_pos; - unsigned int code=0, i; + int code=0; + unsigned int i; for (i = 0; i < MIN(limit, 5u); i++) { if (screen->parser_buf[i] < '0' || screen->parser_buf[i] > '9') break; } @@ -460,12 +465,12 @@ static inline void screen_tabn(Screen *s, unsigned int count) { for (index_type i=0; i < MAX(1u, count); i++) screen_tab(s); } static inline const char* -repr_csi_params(unsigned int *params, unsigned int num_params) { +repr_csi_params(int *params, unsigned int num_params) { if (!num_params) return ""; static char buf[256]; unsigned int pos = 0, i = 0; while (pos < 200 && i++ < num_params && sizeof(buf) > pos + 1) { - const char *fmt = i < num_params ? "%u, " : "%u"; + const char *fmt = i < num_params ? "%i, " : "%i"; int ret = snprintf(buf + pos, sizeof(buf) - pos - 1, fmt, params[i-1]); if (ret < 0) return "An error occurred formatting the params array"; pos += ret; @@ -478,7 +483,7 @@ repr_csi_params(unsigned int *params, unsigned int num_params) { static #endif void -parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) { +parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, int *params, PyObject DUMP_UNUSED *dump_callback, const char *report_name DUMP_UNUSED, Region *region) { enum State { START, NORMAL, MULTIPLE, COLOR, COLOR1, COLOR3 }; enum State state = START; unsigned int num_params, num_start, i; @@ -606,7 +611,8 @@ parse_sgr(Screen *screen, uint32_t *buf, unsigned int num, unsigned int *params, static inline unsigned int parse_region(Region *r, uint32_t *buf, unsigned int num) { - unsigned int i, start, params[8] = {0}, num_params=0; + unsigned int i, start, num_params = 0; + int params[8] = {0}; for (i=0, start=0; i < num && num_params < 4; i++) { switch(buf[i]) { IS_DIGIT @@ -646,10 +652,17 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { break; \ } \ } +#define NON_NEGATIVE_PARAM(x) { \ + if (x < 0) { \ + REPORT_ERROR("CSI code 0x%x is not allowed to have negative parameter (%d)", code, x); \ + break; \ + } \ +} #define CALL_CSI_HANDLER1(name, defval) \ AT_MOST_ONE_PARAMETER; \ p1 = num_params > 0 ? params[0] : defval; \ + NON_NEGATIVE_PARAM(p1); \ REPORT_COMMAND(name, p1); \ name(screen, p1); \ break; @@ -657,6 +670,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { #define CALL_CSI_HANDLER1P(name, defval, qch) \ AT_MOST_ONE_PARAMETER; \ p1 = num_params > 0 ? params[0] : defval; \ + NON_NEGATIVE_PARAM(p1); \ private = start_modifier == qch; \ REPORT_COMMAND(name, p1, private); \ name(screen, p1, private); \ @@ -665,6 +679,7 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { #define CALL_CSI_HANDLER1S(name, defval) \ AT_MOST_ONE_PARAMETER; \ p1 = num_params > 0 ? params[0] : defval; \ + NON_NEGATIVE_PARAM(p1); \ REPORT_COMMAND(name, p1, start_modifier); \ name(screen, p1, start_modifier); \ break; @@ -672,13 +687,20 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { #define CALL_CSI_HANDLER1M(name, defval) \ AT_MOST_ONE_PARAMETER; \ p1 = num_params > 0 ? params[0] : defval; \ + NON_NEGATIVE_PARAM(p1); \ REPORT_COMMAND(name, p1, end_modifier); \ name(screen, p1, end_modifier); \ break; #define CALL_CSI_HANDLER2(name, defval1, defval2) \ + if (num_params > 2) { \ + REPORT_ERROR("CSI code 0x%x has %u > 2 parameters", code, num_params); \ + break; \ + } \ p1 = num_params > 0 ? params[0] : defval1; \ p2 = num_params > 1 ? params[1] : defval2; \ + NON_NEGATIVE_PARAM(p1); \ + NON_NEGATIVE_PARAM(p2); \ REPORT_COMMAND(name, p1, p2); \ name(screen, p1, p2); \ break; @@ -701,8 +723,8 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { char start_modifier = 0, end_modifier = 0; uint32_t *buf = screen->parser_buf, code = screen->parser_buf[screen->parser_buf_pos]; - unsigned int num = screen->parser_buf_pos, start, i, num_params=0, p1, p2; - static unsigned int params[MAX_PARAMS] = {0}; + unsigned int num = screen->parser_buf_pos, start, i, num_params=0; + static int params[MAX_PARAMS] = {0}, p1, p2; bool private; if (buf[0] == '>' || buf[0] == '<' || buf[0] == '?' || buf[0] == '!' || buf[0] == '=') { start_modifier = (char)screen->parser_buf[0]; @@ -732,6 +754,12 @@ dispatch_csi(Screen *screen, PyObject DUMP_UNUSED *dump_callback) { switch(buf[i]) { IS_DIGIT break; + case '-': + if (i > start) { + REPORT_ERROR("CSI code can contain hyphens only at the start of numbers"); + return; + } + break; default: if (i > start) params[num_params++] = utoi(buf + start, i - start); else if (i == start && buf[i] == ';') params[num_params++] = 0; diff --git a/kitty/screen.c b/kitty/screen.c index 97a707acf..c58dec8d5 100644 --- a/kitty/screen.c +++ b/kitty/screen.c @@ -609,7 +609,7 @@ screen_alignment_display(Screen *self) { } void -select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count, Region *region_) { +select_graphic_rendition(Screen *self, int *params, unsigned int count, Region *region_) { if (region_) { Region region = *region_; if (!region.top) region.top = 1; @@ -2214,8 +2214,8 @@ reset_mode(Screen *self, PyObject *args) { static PyObject* _select_graphic_rendition(Screen *self, PyObject *args) { - unsigned int params[256] = {0}; - for (int i = 0; i < PyTuple_GET_SIZE(args); i++) { params[i] = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(args, i)); } + int params[256] = {0}; + for (int i = 0; i < PyTuple_GET_SIZE(args); i++) { params[i] = PyLong_AsLong(PyTuple_GET_ITEM(args, i)); } select_graphic_rendition(self, params, PyTuple_GET_SIZE(args), NULL); Py_RETURN_NONE; } diff --git a/kitty/screen.h b/kitty/screen.h index f1c06486c..36d1f3687 100644 --- a/kitty/screen.h +++ b/kitty/screen.h @@ -197,7 +197,7 @@ uint32_t* translation_table(uint32_t which); void screen_request_capabilities(Screen *, char, PyObject *); void screen_set_8bit_controls(Screen *, bool); void report_device_attributes(Screen *self, unsigned int UNUSED mode, char start_modifier); -void select_graphic_rendition(Screen *self, unsigned int *params, unsigned int count, Region*); +void select_graphic_rendition(Screen *self, int *params, unsigned int count, Region*); void report_device_status(Screen *self, unsigned int which, bool UNUSED); void report_mode_status(Screen *self, unsigned int which, bool); void screen_apply_selection(Screen *self, void *address, size_t size); diff --git a/kitty_tests/parser.py b/kitty_tests/parser.py index 459b28fc1..96bc5d2b5 100644 --- a/kitty_tests/parser.py +++ b/kitty_tests/parser.py @@ -101,6 +101,9 @@ class TestParser(BaseTest): pb('x\033[2@y', 'x', ('screen_insert_characters', 2), 'y') self.ae(str(s.line(0)), 'xy bc') pb('x\033[2;7@y', 'x', ('CSI code 0x40 has 2 > 1 parameters',), 'y') + pb('x\033[2;-7@y', 'x', ('CSI code 0x40 has 2 > 1 parameters',), 'y') + pb('x\033[-2@y', 'x', ('CSI code 0x40 is not allowed to have negative parameter (-2)',), 'y') + pb('x\033[2-3@y', 'x', ('CSI code can contain hyphens only at the start of numbers',), 'y') pb('x\033[@y', 'x', ('screen_insert_characters', 1), 'y') pb('x\033[345@y', 'x', ('screen_insert_characters', 345), 'y') pb('x\033[345;@y', 'x', ('screen_insert_characters', 345), 'y')