#include #include "nvim/ascii.h" #include "nvim/macros.h" #include "nvim/charset.h" #include "nvim/eval/typval.h" #include "nvim/vim.h" int hex2nr(int c) { if ((c >= 'a') && (c <= 'f')) { return c - 'a' + 10; } if ((c >= 'A') && (c <= 'F')) { return c - 'A' + 10; } return c - '0'; } void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen) { const char *ptr = (const char *)start; #define STRING_ENDED(ptr) \ (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal const bool negative = (ptr[0] == '-'); uvarnumber_T un = 0; if (negative) { ptr++; } if (what & STR2NR_FORCE) { // When forcing main consideration is skipping the prefix. Octal and decimal // numbers have no prefixes to skip. pre is not set. switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { case STR2NR_HEX: { if (!STRING_ENDED(ptr + 2) && ptr[0] == '0' && (ptr[1] == 'x' || ptr[1] == 'X') && ascii_isxdigit(ptr[2])) { ptr += 2; } goto vim_str2nr_hex; } case STR2NR_BIN: { if (!STRING_ENDED(ptr + 2) && ptr[0] == '0' && (ptr[1] == 'b' || ptr[1] == 'B') && ascii_isbdigit(ptr[2])) { ptr += 2; } goto vim_str2nr_bin; } case STR2NR_OCT: { goto vim_str2nr_oct; } case 0: { goto vim_str2nr_dec; } default: { abort(); } } } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) && !STRING_ENDED(ptr + 1) && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; // Detect hexadecimal: 0x or 0X followed by hex digit if ((what & STR2NR_HEX) && !STRING_ENDED(ptr + 2) && (pre == 'X' || pre == 'x') && ascii_isxdigit(ptr[2])) { ptr += 2; goto vim_str2nr_hex; } // Detect binary: 0b or 0B followed by 0 or 1 if ((what & STR2NR_BIN) && !STRING_ENDED(ptr + 2) && (pre == 'B' || pre == 'b') && ascii_isbdigit(ptr[2])) { ptr += 2; goto vim_str2nr_bin; } // Detect octal number: zero followed by octal digits without '8' or '9' pre = 0; if (!(what & STR2NR_OCT)) { goto vim_str2nr_dec; } for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { if (ptr[i] > '7') { goto vim_str2nr_dec; } } pre = '0'; goto vim_str2nr_oct; } else { goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. abort(); // Should’ve used goto earlier. #define PARSE_NUMBER(base, cond, conv) \ do { \ while (!STRING_ENDED(ptr) && (cond)) { \ /* avoid ubsan error for overflow */ \ if (un < UVARNUMBER_MAX / base) { \ un = base * un + (uvarnumber_T)(conv); \ } else { \ un = UVARNUMBER_MAX; \ } \ ptr++; \ } \ } while (0) switch (pre) { case 'b': case 'B': { vim_str2nr_bin: PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); break; } case '0': { vim_str2nr_oct: PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); break; } case 0: { vim_str2nr_dec: PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); break; } case 'x': case 'X': { vim_str2nr_hex: PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); break; } } #undef PARSE_NUMBER if (prep != NULL) { *prep = pre; } if (len != NULL) { *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { if (negative) { // account for leading '-' for decimal numbers // avoid ubsan error for overflow if (un > VARNUMBER_MAX) { *nptr = VARNUMBER_MIN; } else { *nptr = -(varnumber_T)un; } } else { if (un > VARNUMBER_MAX) { un = VARNUMBER_MAX; } *nptr = (varnumber_T)un; } } if (unptr != NULL) { *unptr = un; } #undef STRING_ENDED }