aboutsummaryrefslogtreecommitdiff
path: root/src/nvim/strings.c
diff options
context:
space:
mode:
authorzeertzjq <zeertzjq@outlook.com>2023-08-15 19:16:19 +0800
committerGitHub <noreply@github.com>2023-08-15 19:16:19 +0800
commit842a47d6a4103a75e33c2c0023dbae5ad2c0f534 (patch)
tree9d1b92e4a36477bae40673233998ac8390a24a9b /src/nvim/strings.c
parent7aad4643f9a6c2c3cc3033ae6dafef71036d3585 (diff)
downloadrneovim-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.c789
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.