diff --git a/src/clint.lua b/src/clint.lua index c4cd2dd753..dd668d8db4 100755 --- a/src/clint.lua +++ b/src/clint.lua @@ -1088,6 +1088,20 @@ local function check_language(filename, clean_lines, linenum, error) ) end + -- Check for strtol: it silently overflows/truncates and needs error-prone boilerplate. + -- TODO: also flag atoi, atol, atoll, strtoul, strtoll, strtoull. + local strtol_regex = vim.regex([[\]]) + if strtol_regex:match_str(line) then + error( + filename, + linenum, + 'runtime/deprecated', + 4, + 'Use getdigits()/getdigits_int() (or vim_str2nr() for non-decimal bases) instead of strtol, ' + .. 'which overflows silently. See src/nvim/charset.c.' + ) + end + -- Check for memset with wrong argument order: memset(buf, sizeof(buf), 0) -- Pattern: memset(arg1, arg2, 0) where arg2 is NOT a valid fill value local memset_start = line:find('memset%s*%([^)]*,%s*[^,]*,%s*0%s*%)') diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c index cfe87cd641..30cc88ee4a 100644 --- a/src/nvim/api/command.c +++ b/src/nvim/api/command.c @@ -463,8 +463,8 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena count_value = first_arg.data.integer; } else if (first_arg.type == kObjectTypeString) { // Try to parse string as a number Example: vim.api.nvim_cmd({cmd = 'copen', args = {'10'}}, {}) - char *endptr; - long val = strtol(first_arg.data.string.data, &endptr, 10); + char *endptr = first_arg.data.string.data; + long val = getdigits_long(&endptr, false, 0); // Check if entire string was consumed (valid number) and string is not empty if (*endptr == '\0' && first_arg.data.string.size > 0) { is_numeric = true; diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c index b81951d382..a43d7285f5 100644 --- a/src/nvim/eval/window.c +++ b/src/nvim/eval/window.c @@ -9,6 +9,7 @@ #include "nvim/ascii_defs.h" #include "nvim/autocmd.h" #include "nvim/buffer.h" +#include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/errors.h" #include "nvim/eval/funcs.h" @@ -277,8 +278,8 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar) } } else { // Extract the window count (if specified). e.g. winnr('3j') - char *endp; - int count = (int)strtol(arg, &endp, 10); + char *endp = (char *)arg; + int count = getdigits_int(&endp, false, 0); if (count <= 0) { // if count is not specified, default to 1 count = 1; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index f0fed217f8..eb13e80290 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -407,7 +407,9 @@ void *vim_findfile_init(char *path, char *filename, size_t filenamelen, char *st ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; ff_expand_buffer.data[ff_expand_buffer.size++] = *wc_part++; - llevel = strtol(wc_part, &errpt, 10); + errpt = wc_part; + // Use def=255 so that overflow/too-large values keep the "max expand" behavior. + llevel = getdigits(&errpt, false, 255); if (errpt != wc_part && llevel > 0 && llevel < 255) { ff_expand_buffer.data[ff_expand_buffer.size++] = (char)llevel; } else if (errpt != wc_part && llevel == 0) { diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c index 9b893ffaf2..8cda1f7330 100644 --- a/src/nvim/highlight_group.c +++ b/src/nvim/highlight_group.c @@ -1478,7 +1478,8 @@ void do_highlight(const char *line, const bool forceit, const bool init) // Ignored for now } else if (strcmp(key, "BLEND") == 0) { if (strcmp(arg, "NONE") != 0) { - hl_table[idx].sg_blend = (int)strtol(arg, NULL, 10); + char *arg_end = arg; + hl_table[idx].sg_blend = getdigits_int(&arg_end, false, 0); } else { hl_table[idx].sg_blend = -1; } @@ -3182,7 +3183,7 @@ RgbValue name_to_color(const char *name, int *idx) && isxdigit((uint8_t)name[6]) && name[7] == NUL) { // rgb hex string *idx = kColorIdxHex; - return (RgbValue)strtol(name + 1, NULL, 16); + return (RgbValue)strtol(name + 1, NULL, 16); // NOLINT(runtime/deprecated) } else if (!STRICMP(name, "bg") || !STRICMP(name, "background")) { *idx = kColorIdxBg; return normal_bg; diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 9c0a9d371c..ad83540944 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -67,9 +67,11 @@ bool tabstop_set(char *var, colnr_T **array) for (char *cp = var; *cp != NUL; cp++) { if (cp == var || cp[-1] == ',') { - char *end; + char *end = cp; - if (strtol(cp, &end, 10) <= 0) { + // Use def=1 so that overflow/too-large values pass this check and are + // instead rejected by the "n > TABSTOP_MAX" check in the loop below. + if (getdigits(&end, false, 1) <= 0) { if (cp != end) { emsg(_(e_positive)); } else { diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 96f6494ffd..3a0fd6bbfc 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -14,6 +14,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/ascii_defs.h" +#include "nvim/charset.h" #include "nvim/cursor_shape.h" #include "nvim/event/defs.h" #include "nvim/event/loop.h" @@ -525,14 +526,16 @@ static void terminfo_start(TUIData *tui) char *konsolev_env = os_getenv("KONSOLE_VERSION"); char *term_program_version_env = os_getenv("TERM_PROGRAM_VERSION"); - int vtev = vte_version_env ? (int)strtol(vte_version_env, NULL, 10) : 0; + char *vte_version_end = vte_version_env; + int vtev = vte_version_env ? getdigits_int(&vte_version_end, false, 0) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); bool nsterm = (termprg && strstr(termprg, "Apple_Terminal")) || terminfo_is_term_family(term, "nsterm"); bool konsole = terminfo_is_term_family(term, "konsole") || os_env_exists("KONSOLE_PROFILE_NAME", true) || os_env_exists("KONSOLE_DBUS_SESSION", true); - int konsolev = konsolev_env ? (int)strtol(konsolev_env, NULL, 10) + char *konsolev_end = konsolev_env; + int konsolev = konsolev_env ? getdigits_int(&konsolev_end, false, 0) : (konsole ? 1 : 0); bool wezterm = strequal(termprg, "WezTerm"); const char *weztermv = wezterm ? term_program_version_env : NULL; diff --git a/test/functional/fixtures/clint_test.c b/test/functional/fixtures/clint_test.c index dc1eb1bd87..ff960450c3 100644 --- a/test/functional/fixtures/clint_test.c +++ b/test/functional/fixtures/clint_test.c @@ -140,6 +140,7 @@ void test_more() // Try getenv and setenv char *env = getenv("HOME"); // Should trigger runtime/os_fn setenv("TEST", "value", 1); // Should trigger runtime/os_fn + long n = strtol(env, NULL, 10); // Should trigger runtime/deprecated } int main() diff --git a/test/functional/script/clint_spec.lua b/test/functional/script/clint_spec.lua index 64e9d5e140..aca1cd2bcb 100644 --- a/test/functional/script/clint_spec.lua +++ b/test/functional/script/clint_spec.lua @@ -43,7 +43,8 @@ describe('clint.lua', function() 'test/functional/fixtures/clint_test.c:138: Use xfree(...) instead of free(...). [runtime/memory_fn] [2]', 'test/functional/fixtures/clint_test.c:141: Use os_getenv(...) instead of getenv(...). [runtime/os_fn] [2]', 'test/functional/fixtures/clint_test.c:142: Use os_setenv(...) instead of setenv(...). [runtime/os_fn] [2]', - 'Total errors found: 28', + 'test/functional/fixtures/clint_test.c:143: Use getdigits()/getdigits_int() (or vim_str2nr() for non-decimal bases) instead of strtol, which overflows silently. See src/nvim/charset.c. [runtime/deprecated] [4]', + 'Total errors found: 29', } t.eq(expected, output_lines) end)