Skip to content

Commit 614d134

Browse files
committed
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 <[email protected]>
1 parent 312f48b commit 614d134

File tree

1 file changed

+54
-79
lines changed

1 file changed

+54
-79
lines changed

lib/vsprintf.c

Lines changed: 54 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -2566,7 +2566,7 @@ static noinline_for_stack
25662566
struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
25672567
{
25682568
const char *start = fmt.str;
2569-
char flag, qualifier;
2569+
char flag;
25702570

25712571
/* we finished early by reading the field width */
25722572
if (fmt.state == FORMAT_STATE_WIDTH) {
@@ -2637,96 +2637,71 @@ struct fmt format_decode(struct fmt fmt, struct printf_spec *spec)
26372637
}
26382638

26392639
qualifier:
2640-
/* get the conversion qualifier */
2641-
qualifier = 0;
2642-
if (*fmt.str == 'h' || _tolower(*fmt.str) == 'l' ||
2643-
*fmt.str == 'z' || *fmt.str == 't') {
2644-
qualifier = *fmt.str++;
2645-
if (unlikely(qualifier == *fmt.str)) {
2646-
if (qualifier == 'l') {
2647-
qualifier = 'L';
2648-
fmt.str++;
2649-
} else if (qualifier == 'h') {
2650-
qualifier = 'H';
2651-
fmt.str++;
2652-
}
2653-
}
2654-
}
2655-
2656-
/* default base */
2640+
/* Set up default numeric format */
26572641
spec->base = 10;
2658-
switch (*fmt.str) {
2659-
case 'c':
2660-
fmt.state = FORMAT_STATE_CHAR;
2661-
fmt.str++;
2662-
return fmt;
2663-
2664-
case 's':
2665-
fmt.state = FORMAT_STATE_STR;
2666-
fmt.str++;
2667-
return fmt;
2668-
2669-
case 'p':
2670-
fmt.state = FORMAT_STATE_PTR;
2671-
fmt.str++;
2672-
return fmt;
2673-
2674-
case '%':
2675-
fmt.state = FORMAT_STATE_PERCENT_CHAR;
2676-
fmt.str++;
2677-
return fmt;
2678-
2679-
/* integer number formats - set up the flags and "break" */
2680-
case 'o':
2681-
spec->base = 8;
2682-
break;
2683-
2684-
case 'x':
2685-
spec->flags |= SMALL;
2686-
fallthrough;
2687-
2688-
case 'X':
2689-
spec->base = 16;
2690-
break;
2642+
fmt.state = FORMAT_STATE_SIZE(int);
2643+
static const struct format_state {
2644+
unsigned char state;
2645+
unsigned char flags_or_double_state;
2646+
unsigned char modifier;
2647+
unsigned char base;
2648+
} lookup_state[256] = {
2649+
// Qualifiers
2650+
['l'] = { FORMAT_STATE_SIZE(long), FORMAT_STATE_SIZE(long long), 1 },
2651+
['L'] = { FORMAT_STATE_SIZE(long long), 0, 1 },
2652+
['h'] = { FORMAT_STATE_SIZE(short), FORMAT_STATE_SIZE(char), 1 },
2653+
['H'] = { FORMAT_STATE_SIZE(char), 0, 1 }, // Questionable, historic
2654+
['z'] = { FORMAT_STATE_SIZE(size_t), 0, 1 },
2655+
['t'] = { FORMAT_STATE_SIZE(ptrdiff_t), 0, 1 },
2656+
2657+
// Non-numeric formats
2658+
['c'] = { FORMAT_STATE_CHAR },
2659+
['s'] = { FORMAT_STATE_STR },
2660+
['p'] = { FORMAT_STATE_PTR },
2661+
['%'] = { FORMAT_STATE_PERCENT_CHAR },
2662+
2663+
// Numerics
2664+
['o'] = { 0, 0, 0, 8 },
2665+
['x'] = { 0, SMALL, 0, 16 },
2666+
['X'] = { 0, 0, 0, 16 },
2667+
['d'] = { 0, SIGN, 0, 10 },
2668+
['i'] = { 0, SIGN, 0, 10 },
2669+
['u'] = { 0, 0, 0, 10, },
26912670

2692-
case 'd':
2693-
case 'i':
2694-
spec->flags |= SIGN;
2695-
break;
2696-
case 'u':
2697-
break;
2698-
2699-
case 'n':
27002671
/*
27012672
* Since %n poses a greater security risk than
27022673
* utility, treat it as any other invalid or
27032674
* unsupported format specifier.
27042675
*/
2705-
fallthrough;
2676+
};
27062677

2707-
default:
2708-
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
2709-
fmt.state = FORMAT_STATE_INVALID;
2678+
const struct format_state *p = lookup_state + (u8)*fmt.str;
2679+
if (p->modifier) {
2680+
fmt.state = p->state;
2681+
if (p->flags_or_double_state && fmt.str[0] == fmt.str[1]) {
2682+
fmt.state = p->flags_or_double_state;
2683+
fmt.str++;
2684+
}
2685+
fmt.str++;
2686+
p = lookup_state + *fmt.str;
2687+
if (unlikely(p->modifier))
2688+
goto invalid;
2689+
}
2690+
if (p->base) {
2691+
spec->base = p->base;
2692+
spec->flags |= p->flags_or_double_state;
2693+
fmt.str++;
27102694
return fmt;
27112695
}
2712-
2713-
if (qualifier == 'L')
2714-
fmt.state = FORMAT_STATE_SIZE(long long);
2715-
else if (qualifier == 'l') {
2716-
fmt.state = FORMAT_STATE_SIZE(long);
2717-
} else if (qualifier == 'z') {
2718-
fmt.state = FORMAT_STATE_SIZE(size_t);
2719-
} else if (qualifier == 't') {
2720-
fmt.state = FORMAT_STATE_SIZE(ptrdiff_t);
2721-
} else if (qualifier == 'H') {
2722-
fmt.state = FORMAT_STATE_SIZE(char);
2723-
} else if (qualifier == 'h') {
2724-
fmt.state = FORMAT_STATE_SIZE(short);
2725-
} else {
2726-
fmt.state = FORMAT_STATE_SIZE(int);
2696+
if (p->state) {
2697+
fmt.state = p->state;
2698+
fmt.str++;
2699+
return fmt;
27272700
}
27282701

2729-
fmt.str++;
2702+
invalid:
2703+
WARN_ONCE(1, "Please remove unsupported %%%c in format string\n", *fmt.str);
2704+
fmt.state = FORMAT_STATE_INVALID;
27302705
return fmt;
27312706
}
27322707

0 commit comments

Comments
 (0)