0
0
mirror of https://github.com/vim/vim.git synced 2025-09-25 03:54:15 -04:00

patch 9.0.1704: Cannot use positional arguments for printf()

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>
This commit is contained in:
Christ van Willegen
2023-08-13 18:03:14 +02:00
committed by Christian Brabandt
parent 1688938dd5
commit 0c6181fec4
10 changed files with 1473 additions and 34 deletions

View File

@@ -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,25 +3061,8 @@ vim_vsnprintf_typval(
{
p++;
precision_specified = 1;
if (*p == '*')
{
int j;
j =
# if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, int);
p++;
if (j >= 0)
precision = j;
else
{
precision_specified = 0;
precision = 0;
}
}
else if (VIM_ISDIGIT((int)(*p)))
if (VIM_ISDIGIT((int)(*p)))
{
// size_t could be wider than unsigned int; make sure we
// treat argument like common implementations do
@@ -2403,6 +3072,39 @@ vim_vsnprintf_typval(
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
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, int));
if (j >= 0)
precision = j;
else
{
precision_specified = 0;
precision = 0;
}
}
}
// 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)
arg_sign = 1;
}
@@ -2581,7 +3292,9 @@ vim_vsnprintf_typval(
tvs != NULL ?
(uvarnumber_T)tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, uvarnumber_T);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, uvarnumber_T));
if (bin_arg != 0)
arg_sign = 1;
}
@@ -2597,7 +3310,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));
if (int_arg > 0)
arg_sign = 1;
else if (int_arg < 0)
@@ -2608,7 +3323,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, long int);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, long int));
if (long_arg > 0)
arg_sign = 1;
else if (long_arg < 0)
@@ -2619,7 +3336,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL)
tvs != NULL ? tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, varnumber_T);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, varnumber_T));
if (llong_arg > 0)
arg_sign = 1;
else if (llong_arg < 0)
@@ -2639,7 +3358,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (unsigned)
tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, unsigned int);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned int));
if (uint_arg != 0)
arg_sign = 1;
break;
@@ -2649,7 +3370,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (unsigned long)
tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, unsigned long int);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, unsigned long int));
if (ulong_arg != 0)
arg_sign = 1;
break;
@@ -2659,7 +3382,9 @@ vim_vsnprintf_typval(
tvs != NULL ? (uvarnumber_T)
tv_nr(tvs, &arg_idx) :
# endif
va_arg(ap, uvarnumber_T);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, uvarnumber_T));
if (ullong_arg != 0)
arg_sign = 1;
break;
@@ -2859,7 +3584,9 @@ vim_vsnprintf_typval(
# if defined(FEAT_EVAL)
tvs != NULL ? tv_float(tvs, &arg_idx) :
# endif
va_arg(ap, double);
(skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
va_arg(ap, double));
abs_f = f < 0 ? -f : f;
if (fmt_spec == 'g' || fmt_spec == 'G')
@@ -3143,9 +3870,12 @@ vim_vsnprintf_typval(
str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
}
if (tvs != NULL && 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(_(e_too_many_arguments_to_printf));
vim_free(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.