mirror of
https://github.com/torvalds/linux.git
synced 2025-11-03 01:59:51 +02:00
vsprintf: deal with format specifiers with a lookup table
We did the flags as an array earlier, they had simpler rules. The final format specifiers are a bit more complex since they have more fields to deal with, and we want to handle the length modifiers at the same time. But like the flags, we're better off just making it a data-driven table rather than some case statement. Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
This commit is contained in:
parent
312f48b2e2
commit
614d13462d
1 changed files with 53 additions and 78 deletions
131
lib/vsprintf.c
131
lib/vsprintf.c
|
|
@ -2566,7 +2566,7 @@ static noinline_for_stack
|
||||||
struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
|
struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
|
||||||
{
|
{
|
||||||
const char *start = fmt.str;
|
const char *start = fmt.str;
|
||||||
char flag, qualifier;
|
char flag;
|
||||||
|
|
||||||
/* we finished early by reading the field width */
|
/* we finished early by reading the field width */
|
||||||
if (fmt.state == FORMAT_STATE_WIDTH) {
|
if (fmt.state == FORMAT_STATE_WIDTH) {
|
||||||
|
|
@ -2637,96 +2637,71 @@ struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
|
||||||
}
|
}
|
||||||
|
|
||||||
qualifier:
|
qualifier:
|
||||||
/* get the conversion qualifier */
|
/* Set up default numeric format */
|
||||||
qualifier = 0;
|
|
||||||
if (*fmt.str == 'h' || _tolower(*fmt.str) == 'l' ||
|
|
||||||
*fmt.str == 'z' || *fmt.str == 't') {
|
|
||||||
qualifier = *fmt.str++;
|
|
||||||
if (unlikely(qualifier == *fmt.str)) {
|
|
||||||
if (qualifier == 'l') {
|
|
||||||
qualifier = 'L';
|
|
||||||
fmt.str++;
|
|
||||||
} else if (qualifier == 'h') {
|
|
||||||
qualifier = 'H';
|
|
||||||
fmt.str++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* default base */
|
|
||||||
spec->base = 10;
|
spec->base = 10;
|
||||||
switch (*fmt.str) {
|
fmt.state = FORMAT_STATE_SIZE(int);
|
||||||
case 'c':
|
static const struct format_state {
|
||||||
fmt.state = FORMAT_STATE_CHAR;
|
unsigned char state;
|
||||||
fmt.str++;
|
unsigned char flags_or_double_state;
|
||||||
return fmt;
|
unsigned char modifier;
|
||||||
|
unsigned char base;
|
||||||
|
} lookup_state[256] = {
|
||||||
|
// Qualifiers
|
||||||
|
['l'] = { FORMAT_STATE_SIZE(long), FORMAT_STATE_SIZE(long long), 1 },
|
||||||
|
['L'] = { FORMAT_STATE_SIZE(long long), 0, 1 },
|
||||||
|
['h'] = { FORMAT_STATE_SIZE(short), FORMAT_STATE_SIZE(char), 1 },
|
||||||
|
['H'] = { FORMAT_STATE_SIZE(char), 0, 1 }, // Questionable, historic
|
||||||
|
['z'] = { FORMAT_STATE_SIZE(size_t), 0, 1 },
|
||||||
|
['t'] = { FORMAT_STATE_SIZE(ptrdiff_t), 0, 1 },
|
||||||
|
|
||||||
case 's':
|
// Non-numeric formats
|
||||||
fmt.state = FORMAT_STATE_STR;
|
['c'] = { FORMAT_STATE_CHAR },
|
||||||
fmt.str++;
|
['s'] = { FORMAT_STATE_STR },
|
||||||
return fmt;
|
['p'] = { FORMAT_STATE_PTR },
|
||||||
|
['%'] = { FORMAT_STATE_PERCENT_CHAR },
|
||||||
|
|
||||||
case 'p':
|
// Numerics
|
||||||
fmt.state = FORMAT_STATE_PTR;
|
['o'] = { 0, 0, 0, 8 },
|
||||||
fmt.str++;
|
['x'] = { 0, SMALL, 0, 16 },
|
||||||
return fmt;
|
['X'] = { 0, 0, 0, 16 },
|
||||||
|
['d'] = { 0, SIGN, 0, 10 },
|
||||||
|
['i'] = { 0, SIGN, 0, 10 },
|
||||||
|
['u'] = { 0, 0, 0, 10, },
|
||||||
|
|
||||||
case '%':
|
|
||||||
fmt.state = FORMAT_STATE_PERCENT_CHAR;
|
|
||||||
fmt.str++;
|
|
||||||
return fmt;
|
|
||||||
|
|
||||||
/* integer number formats - set up the flags and "break" */
|
|
||||||
case 'o':
|
|
||||||
spec->base = 8;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'x':
|
|
||||||
spec->flags |= SMALL;
|
|
||||||
fallthrough;
|
|
||||||
|
|
||||||
case 'X':
|
|
||||||
spec->base = 16;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'd':
|
|
||||||
case 'i':
|
|
||||||
spec->flags |= SIGN;
|
|
||||||
break;
|
|
||||||
case 'u':
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'n':
|
|
||||||
/*
|
/*
|
||||||
* Since %n poses a greater security risk than
|
* Since %n poses a greater security risk than
|
||||||
* utility, treat it as any other invalid or
|
* utility, treat it as any other invalid or
|
||||||
* unsupported format specifier.
|
* unsupported format specifier.
|
||||||
*/
|
*/
|
||||||
fallthrough;
|
};
|
||||||
|
|
||||||
default:
|
const struct format_state *p = lookup_state + (u8)*fmt.str;
|
||||||
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
|
if (p->modifier) {
|
||||||
fmt.state = FORMAT_STATE_INVALID;
|
fmt.state = p->state;
|
||||||
|
if (p->flags_or_double_state && fmt.str[0] == fmt.str[1]) {
|
||||||
|
fmt.state = p->flags_or_double_state;
|
||||||
|
fmt.str++;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
fmt.state = p->state;
|
||||||
|
fmt.str++;
|
||||||
return fmt;
|
return fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (qualifier == 'L')
|
invalid:
|
||||||
fmt.state = FORMAT_STATE_SIZE(long long);
|
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
|
||||||
else if (qualifier == 'l') {
|
fmt.state = FORMAT_STATE_INVALID;
|
||||||
fmt.state = FORMAT_STATE_SIZE(long);
|
|
||||||
} else if (qualifier == 'z') {
|
|
||||||
fmt.state = FORMAT_STATE_SIZE(size_t);
|
|
||||||
} else if (qualifier == 't') {
|
|
||||||
fmt.state = FORMAT_STATE_SIZE(ptrdiff_t);
|
|
||||||
} else if (qualifier == 'H') {
|
|
||||||
fmt.state = FORMAT_STATE_SIZE(char);
|
|
||||||
} else if (qualifier == 'h') {
|
|
||||||
fmt.state = FORMAT_STATE_SIZE(short);
|
|
||||||
} else {
|
|
||||||
fmt.state = FORMAT_STATE_SIZE(int);
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.str++;
|
|
||||||
return fmt;
|
return fmt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue