diff options
author | Christ van Willegen <cvwillegen@gmail.com> | 2023-08-13 18:03:14 +0200 |
---|---|---|
committer | Christian Brabandt <cb@256bit.org> | 2023-08-13 18:06:00 +0200 |
commit | 0c6181fec4c362eb9682d5af583341eb20cb1af5 (patch) | |
tree | e28ab76bd05d019ae45dee9286ab32f827513cc4 /src | |
parent | 1688938dd5ac78ab67e54299b9d5b93499dba762 (diff) |
patch 9.0.1704: Cannot use positional arguments for printf()v9.0.1704
Problem: Cannot use positional arguments for printf()
Solution: Support positional arguments in string formatting
closes: #12140
Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
Diffstat (limited to 'src')
-rw-r--r-- | src/errors.h | 14 | ||||
-rw-r--r-- | src/globals.h | 13 | ||||
-rw-r--r-- | src/message_test.c | 201 | ||||
-rw-r--r-- | src/po/check.vim | 9 | ||||
-rw-r--r-- | src/strings.c | 784 | ||||
-rw-r--r-- | src/testdir/Make_all.mak | 1 | ||||
-rw-r--r-- | src/testdir/test_expr.vim | 2 | ||||
-rw-r--r-- | src/testdir/test_format.vim | 361 | ||||
-rw-r--r-- | src/version.c | 2 |
9 files changed, 1359 insertions, 28 deletions
diff --git a/src/errors.h b/src/errors.h index 9b40cb2549..6ef47000a9 100644 --- a/src/errors.h +++ b/src/errors.h @@ -3477,3 +3477,17 @@ EXTERN char e_incomplete_type[] #endif EXTERN char e_warning_pointer_block_corrupted[] INIT(= N_("E1364: Warning: Pointer block corrupted")); +EXTERN char e_cannot_mix_positional_and_non_positional_str[] + INIT(= N_("E1400: Cannot mix positional and non-positional arguments: %s")); +EXTERN char e_fmt_arg_nr_unused_str[] + INIT(= N_("E1401: format argument %d unused in $-style format: %s")); +EXTERN char e_positional_num_field_spec_reused_str_str[] + INIT(= N_("E1402: Positional argument %d used as field width reused as different type: %s/%s")); +EXTERN char e_positional_nr_out_of_bounds_str[] + INIT(= N_("E1403: Positional argument %d out of bounds: %s")); +EXTERN char e_positional_arg_num_type_inconsistent_str_str[] + INIT(= N_("E1404: Positional argument %d type used inconsistently: %s/%s")); +EXTERN char e_invalid_format_specifier_str[] + INIT(= N_("E1405: Invalid format specifier: %s")); + +// E1365 - E1399 unused diff --git a/src/globals.h b/src/globals.h index 69b4343d61..6a68c3acbb 100644 --- a/src/globals.h +++ b/src/globals.h @@ -1674,6 +1674,19 @@ EXTERN int cmdwin_result INIT(= 0); // result of cmdline window or 0 EXTERN char_u no_lines_msg[] INIT(= N_("--No lines in buffer--")); +EXTERN char typename_unknown[] INIT(= N_("unknown")); +EXTERN char typename_int[] INIT(= N_("int")); +EXTERN char typename_longint[] INIT(= N_("long int")); +EXTERN char typename_longlongint[] INIT(= N_("long long int")); +EXTERN char typename_unsignedint[] INIT(= N_("unsigned int")); +EXTERN char typename_unsignedlongint[] INIT(= N_("unsigned long int")); +EXTERN char typename_unsignedlonglongint[] INIT(= N_("unsigned long long int")); +EXTERN char typename_pointer[] INIT(= N_("pointer")); +EXTERN char typename_percent[] INIT(= N_("percent")); +EXTERN char typename_char[] INIT(= N_("char")); +EXTERN char typename_string[] INIT(= N_("string")); +EXTERN char typename_float[] INIT(= N_("float")); + /* * When ":global" is used to number of substitutions and changed lines is * accumulated until it's finished. diff --git a/src/message_test.c b/src/message_test.c index 21ec4db8d3..4ad8041a46 100644 --- a/src/message_test.c +++ b/src/message_test.c @@ -39,6 +39,9 @@ char *fmt_012p = "%012p"; char *fmt_5S = "%5S"; char *fmt_06b = "%06b"; +char *fmt_06pb = "%1$0.*2$b"; +char *fmt_212s = "%2$s %1$s %2$s"; +char *fmt_21s = "%2$s %1$s"; /* * Test trunc_string(). @@ -181,6 +184,11 @@ test_vim_snprintf(void) // buffer and its content should then never be used. char *buf = malloc(bsize); + n = vim_snprintf(buf, bsize, "%.8g", 10000000.1); + assert(n == 12); + assert(bsize == 0 || STRNCMP(buf, "1.00000001e7", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + n = vim_snprintf(buf, bsize, "%d", 1234567); assert(n == 7); assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); @@ -211,6 +219,12 @@ test_vim_snprintf(void) assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0); assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + n = vim_snprintf(buf, bsize, "%s %s", "one", "two"); + assert(n == 7); + assert(bsize == 0 || STRNCMP(buf, "one two", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + +#ifdef FEAT_FLOAT n = vim_snprintf(buf, bsize, "%f", 1.234); assert(n == 8); assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0); @@ -240,6 +254,7 @@ test_vim_snprintf(void) assert(n == 9); assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0); assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); +#endif n = vim_snprintf(buf, bsize, "%s", "漢語"); assert(n == 6); @@ -304,6 +319,190 @@ test_vim_snprintf(void) } } +/* + * Test vim_snprintf() with a focus on checking that positional + * arguments are correctly applied and skipped + */ + static void +test_vim_snprintf_positional(void) +{ + int n; + size_t bsize; + int bsize_int; + + // Loop on various buffer sizes to make sure that truncation of + // vim_snprintf() is correct. + for (bsize = 0; bsize < 25; ++bsize) + { + bsize_int = (int)bsize - 1; + + // buf is the heap rather than in the stack + // so valgrind can detect buffer overflows if any. + // Use malloc() rather than alloc() as test checks with 0-size + // buffer and its content should then never be used. + char *buf = malloc(bsize); + + n = vim_snprintf(buf, bsize, "%1$*2$ld", 1234567L, -9); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, "1234567 ", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$*2$.*3$ld", 1234567L, -9, 5); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, "1234567 ", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$*3$.*2$ld", 1234567L, 5, -9); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, "1234567 ", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%3$*1$.*2$ld", -9, 5, 1234567L); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, "1234567 ", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$ld", 1234567L); + assert(n == 7); + assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$*2$ld", 1234567L, 9); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, " 1234567", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$ld %1$d %3$lu", 12345, 9L, 7654321UL); + assert(n == 15); + assert(bsize == 0 || STRNCMP(buf, "9 12345 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$ld %3$lu", 1234567L, 9, 7654321UL); + assert(n == 17); + assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$lld %3$lu", 1234567LL, 9, 7654321UL); + assert(n == 17); + assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$ld %1$u %3$lu", 12345U, 9L, 7654321UL); + assert(n == 15); + assert(bsize == 0 || STRNCMP(buf, "9 12345 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$lu %3$lu", 1234567UL, 9, 7654321UL); + assert(n == 17); + assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$llu %3$lu", 1234567LLU, 9, 7654321UL); + assert(n == 17); + assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$llu %3$lu", 1234567LLU, 9, 7654321UL); + assert(n == 17); + assert(bsize == 0 || STRNCMP(buf, "9 1234567 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$d %1$x %3$lu", 0xdeadbeef, 9, 7654321UL); + assert(n == 18); + assert(bsize == 0 || STRNCMP(buf, "9 deadbeef 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$ld %1$c %3$lu", 'c', 9L, 7654321UL); + assert(n == 11); + assert(bsize == 0 || STRNCMP(buf, "9 c 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$ld %1$s %3$lu", "hi", 9L, 7654321UL); + assert(n == 12); + assert(bsize == 0 || STRNCMP(buf, "9 hi 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%2$ld %1$e %3$lu", 0.0, 9L, 7654321UL); + assert(n == 22); + assert(bsize == 0 || STRNCMP(buf, "9 0.000000e+00 7654321", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, fmt_212s, "one", "two", "three"); + assert(n == 11); + assert(bsize == 0 || STRNCMP(buf, "two one two", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%3$s %1$s %2$s", "one", "two", "three"); + assert(n == 13); + assert(bsize == 0 || STRNCMP(buf, "three one two", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$d", 1234567); + assert(n == 7); + assert(bsize == 0 || STRNCMP(buf, "1234567", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$x", 0xdeadbeef); + assert(n == 8); + assert(bsize == 0 || STRNCMP(buf, "deadbeef", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, fmt_06pb, (uvarnumber_T)12, 6); + assert(n == 6); + assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$s %2$s", "one", "two"); + assert(n == 7); + assert(bsize == 0 || STRNCMP(buf, "one two", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, fmt_06b, (uvarnumber_T)12); + assert(n == 6); + assert(bsize == 0 || STRNCMP(buf, "001100", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, fmt_21s, "one", "two", "three"); + assert(n == 7); + assert(bsize == 0 || STRNCMP(buf, "two one", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + +#ifdef FEAT_FLOAT + n = vim_snprintf(buf, bsize, "%1$f", 1.234); + assert(n == 8); + assert(bsize == 0 || STRNCMP(buf, "1.234000", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$e", 1.234); + assert(n == 12); + assert(bsize == 0 || STRNCMP(buf, "1.234000e+00", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$f", 0.0/0.0); + assert(n == 3); + assert(bsize == 0 || STRNCMP(buf, "nan", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$f", 1.0/0.0); + assert(n == 3); + assert(bsize == 0 || STRNCMP(buf, "inf", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$f", -1.0/0.0); + assert(n == 4); + assert(bsize == 0 || STRNCMP(buf, "-inf", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); + + n = vim_snprintf(buf, bsize, "%1$f", -0.0); + assert(n == 9); + assert(bsize == 0 || STRNCMP(buf, "-0.000000", bsize_int) == 0); + assert(bsize == 0 || buf[MIN(n, bsize_int)] == '\0'); +#endif + + free(buf); + } +} + int main(int argc, char **argv) { @@ -317,11 +516,13 @@ main(int argc, char **argv) test_trunc_string(); test_trunc_string_mbyte(); test_vim_snprintf(); + test_vim_snprintf_positional(); set_option_value_give_err((char_u *)"encoding", 0, (char_u *)"latin1", 0); init_chartab(); test_trunc_string(); test_vim_snprintf(); + test_vim_snprintf_positional(); return 0; } diff --git a/src/po/check.vim b/src/po/check.vim index b2a5fd582e..917c648494 100644 --- a/src/po/check.vim +++ b/src/po/check.vim @@ -30,8 +30,15 @@ func! GetMline() " remove '%' used for plural forms. let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '') + " remove duplicate positional format arguments + let idline2 = "" + while idline2 != idline + let idline2 = idline + let idline = substitute(idline, '%\([1-9][0-9]*\)\$\([-+ #''.*]*[0-9]*l\=[dsuxXpoc%]\)\(.*\)%\1$\([-+ #''.*]*\)\(l\=[dsuxXpoc%]\)', '%\1$\2\3\4', 'g') + endwhile + " remove everything but % items. - return substitute(idline, '[^%]*\(%[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g') + return substitute(idline, '[^%]*\(%([1-9][0-9]*\$)\=[-+ #''.0-9*]*l\=[dsuxXpoc%]\)\=', '\1', 'g') endfunc " This only works when 'wrapscan' is not set. diff --git a/src/strings.c b/src/strings.c index e3be3354d3..ead59164e4 100644 --- a/src/strings.c +++ b/src/strings.c @@ -2242,17 +2242,665 @@ vim_vsnprintf( 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 + */ + int +format_typeof( + const char *type, + int usetvs UNUSED) +{ + // 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 = __int64 / varnumber_T + 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 defined(FEAT_EVAL) + if (usetvs) + { + switch (fmt_spec) + { + case 'd': case 'u': case 'o': case 'x': case 'X': + if (length_modifier == '\0') + length_modifier = 'L'; + } + } +# endif + + // 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; +} + + char * +format_typename( + const char *type) +{ + 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); +} + + int +adjust_types( + const char ***ap_types, + int arg, + int *num_posarg, + const char *type) +{ + if (*ap_types == NULL || *num_posarg < arg) + { + int idx; + const char **new_types; + + if (*ap_types == NULL) + new_types = ALLOC_CLEAR_MULT(const char *, arg); + else + new_types = vim_realloc(*ap_types, arg * sizeof(const char *)); + + if (new_types == NULL) + return FAIL; + + for (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; +} + + int +parse_fmt_types( + const char ***ap_types, + int *num_posarg, + const char *fmt, + typval_T *tvs UNUSED + ) +{ + const char *p = fmt; + const char *arg = NULL; + + int any_pos = 0; + int any_arg = 0; + int arg_idx; + +#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; + const char *ptype = NULL; + + p++; // skip '%' + + // First check to see if we find a positional + // argument specifier + ptype = p; + + while (VIM_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 int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + pos_arg = 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 (VIM_ISDIGIT((int)(*p))) + { + // Positional argument field width + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*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, uj, num_posarg, arg) == FAIL) + goto error; + } + } + else + { + any_arg = 1; + CHECK_POS_ARG; + } + } + else if (VIM_ISDIGIT((int)(*(arg = p)))) + { + // size_t could be wider than unsigned int; make sure we treat + // argument like common implementations do + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + + if (*p == '$') + { + semsg(_( e_invalid_format_specifier_str), fmt); + goto error; + } + } + + // parse precision + if (*p == '.') + { + p++; + + if (*(arg = p) == '*') + { + p++; + + if (VIM_ISDIGIT((int)(*p))) + { + // Parse precision + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + + if (*p == '$') + { + any_pos = 1; + CHECK_POS_ARG; + + ++p; + + if (adjust_types(ap_types, 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 (VIM_ISDIGIT((int)(*(arg = p)))) + { + // size_t could be wider than unsigned int; make sure we + // treat argument like common implementations do + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*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 = __int64 / varnumber_T + 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 (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 defined(FEAT_EVAL) + if (tvs != NULL && tvs[arg_idx].v_type == VAR_UNKNOWN) + { + semsg(_(e_positional_nr_out_of_bounds_str), arg_idx + 1, fmt); + goto error; + } +# endif + } + + return OK; + +error: + vim_free(*ap_types); + *ap_types = NULL; + *num_posarg = 0; + return FAIL; +} + + void +skip_to_arg( + const char **ap_types, + va_list ap_start, + va_list *ap, + int *arg_idx, + int *arg_cur) +{ + 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) + { + 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, 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 int); + break; + + case TYPE_LONGLONGINT: + va_arg(*ap, varnumber_T); + break; + + case TYPE_UNSIGNEDINT: + va_arg(*ap, unsigned int); + break; + + case TYPE_UNSIGNEDLONGINT: + va_arg(*ap, unsigned long int); + break; + + case TYPE_UNSIGNEDLONGLONGINT: + va_arg(*ap, uvarnumber_T); + 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; + + return; +} + int vim_vsnprintf_typval( char *str, size_t str_m, const char *fmt, - va_list ap, + va_list ap_start, typval_T *tvs) { size_t str_l = 0; 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 == NULL) p = ""; @@ -2316,9 +2964,32 @@ vim_vsnprintf_typval( // buffer for 's' and 'S' specs char_u *tofree = NULL; + // variables for positional arg + int pos_arg = -1; + const char *ptype; + p++; // skip '%' + // First check to see if we find a positional + // argument specifier + ptype = p; + + while (VIM_ISDIGIT(*ptype)) + ++ptype; + + if (*ptype == '$') + { + // Positional argument + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + pos_arg = uj; + + ++p; + } + // parse flags while (*p == '0' || *p == '-' || *p == '+' || *p == ' ' || *p == '#' || *p == '\'') @@ -2346,11 +3017,26 @@ vim_vsnprintf_typval( int j; p++; + + if (VIM_ISDIGIT((int)(*p))) + { + // Positional argument field width + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + arg_idx = uj; + + ++p; + } + j = # if defined(FEAT_EVAL) tvs != NULL ? tv_nr(tvs, &arg_idx) : # endif - va_arg(ap, int); + (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int)); + if (j >= 0) min_field_width = j; else @@ -2375,16 +3061,42 @@ vim_vsnprintf_typval( { p++; precision_specified = 1; - if (*p == '*') + + if (VIM_ISDIGIT((int)(*p))) + { + // size_t could be wider than unsigned int; make sure we + // treat argument like common implementations do + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + precision = uj; + } + else if (*p == '*') { int j; + p++; + + if (VIM_ISDIGIT((int)(*p))) + { + // positional argument + unsigned int uj = *p++ - '0'; + + while (VIM_ISDIGIT((int)(*p))) + uj = 10 * uj + (unsigned int)(*p++ - '0'); + arg_idx = uj; + + ++p; + } + j = # if defined(FEAT_EVAL) tvs != NULL ? tv_nr(tvs, &arg_idx) : # endif - va_arg(ap, int); - p++; + (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, int)); + if (j >= 0) precision = j; else @@ -2393,16 +3105,6 @@ vim_vsnprintf_typval( precision = 0; } } - else if (VIM_ISDIGIT((int)(*p))) - { - // size_t could be wider than unsigned int; make sure we - // treat argument like common implementations do - unsigned int uj = *p++ - '0'; - - while (VIM_ISDIGIT((int)(*p))) - uj = 10 * uj + (unsigned int)(*p++ - '0'); - precision = uj; - } } // parse 'h', 'l' and 'll' length modifiers @@ -2438,6 +3140,9 @@ vim_vsnprintf_typval( } # endif + if (pos_arg != -1) + arg_idx = pos_arg; + // get parameter value, do initial processing switch (fmt_spec) { @@ -2462,7 +3167,9 @@ vim_vsnprintf_typval( # if defined(FEAT_EVAL) tvs != NULL ? tv_nr(tvs, &arg_idx) : # endif - va_arg(ap, int); + (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; @@ -2475,7 +3182,9 @@ vim_vsnprintf_typval( # if defined(FEAT_EVAL) tvs != NULL ? tv_str(tvs, &arg_idx, &tofree) : # endif - va_arg(ap, char *); + (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, char *)); + if (str_arg == NULL) { str_arg = "[NULL]"; @@ -2570,7 +3279,9 @@ vim_vsnprintf_typval( tvs != NULL ? (void *)tv_str(tvs, &arg_idx, NULL) : # endif - va_arg(ap, void *); + (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur), + va_arg(ap, void *)); + if (ptr_arg != NULL) |