From b9d0f5d562b1a5e96a32779f8906e50c0e4ba206 Mon Sep 17 00:00:00 2001 From: Martin Storsjo Date: Tue, 3 Nov 2015 20:40:38 +0200 Subject: [PATCH] ucrtbase: Implement the new printf corner case behaviour. Check the option flags whether the new or old legacy behaviours are wanted. For _MSVCR_VER < 140, don't check the option flags but hardcode them to TRUE. (This avoids having to manually add all three flags into every caller of pf_printf.) Mask out any other flags, to avoid other out-of-range flags to be interpreted as the other flags (positional params, invoke invalid param handler). Signed-off-by: Martin Storsjo Signed-off-by: Piotr Caban Signed-off-by: Alexandre Julliard --- dlls/msvcrt/file.c | 6 ++- dlls/msvcrt/msvcrt.h | 3 +- dlls/msvcrt/printf.h | 51 +++++++++++++++----- dlls/msvcrt/wcs.c | 35 +++++++++----- dlls/ucrtbase/tests/printf.c | 90 ++++++++++++++++++++++++++++++++++++ 5 files changed, 158 insertions(+), 27 deletions(-) diff --git a/dlls/msvcrt/file.c b/dlls/msvcrt/file.c index 3fcfb0e7deb..f1de534b41c 100644 --- a/dlls/msvcrt/file.c +++ b/dlls/msvcrt/file.c @@ -4998,7 +4998,11 @@ int CDECL MSVCRT__stdio_common_vfprintf(unsigned __int64 options, MSVCRT_FILE *f MSVCRT__lock_file(file); tmp_buf = add_std_buffer(file); - ret = pf_printf_a(puts_clbk_file_a, file, format, locale, 0, arg_clbk_valist, NULL, &valist); + + if (options & ~UCRTBASE_PRINTF_MASK) + FIXME("options %s not handled\n", wine_dbgstr_longlong(options)); + ret = pf_printf_a(puts_clbk_file_a, file, format, locale, + options & UCRTBASE_PRINTF_MASK, arg_clbk_valist, NULL, &valist); if(tmp_buf) remove_std_buffer(file); MSVCRT__unlock_file(file); diff --git a/dlls/msvcrt/msvcrt.h b/dlls/msvcrt/msvcrt.h index 81aea52331f..4ce820284d2 100644 --- a/dlls/msvcrt/msvcrt.h +++ b/dlls/msvcrt/msvcrt.h @@ -1130,8 +1130,7 @@ extern char* __cdecl __unDName(char *,const char*,int,malloc_func_t,free_func_t, #define UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY (0x0008) #define UCRTBASE_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS (0x0010) -#define UCRTBASE_PRINTF_TERMINATION_MASK (UCRTBASE_PRINTF_LEGACY_VSPRINTF_NULL_TERMINATION | \ - UCRTBASE_PRINTF_STANDARD_SNPRINTF_BEHAVIOUR) +#define UCRTBASE_PRINTF_MASK (0x001F) #define MSVCRT_PRINTF_POSITIONAL_PARAMS (0x0100) #define MSVCRT_PRINTF_INVOKE_INVALID_PARAM_HANDLER (0x0200) diff --git a/dlls/msvcrt/printf.h b/dlls/msvcrt/printf.h index a72440487a1..1e215aab5cb 100644 --- a/dlls/msvcrt/printf.h +++ b/dlls/msvcrt/printf.h @@ -82,7 +82,7 @@ static inline int FUNC_NAME(pf_fill)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ct { int i, r = 0, written; - if(flags->Sign && !strchr("diaeEfgG", flags->Format)) + if(flags->Sign && !strchr("diaeEfFgG", flags->Format)) flags->Sign = 0; if(left && flags->Sign) { @@ -206,8 +206,9 @@ static inline int FUNC_NAME(pf_output_format_str)(FUNC_NAME(puts_clbk) pf_puts, } static inline int FUNC_NAME(pf_handle_string)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, - const void *str, int len, FUNC_NAME(pf_flags) *flags, MSVCRT_pthreadlocinfo locinfo) + const void *str, int len, FUNC_NAME(pf_flags) *flags, MSVCRT_pthreadlocinfo locinfo, BOOL legacy_wide) { + BOOL complement_is_narrow = legacy_wide ? (sizeof(APICHAR)==sizeof(MSVCRT_wchar_t)) : FALSE; #ifdef PRINTF_WIDE static const MSVCRT_wchar_t nullW[] = {'(','n','u','l','l',')',0}; @@ -223,7 +224,7 @@ static inline int FUNC_NAME(pf_handle_string)(FUNC_NAME(puts_clbk) pf_puts, void if(flags->IntegerLength == 'h') return FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, str, len, flags, locinfo); - if((flags->Format=='S' || flags->Format=='C') == (sizeof(APICHAR)==sizeof(MSVCRT_wchar_t))) + if((flags->Format=='S' || flags->Format=='C') == complement_is_narrow) return FUNC_NAME(pf_output_format_str)(pf_puts, puts_ctx, str, len, flags, locinfo); else return FUNC_NAME(pf_output_format_wstr)(pf_puts, puts_ctx, str, len, flags, locinfo); @@ -304,7 +305,7 @@ static inline void FUNC_NAME(pf_integer_conv)(APICHAR *buf, int buf_len, } } -static inline void FUNC_NAME(pf_fixup_exponent)(char *buf) +static inline void FUNC_NAME(pf_fixup_exponent)(char *buf, BOOL three_digit_exp) { char* tmp = buf; @@ -313,7 +314,11 @@ static inline void FUNC_NAME(pf_fixup_exponent)(char *buf) if(tmp[0] && (tmp[1]=='+' || tmp[1]=='-') && isdigit(tmp[2]) && isdigit(tmp[3])) { +#if _MSVCR_VER >= 140 + BOOL two_digit_exp = !three_digit_exp; +#else BOOL two_digit_exp = (MSVCRT__get_output_format() == MSVCRT__TWO_DIGIT_EXPONENT); +#endif tmp += 2; if(isdigit(tmp[2])) { @@ -346,6 +351,13 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API FUNC_NAME(pf_flags) flags; BOOL positional_params = options & MSVCRT_PRINTF_POSITIONAL_PARAMS; BOOL invoke_invalid_param_handler = options & MSVCRT_PRINTF_INVOKE_INVALID_PARAM_HANDLER; +#if _MSVCR_VER >= 140 + BOOL legacy_wide = options & UCRTBASE_PRINTF_LEGACY_WIDE_SPECIFIERS; + BOOL legacy_msvcrt_compat = options & UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY; + BOOL three_digit_exp = options & UCRTBASE_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS; +#else + BOOL legacy_wide = TRUE, legacy_msvcrt_compat = TRUE, three_digit_exp = TRUE; +#endif TRACE("Format is: %s\n", FUNC_NAME(debugstr)(fmt)); @@ -461,7 +473,7 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API p++; } else if(*p == 'w') flags.WideString = *p++; - else if(*p == 'F' || *p == 'N') + else if((*p == 'F' || *p == 'N') && legacy_msvcrt_compat) p++; /* ignore */ else break; @@ -472,14 +484,14 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API if(flags.Format == 's' || flags.Format == 'S') { i = FUNC_NAME(pf_handle_string)(pf_puts, puts_ctx, pf_args(args_ctx, pos, VT_PTR, valist).get_ptr, - -1, &flags, locinfo); + -1, &flags, locinfo, legacy_wide); } else if(flags.Format == 'c' || flags.Format == 'C') { int ch = pf_args(args_ctx, pos, VT_INT, valist).get_int; if((ch&0xff) != ch) FIXME("multibyte characters printing not supported\n"); - i = FUNC_NAME(pf_handle_string)(pf_puts, puts_ctx, &ch, 1, &flags, locinfo); + i = FUNC_NAME(pf_handle_string)(pf_puts, puts_ctx, &ch, 1, &flags, locinfo, legacy_wide); } else if(flags.Format == 'p') { flags.Format = 'X'; flags.PadZero = '0'; @@ -540,7 +552,7 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API #endif if(tmp != buf) HeapFree(GetProcessHeap(), 0, tmp); - } else if(flags.Format && strchr("aeEfgG", flags.Format)) { + } else if(flags.Format && strchr("aeEfFgG", flags.Format)) { char float_fmt[20], buf_a[32], *tmp = buf_a, *decimal_point; int len = flags.Precision + 10; double val = pf_args(args_ctx, pos, VT_R8, valist).get_double; @@ -571,7 +583,7 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API } } - if(flags.Format=='f') { + if(flags.Format=='f' || flags.Format=='F') { if(val>-10.0 && val<10.0) i = 1; else @@ -594,9 +606,24 @@ int FUNC_NAME(pf_printf)(FUNC_NAME(puts_clbk) pf_puts, void *puts_ctx, const API val = -val; } - sprintf(tmp, float_fmt, val); - if(toupper(flags.Format)=='E' || toupper(flags.Format)=='G') - FUNC_NAME(pf_fixup_exponent)(tmp); + if((inf || nan || ind) && !legacy_msvcrt_compat) { + static const char inf_str[] = "inf"; + static const char ind_str[] = "nan(ind)"; + static const char nan_str[] = "nan"; + if(inf) + sprintf(tmp, inf_str); + else if(ind) + sprintf(tmp, ind_str); + else + sprintf(tmp, nan_str); + if (strchr("EFG", flags.Format)) + for(i=0; tmp[i]; i++) + tmp[i] = toupper(tmp[i]); + } else { + sprintf(tmp, float_fmt, val); + if(toupper(flags.Format)=='E' || toupper(flags.Format)=='G') + FUNC_NAME(pf_fixup_exponent)(tmp, three_digit_exp); + } decimal_point = strchr(tmp, '.'); if(decimal_point) { diff --git a/dlls/msvcrt/wcs.c b/dlls/msvcrt/wcs.c index 43c6b0ce887..56625e94b82 100644 --- a/dlls/msvcrt/wcs.c +++ b/dlls/msvcrt/wcs.c @@ -724,10 +724,10 @@ int CDECL MSVCRT__stdio_common_vsprintf( unsigned __int64 options, char *str, MS struct _str_ctx_a ctx = {len, str}; int ret; - if (options & ~UCRTBASE_PRINTF_TERMINATION_MASK) + if (options & ~UCRTBASE_PRINTF_MASK) FIXME("options %s not handled\n", wine_dbgstr_longlong(options)); ret = pf_printf_a(puts_clbk_str_c99_a, - &ctx, format, locale, 0, arg_clbk_valist, NULL, &valist); + &ctx, format, locale, options & UCRTBASE_PRINTF_MASK, arg_clbk_valist, NULL, &valist); puts_clbk_str_a(&ctx, 1, &nullbyte); if(options & UCRTBASE_PRINTF_LEGACY_VSPRINTF_NULL_TERMINATION) @@ -778,11 +778,8 @@ int CDECL MSVCRT_sprintf_l(char *str, const char *format, return retval; } -/********************************************************************* - * _vsnprintf_s_l (MSVCRT.@) - */ -int CDECL MSVCRT_vsnprintf_s_l( char *str, MSVCRT_size_t sizeOfBuffer, - MSVCRT_size_t count, const char *format, +static int CDECL MSVCRT_vsnprintf_s_l_opt( char *str, MSVCRT_size_t sizeOfBuffer, + MSVCRT_size_t count, const char *format, DWORD options, MSVCRT__locale_t locale, __ms_va_list valist ) { static const char nullbyte = '\0'; @@ -796,7 +793,7 @@ int CDECL MSVCRT_vsnprintf_s_l( char *str, MSVCRT_size_t sizeOfBuffer, ctx.len = len; ctx.buf = str; - ret = pf_printf_a(puts_clbk_str_a, &ctx, format, locale, MSVCRT_PRINTF_INVOKE_INVALID_PARAM_HANDLER, + ret = pf_printf_a(puts_clbk_str_a, &ctx, format, locale, MSVCRT_PRINTF_INVOKE_INVALID_PARAM_HANDLER | options, arg_clbk_valist, NULL, &valist); puts_clbk_str_a(&ctx, 1, &nullbyte); @@ -813,6 +810,16 @@ int CDECL MSVCRT_vsnprintf_s_l( char *str, MSVCRT_size_t sizeOfBuffer, return ret; } +/********************************************************************* + * _vsnprintf_s_l (MSVCRT.@) + */ +int CDECL MSVCRT_vsnprintf_s_l( char *str, MSVCRT_size_t sizeOfBuffer, + MSVCRT_size_t count, const char *format, + MSVCRT__locale_t locale, __ms_va_list valist ) +{ + return MSVCRT_vsnprintf_s_l_opt(str, sizeOfBuffer, count, format, 0, locale, valist); +} + /********************************************************************* * _vsprintf_s_l (MSVCRT.@) */ @@ -852,7 +859,9 @@ int CDECL MSVCRT__stdio_common_vsnprintf_s( unsigned __int64 options, char *str, MSVCRT_size_t sizeOfBuffer, MSVCRT_size_t count, const char *format, MSVCRT__locale_t locale, __ms_va_list valist ) { - return MSVCRT_vsnprintf_s_l(str, sizeOfBuffer, count, format, locale, valist); + if (options & ~UCRTBASE_PRINTF_MASK) + FIXME("options %s not handled\n", wine_dbgstr_longlong(options)); + return MSVCRT_vsnprintf_s_l_opt(str, sizeOfBuffer, count, format, options & UCRTBASE_PRINTF_MASK, locale, valist); } /********************************************************************* @@ -862,7 +871,9 @@ int CDECL MSVCRT__stdio_common_vsprintf_s( unsigned __int64 options, char *str, MSVCRT_size_t count, const char *format, MSVCRT__locale_t locale, __ms_va_list valist ) { - return MSVCRT_vsnprintf_s_l(str, INT_MAX, count, format, locale, valist); + if (options & ~UCRTBASE_PRINTF_MASK) + FIXME("options %s not handled\n", wine_dbgstr_longlong(options)); + return MSVCRT_vsnprintf_s_l_opt(str, INT_MAX, count, format, options & UCRTBASE_PRINTF_MASK, locale, valist); } /********************************************************************* @@ -1183,10 +1194,10 @@ int CDECL MSVCRT__stdio_common_vswprintf( unsigned __int64 options, struct _str_ctx_w ctx = {len, str}; int ret; - if (options & ~UCRTBASE_PRINTF_TERMINATION_MASK) + if (options & ~UCRTBASE_PRINTF_MASK) FIXME("options %s not handled\n", wine_dbgstr_longlong(options)); ret = pf_printf_w(puts_clbk_str_c99_w, - &ctx, format, locale, 0, arg_clbk_valist, NULL, &valist); + &ctx, format, locale, options & UCRTBASE_PRINTF_MASK, arg_clbk_valist, NULL, &valist); puts_clbk_str_w(&ctx, 1, &nullbyte); if(options & UCRTBASE_PRINTF_LEGACY_VSPRINTF_NULL_TERMINATION) diff --git a/dlls/ucrtbase/tests/printf.c b/dlls/ucrtbase/tests/printf.c index ce47e2abd86..ae12b60d385 100644 --- a/dlls/ucrtbase/tests/printf.c +++ b/dlls/ucrtbase/tests/printf.c @@ -36,6 +36,27 @@ #define UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY (0x0008) #define UCRTBASE_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS (0x0010) +static inline float __port_infinity(void) +{ + static const unsigned __inf_bytes = 0x7f800000; + return *(const float *)&__inf_bytes; +} +#define INFINITY __port_infinity() + +static inline float __port_nan(void) +{ + static const unsigned __nan_bytes = 0x7fc00000; + return *(const float *)&__nan_bytes; +} +#define NAN __port_nan() + +static inline float __port_ind(void) +{ + static const unsigned __ind_bytes = 0xffc00000; + return *(const float *)&__ind_bytes; +} +#define IND __port_ind() + static int (__cdecl *p_vfprintf)(unsigned __int64 options, FILE *file, const char *format, void *locale, __ms_va_list valist); static int (__cdecl *p_vsprintf)(unsigned __int64 options, char *str, size_t len, const char *format, @@ -336,6 +357,72 @@ static void test_vsnprintf_s(void) ok( !strcmp(out1, buffer), "buffer wrong, got=%s\n", buffer); } +static void test_printf_legacy_wide(void) +{ + const wchar_t wide[] = {'A','B','C','D',0}; + const char narrow[] = "abcd"; + const char out[] = "abcd ABCD"; + /* The legacy wide flag doesn't affect narrow printfs, so the same + * format should behave the same both with and without the flag. */ + const char narrow_fmt[] = "%s %ls"; + /* The standard behaviour is to use the same format as for the narrow + * case, while the legacy case has got a different meaning for %s. */ + const wchar_t std_wide_fmt[] = {'%','s',' ','%','l','s',0}; + const wchar_t legacy_wide_fmt[] = {'%','h','s',' ','%','s',0}; + char buffer[20]; + wchar_t wbuffer[20]; + + vsprintf_wrapper(0, buffer, sizeof(buffer), narrow_fmt, narrow, wide); + ok(!strcmp(buffer, out), "buffer wrong, got=%s\n", buffer); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_WIDE_SPECIFIERS, buffer, sizeof(buffer), narrow_fmt, narrow, wide); + ok(!strcmp(buffer, out), "buffer wrong, got=%s\n", buffer); + + vswprintf_wrapper(0, wbuffer, sizeof(wbuffer), std_wide_fmt, narrow, wide); + WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer), NULL, NULL); + ok(!strcmp(buffer, out), "buffer wrong, got=%s\n", buffer); + vswprintf_wrapper(UCRTBASE_PRINTF_LEGACY_WIDE_SPECIFIERS, wbuffer, sizeof(wbuffer), legacy_wide_fmt, narrow, wide); + WideCharToMultiByte(CP_ACP, 0, wbuffer, -1, buffer, sizeof(buffer), NULL, NULL); + ok(!strcmp(buffer, out), "buffer wrong, got=%s\n", buffer); +} + +static void test_printf_legacy_msvcrt(void) +{ + char buf[50]; + + /* In standard mode, %F is a float format conversion, while it is a + * length modifier in legacy msvcrt mode. In legacy mode, N is also + * a length modifier. */ + vsprintf_wrapper(0, buf, sizeof(buf), "%F", 1.23); + ok(!strcmp(buf, "1.230000"), "buf = %s\n", buf); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY, buf, sizeof(buf), "%Fd %Nd", 123, 456); + ok(!strcmp(buf, "123 456"), "buf = %s\n", buf); + + vsprintf_wrapper(0, buf, sizeof(buf), "%f %F %f %e %E %g %G", INFINITY, INFINITY, -INFINITY, INFINITY, INFINITY, INFINITY, INFINITY); + ok(!strcmp(buf, "inf INF -inf inf INF inf INF"), "buf = %s\n", buf); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY, buf, sizeof(buf), "%f", INFINITY); + ok(!strcmp(buf, "1.#INF00"), "buf = %s\n", buf); + vsprintf_wrapper(0, buf, sizeof(buf), "%f %F", NAN, NAN); + ok(!strcmp(buf, "nan NAN"), "buf = %s\n", buf); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY, buf, sizeof(buf), "%f", NAN); + ok(!strcmp(buf, "1.#QNAN0"), "buf = %s\n", buf); + vsprintf_wrapper(0, buf, sizeof(buf), "%f %F", IND, IND); + ok(!strcmp(buf, "-nan(ind) -NAN(IND)"), "buf = %s\n", buf); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_MSVCRT_COMPATIBILITY, buf, sizeof(buf), "%f", IND); + ok(!strcmp(buf, "-1.#IND00"), "buf = %s\n", buf); +} + +static void test_printf_legacy_three_digit_exp(void) +{ + char buf[20]; + + vsprintf_wrapper(0, buf, sizeof(buf), "%E", 1.23); + ok(!strcmp(buf, "1.230000E+00"), "buf = %s\n", buf); + vsprintf_wrapper(UCRTBASE_PRINTF_LEGACY_THREE_DIGIT_EXPONENTS, buf, sizeof(buf), "%E", 1.23); + ok(!strcmp(buf, "1.230000E+000"), "buf = %s\n", buf); + vsprintf_wrapper(0, buf, sizeof(buf), "%E", 1.23e+123); + ok(!strcmp(buf, "1.230000E+123"), "buf = %s\n", buf); +} + START_TEST(printf) { if (!init()) return; @@ -344,4 +431,7 @@ START_TEST(printf) test_swprintf(); test_fprintf(); test_vsnprintf_s(); + test_printf_legacy_wide(); + test_printf_legacy_msvcrt(); + test_printf_legacy_three_digit_exp(); }