From d3d735c04f31efc01e273289bbcfe991eba90510 Mon Sep 17 00:00:00 2001 From: Daniel Gibson Date: Mon, 10 Jan 2022 04:06:54 +0100 Subject: [PATCH] Add D3_(v)snprintfC99() for C99-compatible implementations These are now used by idStr::(v)snPrintf(), and in the future can be used if a (v)snprintf() that's guaranteed not to call common->Warning() or similar is needed (e.g. used during early startup) --- idlib/Str.cpp | 84 ++++++++++++++++++++++++++++++++++++++++----------- idlib/Str.h | 11 +++++++ 2 files changed, 77 insertions(+), 18 deletions(-) diff --git a/idlib/Str.cpp b/idlib/Str.cpp index 9bd6b1b..e730233 100644 --- a/idlib/Str.cpp +++ b/idlib/Str.cpp @@ -30,6 +30,7 @@ If you have questions concerning this license or the applicable additional terms #include "idlib/math/Vector.h" #include "idlib/Heap.h" #include "framework/Common.h" +#include #include "idlib/Str.h" @@ -1513,21 +1514,25 @@ idStr::snPrintf ================ */ int idStr::snPrintf( char *dest, int size, const char *fmt, ...) { - int len; va_list argptr; - char buffer[32000]; // big, but small enough to fit in PPC stack - + int len; va_start( argptr, fmt ); - len = vsprintf( buffer, fmt, argptr ); + len = D3_vsnprintfC99(dest, size, fmt, argptr); va_end( argptr ); - if ( len >= sizeof( buffer ) ) { + if ( len >= 32000 ) { + // TODO: Previously this function used a 32000 byte buffer to write into + // with vsprintf(), and raised this error if that was overflowed + // (more likely that'd have lead to a crash..). + // Technically we don't have that restriction anymore, so I'm unsure + // if this error should really still be raised to preserve + // the old intended behavior, maybe for compat with mod DLLs using + // the old version of the function or something? idLib::common->Error( "idStr::snPrintf: overflowed buffer" ); } if ( len >= size ) { idLib::common->Warning( "idStr::snPrintf: overflow of %i in %i\n", len, size ); len = size; } - idStr::Copynz( dest, buffer, size ); return len; } @@ -1550,18 +1555,7 @@ or returns -1 on failure or if the buffer would be overflowed. ============ */ int idStr::vsnPrintf( char *dest, int size, const char *fmt, va_list argptr ) { - int ret; - -#ifdef _WIN32 -#undef _vsnprintf - ret = _vsnprintf( dest, size-1, fmt, argptr ); -#define _vsnprintf use_idStr_vsnPrintf -#else -#undef vsnprintf - ret = vsnprintf( dest, size, fmt, argptr ); -#define vsnprintf use_idStr_vsnPrintf -#endif - dest[size-1] = '\0'; + int ret = D3_vsnprintfC99(dest, size, fmt, argptr); if ( ret < 0 || ret >= size ) { return -1; } @@ -1790,3 +1784,57 @@ idStr idStr::FormatNumber( int number ) { return string; } + +// behaves like C99's vsnprintf() by returning the amount of bytes that +// *would* have been written into a big enough buffer, even if that's > size +// unlike idStr::vsnPrintf() which returns -1 in that case +int D3_vsnprintfC99(char *dst, size_t size, const char *format, va_list ap) +{ + // before VS2015, it didn't have a standards-conforming (v)snprintf()-implementation + // same might be true for other windows compilers if they use old CRT versions, like MinGW does +#if defined(_WIN32) && (!defined(_MSC_VER) || _MSC_VER < 1900) + #undef _vsnprintf + // based on DG_vsnprintf() from https://github.com/DanielGibson/Snippets/blob/master/DG_misc.h + int ret = -1; + if(dst != NULL && size > 0) + { +#if defined(_MSC_VER) && _MSC_VER >= 1400 + // I think MSVC2005 introduced _vsnprintf_s(). + // this shuts up _vsnprintf() security/deprecation warnings. + ret = _vsnprintf_s(dst, size, _TRUNCATE, format, ap); +#else + ret = _vsnprintf(dst, size, format, ap); + dst[size-1] = '\0'; // ensure '\0'-termination +#endif + } + + if(ret == -1) + { + // _vsnprintf() returns -1 if the output is truncated + // it's also -1 if dst or size were NULL/0, so the user didn't want to write + // we want to return the number of characters that would've been + // needed, though.. fortunately _vscprintf() calculates that. + ret = _vscprintf(format, ap); + } + return ret; + #define _vsnprintf use_idStr_vsnPrintf +#else // other operating systems and VisualC++ >= 2015 should have a proper vsnprintf() + #undef vsnprintf + return vsnprintf(dst, size, format, ap); + #define vsnprintf use_idStr_vsnPrintf +#endif +} + +// behaves like C99's snprintf() by returning the amount of bytes that +// *would* have been written into a big enough buffer, even if that's > size +// unlike idStr::snPrintf() which returns the written bytes in that case +// and also calls common->Warning() in case of overflows +int D3_snprintfC99(char *dst, size_t size, const char *format, ...) +{ + int ret = 0; + va_list argptr; + va_start( argptr, format ); + ret = D3_vsnprintfC99(dst, size, format, argptr); + va_end( argptr ); + return ret; +} diff --git a/idlib/Str.h b/idlib/Str.h index 5dfabe9..a44bab2 100644 --- a/idlib/Str.h +++ b/idlib/Str.h @@ -1068,4 +1068,15 @@ ID_INLINE int idStr::DynamicMemoryUsed() const { return ( data == baseBuffer ) ? 0 : alloced; } +// behaves like C99's snprintf() by returning the amount of bytes that +// *would* have been written into a big enough buffer, even if that's > size +// unlike idStr::snPrintf() which returns the written bytes in that case +// and also calls common->Warning() in case of overflows +int D3_snprintfC99(char *dst, size_t size, const char *format, ...) id_attribute((format(printf,3,4))); + +// behaves like C99's vsnprintf() by returning the amount of bytes that +// *would* have been written into a big enough buffer, even if that's > size +// unlike idStr::vsnPrintf() which returns -1 in that case +int D3_vsnprintfC99(char *dst, size_t size, const char *format, va_list ap); + #endif /* !__STR_H__ */