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 <peter.bigot@nordicsemi.no>
This commit is contained in:
parent
8eda19a898
commit
6297c667c5
1 changed files with 46 additions and 3 deletions
|
@ -88,6 +88,37 @@ enum specifier_cat_enum {
|
|||
#define CASE_UINT_CHAR case 'c':
|
||||
#endif
|
||||
|
||||
/* We need two pieces of information about wchar_t:
|
||||
* * WCHAR_IS_SIGNED: whether it's signed or unsigned;
|
||||
* * WINT_TYPE: the type to use when extracting it from va_args
|
||||
*
|
||||
* The former can be determined from the value of WCHAR_MIN if it's defined.
|
||||
* It's not for minimal libc, so treat it as whatever char is.
|
||||
*
|
||||
* The latter should be wint_t, but minimal libc doesn't provide it. We can
|
||||
* substitute wchar_t as long as that type does not undergo default integral
|
||||
* promotion as an argument. But it does for at least one toolchain (xtensa),
|
||||
* and where it does we need to use the promoted type in va_arg() to avoid
|
||||
* build errors, otherwise we can use the base type. We can tell that
|
||||
* integral promotion occurs if WCHAR_MAX is strictly less than INT_MAX.
|
||||
*/
|
||||
#ifndef WCHAR_MIN
|
||||
#define WCHAR_IS_SIGNED CHAR_IS_SIGNED
|
||||
#if WCHAR_IS_SIGNED
|
||||
#define WINT_TYPE int
|
||||
#else /* wchar signed */
|
||||
#define WINT_TYPE unsigned int
|
||||
#endif /* wchar signed */
|
||||
#else /* WCHAR_MIN defined */
|
||||
#define WCHAR_IS_SIGNED ((WCHAR_MIN - 0) != 0)
|
||||
#if WCHAR_MAX < INT_MAX
|
||||
/* Signed or unsigned, it'll be int */
|
||||
#define WINT_TYPE int
|
||||
#else /* wchar rank vs int */
|
||||
#define WINT_TYPE wchar_t
|
||||
#endif /* wchar rank vs int */
|
||||
#endif /* WCHAR_MIN defined */
|
||||
|
||||
/* Case label to identify conversions for signed integral values. The
|
||||
* corresponding argument_value tag is sint and category is
|
||||
* SPECIFIER_SINT.
|
||||
|
@ -499,7 +530,7 @@ int_conv:
|
|||
}
|
||||
|
||||
/* For c LENGTH_NONE and LENGTH_L would be ok,
|
||||
* but we don't support wide characters.
|
||||
* but we don't support formatting wide characters.
|
||||
*/
|
||||
if (conv->specifier == 'c') {
|
||||
unsupported = (conv->length_mod != LENGTH_NONE);
|
||||
|
@ -1420,7 +1451,13 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap)
|
|||
value->sint = va_arg(ap, int);
|
||||
break;
|
||||
case LENGTH_L:
|
||||
value->sint = va_arg(ap, long);
|
||||
if (WCHAR_IS_SIGNED
|
||||
&& (conv->specifier == 'c')) {
|
||||
value->sint = (wchar_t)va_arg(ap,
|
||||
WINT_TYPE);
|
||||
} else {
|
||||
value->sint = va_arg(ap, long);
|
||||
}
|
||||
break;
|
||||
case LENGTH_LL:
|
||||
value->sint =
|
||||
|
@ -1457,7 +1494,13 @@ int cbvprintf(cbprintf_cb out, void *ctx, const char *fp, va_list ap)
|
|||
value->uint = va_arg(ap, unsigned int);
|
||||
break;
|
||||
case LENGTH_L:
|
||||
value->uint = va_arg(ap, unsigned long);
|
||||
if ((!WCHAR_IS_SIGNED)
|
||||
&& (conv->specifier == 'c')) {
|
||||
value->uint = (wchar_t)va_arg(ap,
|
||||
WINT_TYPE);
|
||||
} else {
|
||||
value->uint = va_arg(ap, unsigned long);
|
||||
}
|
||||
break;
|
||||
case LENGTH_LL:
|
||||
value->uint =
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue