mirror of
https://github.com/neovim/neovim.git
synced 2025-10-06 01:46:29 +00:00
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
0c6181fec4
Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
This commit is contained in:
@@ -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.
|
||||
|
Reference in New Issue
Block a user