Skip to content

Commit 6297c66

Browse files
pabigotcarlescufi
authored andcommitted
lib: os: cbprintf: correct arg extraction of wide characters
The l length modifier can apply to the c format specifier; in that case the expected value is of type wint_t. Minimal libc doesn't define wint_t, and it is complex to do so correctly (must add <wchar.h>, and use a lot of conditional tricks). wint_t can differ from wchar_t in rank when wchar_t undergoes default integral promotion, which it does on xtensa (wchar_t is unsigned short). So we can use wchar_t as an approximation, except in va_arg where we need to use a wider type: int covers this case. Note that we still don't format wide characters, but we do want to consume the correct amount of data for a default-promoted extended character. Signed-off-by: Peter Bigot <[email protected]>
1 parent 8eda19a commit 6297c66

File tree

1 file changed

+46
-3
lines changed

1 file changed

+46
-3
lines changed

lib/os/cbprintf_complete.c

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,37 @@ enum specifier_cat_enum {
8888
#define CASE_UINT_CHAR case 'c':
8989
#endif
9090

91+
/* We need two pieces of information about wchar_t:
92+
* * WCHAR_IS_SIGNED: whether it's signed or unsigned;
93+
* * WINT_TYPE: the type to use when extracting it from va_args
94+
*
95+
* The former can be determined from the value of WCHAR_MIN if it's defined.
96+
* It's not for minimal libc, so treat it as whatever char is.
97+
*
98+
* The latter should be wint_t, but minimal libc doesn't provide it. We can
99+
* substitute wchar_t as long as that type does not undergo default integral
100+
* promotion as an argument. But it does for at least one toolchain (xtensa),
101+
* and where it does we need to use the promoted type in va_arg() to avoid
102+
* build errors, otherwise we can use the base type. We can tell that
103+
* integral promotion occurs if WCHAR_MAX is strictly less than INT_MAX.
104+
*/
105+
#ifndef WCHAR_MIN
106+
#define WCHAR_IS_SIGNED CHAR_IS_SIGNED
107+
#if WCHAR_IS_SIGNED
108+
#define WINT_TYPE int
109+
#else /* wchar signed */
110+
#define WINT_TYPE unsigned int
111+
#endif /* wchar signed */
112+
#else /* WCHAR_MIN defined */
113+
#define WCHAR_IS_SIGNED ((WCHAR_MIN - 0) != 0)
114+
#if WCHAR_MAX < INT_MAX
115+
/* Signed or unsigned, it'll be int */
116+
#define WINT_TYPE int
117+
#else /* wchar rank vs int */
118+
#define WINT_TYPE wchar_t
119+
#endif /* wchar rank vs int */
120+
#endif /* WCHAR_MIN defined */
121+
91122
/* Case label to identify conversions for signed integral values. The
92123
* corresponding argument_value tag is sint and category is
93124
* SPECIFIER_SINT.
@@ -499,7 +530,7 @@ static inline const char *extract_specifier(struct conversion *conv,
499530
}
500531

501532
/* For c LENGTH_NONE and LENGTH_L would be ok,
502-
* but we don't support wide characters.
533+
* but we don't support formatting wide characters.
503534
*/
504535
if (conv->specifier == 'c') {
505536
unsupported = (conv->length_mod != LENGTH_NONE);
@@ -1420,7 +1451,13 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap)
14201451
value->sint = va_arg(ap, int);
14211452
break;
14221453
case LENGTH_L:
1423-
value->sint = va_arg(ap, long);
1454+
if (WCHAR_IS_SIGNED
1455+
&& (conv->specifier == 'c')) {
1456+
value->sint = (wchar_t)va_arg(ap,
1457+
WINT_TYPE);
1458+
} else {
1459+
value->sint = va_arg(ap, long);
1460+
}
14241461
break;
14251462
case LENGTH_LL:
14261463
value->sint =
@@ -1457,7 +1494,13 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap)
14571494
value->uint = va_arg(ap, unsigned int);
14581495
break;
14591496
case LENGTH_L:
1460-
value->uint = va_arg(ap, unsigned long);
1497+
if ((!WCHAR_IS_SIGNED)
1498+
&& (conv->specifier == 'c')) {
1499+
value->uint = (wchar_t)va_arg(ap,
1500+
WINT_TYPE);
1501+
} else {
1502+
value->uint = va_arg(ap, unsigned long);
1503+
}
14611504
break;
14621505
case LENGTH_LL:
14631506
value->uint =

0 commit comments

Comments
 (0)