1
0
Fork 0
mirror of synced 2025-03-06 20:59:54 +01:00

vsnprintf: collapse the number format state into one single state

We'll squirrel away the size of the number in 'struct fmt' instead.

We have two fairly separate state structures: the 'decode state' is in
'struct fmt', while the 'printout format' is in 'printf_spec'.  Both
structures are small enough to pass around in registers even across
function boundaries (ie two words), even on 32-bit machines.

The goal here is to avoid the case statements on the format states,
which generate either deep conditionals or jump tables, while also
keeping the state size manageable.

Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
Linus Torvalds 2024-12-19 13:52:53 -08:00
parent 2b76e39fca
commit 8d4826cc8a

View file

@ -419,10 +419,7 @@ static_assert(SMALL == ('a' ^ 'A'));
enum format_state { enum format_state {
FORMAT_STATE_NONE, /* Just a string part */ FORMAT_STATE_NONE, /* Just a string part */
FORMAT_STATE_1BYTE = 1, /* char/short/int are their own sizes */ FORMAT_STATE_NUM,
FORMAT_STATE_2BYTE = 2,
FORMAT_STATE_8BYTE = 3,
FORMAT_STATE_4BYTE = 4,
FORMAT_STATE_WIDTH, FORMAT_STATE_WIDTH,
FORMAT_STATE_PRECISION, FORMAT_STATE_PRECISION,
FORMAT_STATE_CHAR, FORMAT_STATE_CHAR,
@ -432,8 +429,6 @@ enum format_state {
FORMAT_STATE_INVALID, FORMAT_STATE_INVALID,
}; };
#define FORMAT_STATE_SIZE(type) (sizeof(type) <= 4 ? sizeof(type) : FORMAT_STATE_8BYTE)
struct printf_spec { struct printf_spec {
unsigned char flags; /* flags to number() */ unsigned char flags; /* flags to number() */
unsigned char base; /* number base, 8, 10 or 16 only */ unsigned char base; /* number base, 8, 10 or 16 only */
@ -2523,7 +2518,8 @@ char *pointer(const char *fmt, char *buf, char *end, void *ptr,
struct fmt { struct fmt {
const char *str; const char *str;
enum format_state state; unsigned char state; // enum format_state
unsigned char size; // size of numbers
}; };
#define SPEC_CHAR(x, flag) [(x)-32] = flag #define SPEC_CHAR(x, flag) [(x)-32] = flag
@ -2638,20 +2634,21 @@ precision:
qualifier: qualifier:
/* Set up default numeric format */ /* Set up default numeric format */
spec->base = 10; spec->base = 10;
fmt.state = FORMAT_STATE_SIZE(int); fmt.state = FORMAT_STATE_NUM;
fmt.size = sizeof(int);
static const struct format_state { static const struct format_state {
unsigned char state; unsigned char state;
unsigned char flags_or_double_state; unsigned char size;
unsigned char modifier; unsigned char flags_or_double_size;
unsigned char base; unsigned char base;
} lookup_state[256] = { } lookup_state[256] = {
// Qualifiers // Length
['l'] = { FORMAT_STATE_SIZE(long), FORMAT_STATE_SIZE(long long), 1 }, ['l'] = { 0, sizeof(long), sizeof(long long) },
['L'] = { FORMAT_STATE_SIZE(long long), 0, 1 }, ['L'] = { 0, sizeof(long long) },
['h'] = { FORMAT_STATE_SIZE(short), FORMAT_STATE_SIZE(char), 1 }, ['h'] = { 0, sizeof(short), sizeof(char) },
['H'] = { FORMAT_STATE_SIZE(char), 0, 1 }, // Questionable, historic ['H'] = { 0, sizeof(char) }, // Questionable historical
['z'] = { FORMAT_STATE_SIZE(size_t), 0, 1 }, ['z'] = { 0, sizeof(size_t) },
['t'] = { FORMAT_STATE_SIZE(ptrdiff_t), 0, 1 }, ['t'] = { 0, sizeof(ptrdiff_t) },
// Non-numeric formats // Non-numeric formats
['c'] = { FORMAT_STATE_CHAR }, ['c'] = { FORMAT_STATE_CHAR },
@ -2660,12 +2657,12 @@ qualifier:
['%'] = { FORMAT_STATE_PERCENT_CHAR }, ['%'] = { FORMAT_STATE_PERCENT_CHAR },
// Numerics // Numerics
['o'] = { 0, 0, 0, 8 }, ['o'] = { FORMAT_STATE_NUM, 0, 0, 8 },
['x'] = { 0, SMALL, 0, 16 }, ['x'] = { FORMAT_STATE_NUM, 0, SMALL, 16 },
['X'] = { 0, 0, 0, 16 }, ['X'] = { FORMAT_STATE_NUM, 0, 0, 16 },
['d'] = { 0, SIGN, 0, 10 }, ['d'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
['i'] = { 0, SIGN, 0, 10 }, ['i'] = { FORMAT_STATE_NUM, 0, SIGN, 10 },
['u'] = { 0, 0, 0, 10, }, ['u'] = { FORMAT_STATE_NUM, 0, 0, 10, },
/* /*
* Since %n poses a greater security risk than * Since %n poses a greater security risk than
@ -2675,30 +2672,23 @@ qualifier:
}; };
const struct format_state *p = lookup_state + (u8)*fmt.str; const struct format_state *p = lookup_state + (u8)*fmt.str;
if (p->modifier) { if (p->size) {
fmt.state = p->state; fmt.size = p->size;
if (p->flags_or_double_state && fmt.str[0] == fmt.str[1]) { if (p->flags_or_double_size && fmt.str[0] == fmt.str[1]) {
fmt.state = p->flags_or_double_state; fmt.size = p->flags_or_double_size;
fmt.str++; fmt.str++;
} }
fmt.str++; fmt.str++;
p = lookup_state + *fmt.str; p = lookup_state + *fmt.str;
if (unlikely(p->modifier))
goto invalid;
}
if (p->base) {
spec->base = p->base;
spec->flags |= p->flags_or_double_state;
fmt.str++;
return fmt;
} }
if (p->state) { if (p->state) {
spec->base = p->base;
spec->flags |= p->flags_or_double_size;
fmt.state = p->state; fmt.state = p->state;
fmt.str++; fmt.str++;
return fmt; return fmt;
} }
invalid:
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str); WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
fmt.state = FORMAT_STATE_INVALID; fmt.state = FORMAT_STATE_INVALID;
return fmt; return fmt;
@ -2768,7 +2758,6 @@ static unsigned long long convert_num_spec(unsigned int val, int size, struct pr
*/ */
int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args) int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
{ {
unsigned long long num;
char *str, *end; char *str, *end;
struct printf_spec spec = {0}; struct printf_spec spec = {0};
struct fmt fmt = { struct fmt fmt = {
@ -2808,6 +2797,16 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
continue; continue;
} }
case FORMAT_STATE_NUM: {
unsigned long long num;
if (fmt.size <= sizeof(int))
num = convert_num_spec(va_arg(args, int), fmt.size, spec);
else
num = va_arg(args, long long);
str = number(str, end, num, spec);
continue;
}
case FORMAT_STATE_WIDTH: case FORMAT_STATE_WIDTH:
set_field_width(&spec, va_arg(args, int)); set_field_width(&spec, va_arg(args, int));
continue; continue;
@ -2856,7 +2855,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
++str; ++str;
continue; continue;
case FORMAT_STATE_INVALID: default:
/* /*
* Presumably the arguments passed gcc's type * Presumably the arguments passed gcc's type
* checking, but there is no safe or sane way * checking, but there is no safe or sane way
@ -2866,17 +2865,7 @@ int vsnprintf(char *buf, size_t size, const char *fmt_str, va_list args)
* sync. * sync.
*/ */
goto out; goto out;
case FORMAT_STATE_8BYTE:
num = va_arg(args, long long);
break;
default:
num = convert_num_spec(va_arg(args, int), fmt.state, spec);
break;
} }
str = number(str, end, num, spec);
} }
out: out:
@ -3147,17 +3136,20 @@ int vbin_printf(u32 *bin_buf, size_t size, const char *fmt_str, va_list args)
fmt.str++; fmt.str++;
break; break;
case FORMAT_STATE_8BYTE: case FORMAT_STATE_NUM:
save_arg(long long); switch (fmt.size) {
break; case 8:
case FORMAT_STATE_1BYTE: save_arg(long long);
save_arg(char); break;
break; case 1:
case FORMAT_STATE_2BYTE: save_arg(char);
save_arg(short); break;
break; case 2:
default: save_arg(short);
save_arg(int); break;
default:
save_arg(int);
}
} }
} }
@ -3325,18 +3317,21 @@ int bstr_printf(char *buf, size_t size, const char *fmt_str, const u32 *bin_buf)
case FORMAT_STATE_INVALID: case FORMAT_STATE_INVALID:
goto out; goto out;
case FORMAT_STATE_8BYTE: case FORMAT_STATE_NUM:
num = get_arg(long long); switch (fmt.size) {
break; case 8:
case FORMAT_STATE_2BYTE: num = get_arg(long long);
num = convert_num_spec(get_arg(short), fmt.state, spec); break;
break; case 1:
case FORMAT_STATE_1BYTE: num = convert_num_spec(get_arg(char), fmt.size, spec);
num = convert_num_spec(get_arg(char), fmt.state, spec); break;
break; case 2:
default: num = convert_num_spec(get_arg(short), fmt.size, spec);
num = convert_num_spec(get_arg(int), fmt.state, spec); break;
break; default:
num = convert_num_spec(get_arg(int), fmt.size, spec);
break;
}
} }
str = number(str, end, num, spec); str = number(str, end, num, spec);