diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-08-15 19:16:19 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-15 19:16:19 +0800 |
commit | 842a47d6a4103a75e33c2c0023dbae5ad2c0f534 (patch) | |
tree | 9d1b92e4a36477bae40673233998ac8390a24a9b /src/nvim/strings.c | |
parent | 7aad4643f9a6c2c3cc3033ae6dafef71036d3585 (diff) | |
download | rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.tar.gz rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.tar.bz2 rneovim-842a47d6a4103a75e33c2c0023dbae5ad2c0f534.zip |
vim-patch:9.0.1704: Cannot use positional arguments for printf() (#24719)
Problem: Cannot use positional arguments for printf()
Solution: Support positional arguments in string formatting
closes: vim/vim#12140
https://github.com/vim/vim/commit/0c6181fec4c362eb9682d5af583341eb20cb1af5
Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
Diffstat (limited to 'src/nvim/strings.c')
-rw-r--r-- | src/nvim/strings.c | 789 |
1 files changed, 749 insertions, 40 deletions
diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 6fe2fd8ff3..2e603b8c9f 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -32,6 +32,32 @@ #include "nvim/types.h" #include "nvim/vim.h" +static char e_cannot_mix_positional_and_non_positional_str[] + = N_("E1400: Cannot mix positional and non-positional arguments: %s"); +static char e_fmt_arg_nr_unused_str[] + = N_("E1401: format argument %d unused in $-style format: %s"); +static char e_positional_num_field_spec_reused_str_str[] + = N_("E1402: Positional argument %d used as field width reused as different type: %s/%s"); +static char e_positional_nr_out_of_bounds_str[] + = N_("E1403: Positional argument %d out of bounds: %s"); +static char e_positional_arg_num_type_inconsistent_str_str[] + = N_("E1404: Positional argument %d type used inconsistently: %s/%s"); +static char e_invalid_format_specifier_str[] + = N_("E1405: Invalid format specifier: %s"); + +static char typename_unknown[] = N_("unknown"); +static char typename_int[] = N_("int"); +static char typename_longint[] = N_("long int"); +static char typename_longlongint[] = N_("long long int"); +static char typename_unsignedint[] = N_("unsigned int"); +static char typename_unsignedlongint[] = N_("unsigned long int"); +static char typename_unsignedlonglongint[] = N_("unsigned long long int"); +static char typename_pointer[] = N_("pointer"); +static char typename_percent[] = N_("percent"); +static char typename_char[] = N_("char"); +static char typename_string[] = N_("string"); +static char typename_float[] = N_("float"); + /// Copy up to `len` bytes of `string` into newly allocated memory and /// terminate with a NUL. The allocated memory always has size `len + 1`, even /// when `string` is shorter. @@ -717,6 +743,571 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) return vim_vsnprintf_typval(str, str_m, fmt, ap, NULL); } +enum { + TYPE_UNKNOWN = -1, + TYPE_INT, + TYPE_LONGINT, + TYPE_LONGLONGINT, + TYPE_UNSIGNEDINT, + TYPE_UNSIGNEDLONGINT, + TYPE_UNSIGNEDLONGLONGINT, + TYPE_POINTER, + TYPE_PERCENT, + TYPE_CHAR, + TYPE_STRING, + TYPE_FLOAT, +}; + +/// Types that can be used in a format string +static int format_typeof(const char *type, bool usetvs) + FUNC_ATTR_NONNULL_ALL +{ + // allowed values: \0, h, l, L + char length_modifier = '\0'; + + // current conversion specifier character + char fmt_spec = '\0'; + + // parse 'h', 'l' and 'll' length modifiers + if (*type == 'h' || *type == 'l') { + length_modifier = *type; + type++; + if (length_modifier == 'l' && *type == 'l') { + // double l = long long + length_modifier = 'L'; + type++; + } + } + fmt_spec = *type; + + // common synonyms: + switch (fmt_spec) { + case 'i': + fmt_spec = 'd'; break; + case '*': + fmt_spec = 'd'; length_modifier = 'h'; break; + case 'D': + fmt_spec = 'd'; length_modifier = 'l'; break; + case 'U': + fmt_spec = 'u'; length_modifier = 'l'; break; + case 'O': + fmt_spec = 'o'; length_modifier = 'l'; break; + default: + break; + } + + if (usetvs) { + switch (fmt_spec) { + case 'd': + case 'u': + case 'o': + case 'x': + case 'X': + if (length_modifier == '\0') { + length_modifier = 'L'; + } + } + } + + // get parameter value, do initial processing + switch (fmt_spec) { + // '%' and 'c' behave similar to 's' regarding flags and field + // widths + case '%': + return TYPE_PERCENT; + + case 'c': + return TYPE_CHAR; + + case 's': + case 'S': + return TYPE_STRING; + + case 'd': + case 'u': + case 'b': + case 'B': + case 'o': + case 'x': + case 'X': + case 'p': + // NOTE: the u, b, o, x, X and p conversion specifiers + // imply the value is unsigned; d implies a signed + // value + + // 0 if numeric argument is zero (or if pointer is + // NULL for 'p'), +1 if greater than zero (or nonzero + // for unsigned arguments), -1 if negative (unsigned + // argument is never negative) + + if (fmt_spec == 'p') { + return TYPE_POINTER; + } else if (fmt_spec == 'b' || fmt_spec == 'B') { + return TYPE_UNSIGNEDINT; + } else if (fmt_spec == 'd') { + // signed + switch (length_modifier) { + case '\0': + case 'h': + // char and short arguments are passed as int. + return TYPE_INT; + case 'l': + return TYPE_LONGINT; + case 'L': + return TYPE_LONGLONGINT; + } + } else { + // unsigned + switch (length_modifier) { + case '\0': + case 'h': + return TYPE_UNSIGNEDINT; + case 'l': + return TYPE_UNSIGNEDLONGINT; + case 'L': + return TYPE_UNSIGNEDLONGLONGINT; + } + } + break; + + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + return TYPE_FLOAT; + } + + return TYPE_UNKNOWN; +} + +static char *format_typename(const char *type) + FUNC_ATTR_NONNULL_ALL +{ + switch (format_typeof(type, false)) { + case TYPE_INT: + return _(typename_int); + case TYPE_LONGINT: + return _(typename_longint); + case TYPE_LONGLONGINT: + return _(typename_longlongint); + case TYPE_UNSIGNEDINT: + return _(typename_unsignedint); + case TYPE_UNSIGNEDLONGINT: + return _(typename_unsignedlongint); + case TYPE_UNSIGNEDLONGLONGINT: + return _(typename_unsignedlonglongint); + case TYPE_POINTER: + return _(typename_pointer); + case TYPE_PERCENT: + return _(typename_percent); + case TYPE_CHAR: + return _(typename_char); + case TYPE_STRING: + return _(typename_string); + case TYPE_FLOAT: + return _(typename_float); + } + + return _(typename_unknown); +} + +static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const char *type) + FUNC_ATTR_NONNULL_ALL +{ + if (*ap_types == NULL || *num_posarg < arg) { + const char **new_types = *ap_types == NULL + ? xcalloc(sizeof(const char *), (size_t)arg) + : xrealloc(*ap_types, (size_t)arg * sizeof(const char *)); + + for (int idx = *num_posarg; idx < arg; idx++) { + new_types[idx] = NULL; + } + + *ap_types = new_types; + *num_posarg = arg; + } + + if ((*ap_types)[arg - 1] != NULL) { + if ((*ap_types)[arg - 1][0] == '*' || type[0] == '*') { + const char *pt = type; + if (pt[0] == '*') { + pt = (*ap_types)[arg - 1]; + } + + if (pt[0] != '*') { + switch (pt[0]) { + case 'd': + case 'i': + break; + default: + semsg(_(e_positional_num_field_spec_reused_str_str), arg, + format_typename((*ap_types)[arg - 1]), format_typename(type)); + return FAIL; + } + } + } else { + if (format_typeof(type, false) != format_typeof((*ap_types)[arg - 1], false)) { + semsg(_(e_positional_arg_num_type_inconsistent_str_str), arg, + format_typename(type), format_typename((*ap_types)[arg - 1])); + return FAIL; + } + } + } + + (*ap_types)[arg - 1] = type; + + return OK; +} + +static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *fmt, typval_T *tvs) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + const char *p = fmt; + const char *arg = NULL; + + int any_pos = 0; + int any_arg = 0; + +#define CHECK_POS_ARG \ + do { \ + if (any_pos && any_arg) { \ + semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt); \ + goto error; \ + } \ + } while (0); + + if (p == NULL) { + return OK; + } + + while (*p != NUL) { + if (*p != '%') { + char *q = strchr(p + 1, '%'); + size_t n = (q == NULL) ? strlen(p) : (size_t)(q - p); + + p += n; + } else { + // allowed values: \0, h, l, L + char length_modifier = '\0'; + + // variable for positional arg + int pos_arg = -1; + + p++; // skip '%' + + // First check to see if we find a positional + // argument specifier + const char *ptype = p; + + while (ascii_isdigit(*ptype)) { + ptype++; + } + + if (*ptype == '$') { + if (*p == '0') { + // 0 flag at the wrong place + semsg(_(e_invalid_format_specifier_str), fmt); + goto error; + } + + // Positional argument + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + pos_arg = (int)uj; + + any_pos = 1; + CHECK_POS_ARG; + + p++; + } + + // parse flags + while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' + || *p == '#' || *p == '\'') { + switch (*p) { + case '0': + break; + case '-': + break; + case '+': + break; + case ' ': // If both the ' ' and '+' flags appear, the ' ' + // flag should be ignored + break; + case '#': + break; + case '\'': + break; + } + p++; + } + // If the '0' and '-' flags both appear, the '0' flag should be + // ignored. + + // parse field width + if (*(arg = p) == '*') { + p++; + + if (ascii_isdigit((int)(*p))) { + // Positional argument field width + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + + if (*p != '$') { + semsg(_(e_invalid_format_specifier_str), fmt); + goto error; + } else { + p++; + any_pos = 1; + CHECK_POS_ARG; + + if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) { + goto error; + } + } + } else { + any_arg = 1; + CHECK_POS_ARG; + } + } else if (ascii_isdigit((int)(*(arg = p)))) { + // size_t could be wider than unsigned int; make sure we treat + // argument like common implementations do + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + + if (*p == '$') { + semsg(_(e_invalid_format_specifier_str), fmt); + goto error; + } + } + + // parse precision + if (*p == '.') { + p++; + + if (*(arg = p) == '*') { + p++; + + if (ascii_isdigit((int)(*p))) { + // Parse precision + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + + if (*p == '$') { + any_pos = 1; + CHECK_POS_ARG; + + p++; + + if (adjust_types(ap_types, (int)uj, num_posarg, arg) == FAIL) { + goto error; + } + } else { + semsg(_(e_invalid_format_specifier_str), fmt); + goto error; + } + } else { + any_arg = 1; + CHECK_POS_ARG; + } + } else if (ascii_isdigit((int)(*(arg = p)))) { + // size_t could be wider than unsigned int; make sure we + // treat argument like common implementations do + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + + if (*p == '$') { + semsg(_(e_invalid_format_specifier_str), fmt); + goto error; + } + } + } + + if (pos_arg != -1) { + any_pos = 1; + CHECK_POS_ARG; + + ptype = p; + } + + // parse 'h', 'l' and 'll' length modifiers + if (*p == 'h' || *p == 'l') { + length_modifier = *p; + p++; + if (length_modifier == 'l' && *p == 'l') { + // double l = long long + length_modifier = 'L'; + p++; + } + } + + switch (*p) { + // Check for known format specifiers. % is special! + case 'i': + case '*': + case 'd': + case 'u': + case 'o': + case 'D': + case 'U': + case 'O': + case 'x': + case 'X': + case 'b': + case 'B': + case 'c': + case 's': + case 'S': + case 'p': + case 'f': + case 'F': + case 'e': + case 'E': + case 'g': + case 'G': + if (pos_arg != -1) { + if (adjust_types(ap_types, pos_arg, num_posarg, ptype) == FAIL) { + goto error; + } + } else { + any_arg = 1; + CHECK_POS_ARG; + } + break; + + default: + if (pos_arg != -1) { + semsg(_(e_cannot_mix_positional_and_non_positional_str), fmt); + goto error; + } + } + + if (*p != NUL) { + p++; // step over the just processed conversion specifier + } + } + } + + for (int arg_idx = 0; arg_idx < *num_posarg; arg_idx++) { + if ((*ap_types)[arg_idx] == NULL) { + semsg(_(e_fmt_arg_nr_unused_str), arg_idx + 1, fmt); + goto error; + } + + if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN) { + semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt); + goto error; + } + } + + return OK; + +error: + xfree(*ap_types); + *ap_types = NULL; + *num_posarg = 0; + return FAIL; +} + +static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, int *arg_idx, + int *arg_cur) + FUNC_ATTR_NONNULL_ARG(3, 4, 5) +{ + int arg_min = 0; + + if (*arg_cur + 1 == *arg_idx) { + (*arg_cur)++; + (*arg_idx)++; + return; + } + + if (*arg_cur >= *arg_idx) { + // Reset ap to ap_start and skip arg_idx - 1 types + va_end(*ap); + va_copy(*ap, ap_start); + } else { + // Skip over any we should skip + arg_min = *arg_cur; + } + + for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; (*arg_cur)++) { + assert(ap_types != NULL); + const char *p = ap_types[*arg_cur]; + + int fmt_type = format_typeof(p, true); + + // get parameter value, do initial processing + switch (fmt_type) { + case TYPE_PERCENT: + case TYPE_UNKNOWN: + break; + + case TYPE_CHAR: + va_arg(*ap, int); + break; + + case TYPE_STRING: + va_arg(*ap, const char *); + break; + + case TYPE_POINTER: + va_arg(*ap, void *); + break; + + case TYPE_INT: + va_arg(*ap, int); + break; + + case TYPE_LONGINT: + va_arg(*ap, long); + break; + + case TYPE_LONGLONGINT: + va_arg(*ap, long long); // NOLINT(runtime/int) + break; + + case TYPE_UNSIGNEDINT: + va_arg(*ap, unsigned); + break; + + case TYPE_UNSIGNEDLONGINT: + va_arg(*ap, unsigned long); + break; + + case TYPE_UNSIGNEDLONGLONGINT: + va_arg(*ap, unsigned long long); // NOLINT(runtime/int) + break; + + case TYPE_FLOAT: + va_arg(*ap, double); + break; + } + } + + // Because we know that after we return from this call, + // a va_arg() call is made, we can pre-emptively + // increment the current argument index. + (*arg_cur)++; + (*arg_idx)++; +} + /// Write formatted value to the string /// /// @param[out] str String to write to. @@ -728,12 +1319,23 @@ int vim_vsnprintf(char *str, size_t str_m, const char *fmt, va_list ap) /// /// @return Number of bytes excluding NUL byte that would be written to the /// string if str_m was greater or equal to the return value. -int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, typval_T *const tvs) +int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_start, + typval_T *const tvs) { size_t str_l = 0; bool str_avail = str_l < str_m; const char *p = fmt; + int arg_cur = 0; + int num_posarg = 0; int arg_idx = 1; + va_list ap; + const char **ap_types = NULL; + + if (parse_fmt_types(&ap_types, &num_posarg, fmt, tvs) == FAIL) { + return 0; + } + + va_copy(ap, ap_start); if (!p) { p = ""; @@ -789,8 +1391,31 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // buffer for 's' and 'S' specs char *tofree = NULL; + // variable for positional arg + int pos_arg = -1; + p++; // skip '%' + // First check to see if we find a positional + // argument specifier + const char *ptype = p; + + while (ascii_isdigit(*ptype)) { + ptype++; + } + + if (*ptype == '$') { + // Positional argument + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + pos_arg = (int)uj; + + p++; + } + // parse flags while (true) { switch (*p) { @@ -817,7 +1442,24 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // parse field width if (*p == '*') { p++; - const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); + + if (ascii_isdigit((int)(*p))) { + // Positional argument field width + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + arg_idx = (int)uj; + + p++; + } + + const int j = (tvs + ? (int)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int))); + if (j >= 0) { min_field_width = (size_t)j; } else { @@ -839,16 +1481,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t if (*p == '.') { p++; precision_specified = 1; - if (*p == '*') { - const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); - p++; - if (j >= 0) { - precision = (size_t)j; - } else { - precision_specified = 0; - precision = 0; - } - } else if (ascii_isdigit((int)(*p))) { + + if (ascii_isdigit((int)(*p))) { // size_t could be wider than unsigned int; make sure we // treat argument like common implementations do unsigned uj = (unsigned)(*p++ - '0'); @@ -857,6 +1491,32 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t uj = 10 * uj + (unsigned)(*p++ - '0'); } precision = uj; + } else if (*p == '*') { + p++; + + if (ascii_isdigit((int)(*p))) { + // positional argument + unsigned uj = (unsigned)(*p++ - '0'); + + while (ascii_isdigit((int)(*p))) { + uj = 10 * uj + (unsigned)(*p++ - '0'); + } + arg_idx = (int)uj; + + p++; + } + + const int j = (tvs + ? (int)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int))); + + if (j >= 0) { + precision = (size_t)j; + } else { + precision_specified = 0; + precision = 0; + } } } @@ -864,8 +1524,9 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t if (*p == 'h' || *p == 'l' || *p == 'z') { length_modifier = *p; p++; - if (length_modifier == 'l' && *p == 'l') { // ll, encoded as 2 - length_modifier = '2'; + if (length_modifier == 'l' && *p == 'l') { + // double l = long long + length_modifier = 'L'; p++; } } @@ -895,10 +1556,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t case 'x': case 'X': if (tvs && length_modifier == '\0') { - length_modifier = '2'; + length_modifier = 'L'; } } + if (pos_arg != -1) { + arg_idx = pos_arg; + } + // get parameter value, do initial processing switch (fmt_spec) { // '%' and 'c' behave similar to 's' regarding flags and field widths @@ -913,7 +1578,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t break; case 'c': { - const int j = tvs ? (int)tv_nr(tvs, &arg_idx) : va_arg(ap, int); + const int j = (tvs + ? (int)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int))); + // standard demands unsigned char uchar_arg = (unsigned char)j; str_arg = (char *)&uchar_arg; @@ -922,8 +1591,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t case 's': case 'S': - str_arg = tvs ? tv_str(tvs, &arg_idx, &tofree) - : va_arg(ap, const char *); + str_arg = (tvs + ? tv_str(tvs, &arg_idx, &tofree) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, const char *))); + if (!str_arg) { str_arg = "[NULL]"; str_arg_l = 6; @@ -990,7 +1662,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t const void *ptr_arg = NULL; if (fmt_spec == 'p') { - ptr_arg = tvs ? tv_ptr(tvs, &arg_idx) : va_arg(ap, void *); + ptr_arg = (tvs + ? tv_ptr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, void *))); + if (ptr_arg) { arg_sign = 1; } @@ -998,23 +1674,36 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // signed switch (length_modifier) { case '\0': - arg = (int)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int)); + arg = (tvs + ? (int)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int))); break; case 'h': // char and short arguments are passed as int16_t - arg = (int16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, int)); + arg = (int16_t) + (tvs + ? (int)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int))); break; case 'l': - arg = (tvs ? (long)tv_nr(tvs, &arg_idx) : va_arg(ap, long)); + arg = (tvs + ? (long)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, long))); break; - case '2': - arg = ( - tvs - ? (long long)tv_nr(tvs, &arg_idx) // NOLINT (runtime/int) - : va_arg(ap, long long)); // NOLINT (runtime/int) + case 'L': + arg = (tvs + ? (long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, long long))); // NOLINT(runtime/int) break; case 'z': - arg = (tvs ? (ptrdiff_t)tv_nr(tvs, &arg_idx) : va_arg(ap, ptrdiff_t)); + arg = (tvs + ? (ptrdiff_t)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, ptrdiff_t))); break; } if (arg > 0) { @@ -1026,23 +1715,35 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t // unsigned switch (length_modifier) { case '\0': - uarg = (unsigned)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned)); + uarg = (tvs + ? (unsigned)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, unsigned))); break; case 'h': - uarg = (uint16_t)(tvs ? tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned)); + uarg = (uint16_t) + (tvs + ? (unsigned)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, unsigned))); break; case 'l': - uarg = (tvs ? (unsigned long)tv_nr(tvs, &arg_idx) : va_arg(ap, unsigned long)); + uarg = (tvs + ? (unsigned long)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, unsigned long))); break; - case '2': - uarg = (uintmax_t)(unsigned long long)( // NOLINT (runtime/int) - tvs - ? ((unsigned long long) // NOLINT (runtime/int) - tv_nr(tvs, &arg_idx)) - : va_arg(ap, unsigned long long)); // NOLINT (runtime/int) + case 'L': + uarg = (tvs + ? (unsigned long long)tv_nr(tvs, &arg_idx) // NOLINT(runtime/int) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, unsigned long long))); // NOLINT(runtime/int) break; case 'z': - uarg = (tvs ? (size_t)tv_nr(tvs, &arg_idx) : va_arg(ap, size_t)); + uarg = (tvs + ? (size_t)tv_nr(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, size_t))); break; } arg_sign = (uarg != 0); @@ -1177,7 +1878,11 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t char format[40]; int remove_trailing_zeroes = false; - double f = tvs ? tv_float(tvs, &arg_idx) : va_arg(ap, double); + double f = (tvs + ? tv_float(tvs, &arg_idx) + : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, double))); + double abs_f = f < 0 ? -f : f; if (fmt_spec == 'g' || fmt_spec == 'G') { @@ -1395,10 +2100,14 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap, t str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0'; } - if (tvs && tvs[arg_idx - 1].v_type != VAR_UNKNOWN) { + if (tvs != NULL + && tvs[num_posarg != 0 ? num_posarg : arg_idx - 1].v_type != VAR_UNKNOWN) { emsg(_("E767: Too many arguments to printf()")); } + xfree(ap_types); + va_end(ap); + // return the number of characters formatted (excluding trailing nul // character); that is, the number of characters that would have been // written to the buffer if it were large enough. |