vim-patch:9.0.1856: issues with formatting positional arguments (#25013)

Problem:  issues with formatting positional arguments
Solution: fix them, add tests and documentation

closes: vim/vim#12140
closes: vim/vim#12985

Tentatively fix message_test. Check NULL ptr.

aa90d4f031

Co-authored-by: Christ van Willegen <cvwillegen@gmail.com>
This commit is contained in:
zeertzjq 2023-09-04 08:49:50 +08:00 committed by GitHub
parent c50951a4d0
commit c431d820e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 87 additions and 62 deletions

View File

@ -5132,8 +5132,13 @@ printf({fmt}, {expr1} ...) *printf()*
than the field width, the field is expanded to contain
the conversion result.
The 'h' modifier indicates the argument is 16 bits.
The 'l' modifier indicates the argument is 32 bits.
The 'L' modifier indicates the argument is 64 bits.
The 'l' modifier indicates the argument is a long
integer. The size will be 32 bits or 64 bits
depending on your platform.
The "ll" modifier indicates the argument is 64 bits.
The b and B conversion specifiers never take a width
modifier and always assume their argument is a 64 bit
integer.
Generally, these modifiers are not useful. They are
ignored when type is known from the argument.

View File

@ -6115,8 +6115,13 @@ function vim.fn.prevnonblank(lnum) end
--- than the field width, the field is expanded to contain
--- the conversion result.
--- The 'h' modifier indicates the argument is 16 bits.
--- The 'l' modifier indicates the argument is 32 bits.
--- The 'L' modifier indicates the argument is 64 bits.
--- The 'l' modifier indicates the argument is a long
--- integer. The size will be 32 bits or 64 bits
--- depending on your platform.
--- The "ll" modifier indicates the argument is 64 bits.
--- The b and B conversion specifiers never take a width
--- modifier and always assume their argument is a 64 bit
--- integer.
--- Generally, these modifiers are not useful. They are
--- ignored when type is known from the argument.
---

View File

@ -7393,8 +7393,13 @@ M.funcs = {
than the field width, the field is expanded to contain
the conversion result.
The 'h' modifier indicates the argument is 16 bits.
The 'l' modifier indicates the argument is 32 bits.
The 'L' modifier indicates the argument is 64 bits.
The 'l' modifier indicates the argument is a long
integer. The size will be 32 bits or 64 bits
depending on your platform.
The "ll" modifier indicates the argument is 64 bits.
The b and B conversion specifiers never take a width
modifier and always assume their argument is a 64 bit
integer.
Generally, these modifiers are not useful. They are
ignored when type is known from the argument.

View File

@ -32,33 +32,35 @@
#include "nvim/types.h"
#include "nvim/vim.h"
static char e_cannot_mix_positional_and_non_positional_str[]
static const 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[]
static const 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[]
static const 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[]
static const 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[]
static const 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[]
static const char e_invalid_format_specifier_str[]
= N_("E1405: Invalid format specifier: %s");
static const char e_aptypes_is_null_str_nr[]
= "E1408: Internal error: ap_types or ap_types[idx] is NULL: %s: %d";
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_signedsizet[] = N_("signed size_t");
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_sizet[] = N_("size_t");
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");
static const char typename_unknown[] = N_("unknown");
static const char typename_int[] = N_("int");
static const char typename_longint[] = N_("long int");
static const char typename_longlongint[] = N_("long long int");
static const char typename_signedsizet[] = N_("signed size_t");
static const char typename_unsignedint[] = N_("unsigned int");
static const char typename_unsignedlongint[] = N_("unsigned long int");
static const char typename_unsignedlonglongint[] = N_("unsigned long long int");
static const char typename_sizet[] = N_("size_t");
static const char typename_pointer[] = N_("pointer");
static const char typename_percent[] = N_("percent");
static const char typename_char[] = N_("char");
static const char typename_string[] = N_("string");
static const 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
@ -763,7 +765,7 @@ enum {
};
/// Types that can be used in a format string
static int format_typeof(const char *type, bool usetvs)
static int format_typeof(const char *type)
FUNC_ATTR_NONNULL_ALL
{
// allowed values: \0, h, l, L
@ -800,19 +802,6 @@ static int format_typeof(const char *type, bool usetvs)
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
@ -847,7 +836,7 @@ static int format_typeof(const char *type, bool usetvs)
if (fmt_spec == 'p') {
return TYPE_POINTER;
} else if (fmt_spec == 'b' || fmt_spec == 'B') {
return TYPE_UNSIGNEDINT;
return TYPE_UNSIGNEDLONGLONGINT;
} else if (fmt_spec == 'd') {
// signed
switch (length_modifier) {
@ -893,7 +882,7 @@ static int format_typeof(const char *type, bool usetvs)
static char *format_typename(const char *type)
FUNC_ATTR_NONNULL_ALL
{
switch (format_typeof(type, false)) {
switch (format_typeof(type)) {
case TYPE_INT:
return _(typename_int);
case TYPE_LONGINT:
@ -960,7 +949,7 @@ static int adjust_types(const char ***ap_types, int arg, int *num_posarg, const
}
}
} else {
if (format_typeof(type, false) != format_typeof((*ap_types)[arg - 1], false)) {
if (format_typeof(type) != format_typeof((*ap_types)[arg - 1])) {
semsg(_(e_positional_arg_num_type_inconsistent_str_str), arg,
format_typename(type), format_typename((*ap_types)[arg - 1]));
return FAIL;
@ -1239,7 +1228,7 @@ error:
}
static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, int *arg_idx,
int *arg_cur)
int *arg_cur, const char *fmt)
FUNC_ATTR_NONNULL_ARG(3, 4, 5)
{
int arg_min = 0;
@ -1260,10 +1249,14 @@ static void skip_to_arg(const char **ap_types, va_list ap_start, va_list *ap, in
}
for (*arg_cur = arg_min; *arg_cur < *arg_idx - 1; (*arg_cur)++) {
assert(ap_types != NULL);
if (ap_types == NULL || ap_types[*arg_cur] == NULL) {
semsg(e_aptypes_is_null_str_nr, fmt, *arg_cur);
return;
}
const char *p = ap_types[*arg_cur];
int fmt_type = format_typeof(p, true);
int fmt_type = format_typeof(p);
// get parameter value, do initial processing
switch (fmt_type) {
@ -1477,7 +1470,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, int)));
if (j >= 0) {
@ -1528,7 +1522,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, int)));
if (j >= 0) {
@ -1600,7 +1595,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
case 'c': {
const int j = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, int)));
// standard demands unsigned char
@ -1613,7 +1609,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
case 'S':
str_arg = (tvs
? tv_str(tvs, &arg_idx, &tofree)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, const char *)));
if (!str_arg) {
@ -1684,7 +1681,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
if (fmt_spec == 'p') {
ptr_arg = (tvs
? tv_ptr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, void *)));
if (ptr_arg) {
@ -1696,7 +1694,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
case '\0':
arg = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, int)));
break;
case 'h':
@ -1704,25 +1703,29 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
arg = (int16_t)
(tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, int)));
break;
case 'l':
arg = (tvs
? (long)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, long)));
break;
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),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, long long))); // NOLINT(runtime/int)
break;
case 'z': // implementation-defined, usually ptrdiff_t
arg = (tvs
? (ptrdiff_t)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, ptrdiff_t)));
break;
}
@ -1737,32 +1740,37 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
case '\0':
uarg = (tvs
? (unsigned)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, unsigned)));
break;
case 'h':
uarg = (uint16_t)
(tvs
? (unsigned)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, unsigned)));
break;
case 'l':
uarg = (tvs
? (unsigned long)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, unsigned long)));
break;
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),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, unsigned long long))); // NOLINT(runtime/int)
break;
case 'z':
uarg = (tvs
? (size_t)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, size_t)));
break;
}
@ -1900,7 +1908,8 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
double f = (tvs
? tv_float(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx, &arg_cur),
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
&arg_cur, fmt),
va_arg(ap, double)));
double abs_f = f < 0 ? -f : f;

View File

@ -208,6 +208,7 @@ describe('vim_snprintf()', function()
a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three')
a('1234567', buf, bsize, '%1$d', i(1234567))
a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef))
a('001100', buf, bsize, '%2$0*1$b', i(6), u(12))
a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6))
a('one two', buf, bsize, '%1$s %2$s', 'one', 'two')
a('001100', buf, bsize, '%06b', u(12))