1
0
forked from 0ad/0ad

Remove sys_vswprintf.

The implementation on Windows was gross. The only user was CLogger,
which no longer uses it.

Also fix vswprintf_s to handle truncated output correctly (by returning
"") on Linux, now that CLogger is no longer relying on the buggy
behaviour.

This was SVN commit r16190.
This commit is contained in:
Ykkrosh 2015-01-22 20:39:28 +00:00
parent afa492f473
commit ef2a358f87
7 changed files with 35 additions and 680 deletions

View File

@ -237,7 +237,7 @@ int tvsprintf_s(tchar* dst, size_t max_dst_chars, const tchar* fmt, va_list ap)
}
const int ret = tvsnprintf(dst, max_dst_chars, fmt, ap);
if(ret >= int(max_dst_chars)) // not enough space
if(ret < 0 || ret >= int(max_dst_chars)) // not enough space
{
dst[0] = '\0';
return -1;

View File

@ -1,39 +0,0 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "precompiled.h"
#include <cstdio>
#include <cstdarg>
// See declaration in sysdep.h for explanation of need
int sys_vswprintf(wchar_t* buffer, size_t count, const wchar_t* format, va_list argptr)
{
int ret = vswprintf(buffer, count, format, argptr);
// Guarantee the buffer is null terminated on error
if (ret < 0 && count > 0)
buffer[count-1] = '\0';
return ret;
}

View File

@ -1,526 +0,0 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/*
* implementation of sys_vswprintf.
*/
#include "precompiled.h"
#if ARCH_IA32
/*
See http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html
for the specification (apparently an extension to ISO C) that was used
when creating this code.
*/
/*
Added features (compared to MSVC's printf):
Positional parameters (e.g. "%1$d", where '1' means '1st in the parameter list')
%lld (equivalent to %I64d in MSVC7.1, though it's supported natively by MSVC8)
Unsupported features (compared to a perfect implementation):
' <-- because MSVC doesn't support it
%S }
%C } because they're unnecessary and can cause confusion
* <-- probably works in some situations, but don't expect it to
*m$ <-- positional precision parameters, because they're not worthwhile
portability <-- just use GCC
efficiency <-- nothing in the spec says it should be as fast as possible
(the code could be made a lot faster if speed mattered more
than non-fixed size buffers)
*/
#ifndef _UNICODE
#define _UNICODE
#endif
#include <tchar.h>
#ifdef _UNICODE
# define tstring wstring
# define tstringstream wstringstream
#else
# define tstring string
# define tstringstream stringstream
#endif
#include <string>
#include <vector>
#include <stdio.h>
#include <sstream>
#include <stdarg.h>
#if MSC_VERSION < 1400
# define USE_I64_FORMAT 1
#else
# define USE_I64_FORMAT 0
#endif
enum
{
SPECFLAG_THOUSANDS = 1, // '
SPECFLAG_LEFTJUSTIFIED = 2, // -
SPECFLAG_SIGNED = 4, // +
SPECFLAG_SPACEPREFIX = 8, // <space>
SPECFLAG_ALTERNATE = 16, // #
SPECFLAG_ZEROPAD = 32 // 0
};
struct FormatChunk
{
virtual ~FormatChunk() { }
virtual int ChunkType() = 0; // 0 = FormatSpecification, 1 = FormatString
};
struct FormatVariable : public FormatChunk
{
int ChunkType() { return 0; }
int position; // undefined if the format includes no positional elements
char flags; // ['-+ #0]
int width; // -1 for *, 0 for unspecified
int precision; // -1 for *
int length; // "\0\0\0l", "\0\0hh", etc
char type; // 'd', etc
};
struct FormatString : public FormatChunk
{
int ChunkType() { return 1; }
FormatString(std::tstring t) : text(t) {}
std::tstring text;
};
int get_flag(TCHAR c)
{
switch (c)
{
case _T('\''): return SPECFLAG_THOUSANDS;
case _T('-'): return SPECFLAG_LEFTJUSTIFIED;
case _T('+'): return SPECFLAG_SIGNED;
case _T(' '): return SPECFLAG_SPACEPREFIX;
case _T('#'): return SPECFLAG_ALTERNATE;
case _T('0'): return SPECFLAG_ZEROPAD;
}
return 0;
}
std::tstring flags_to_string(char flags)
{
std::tstring s;
const char* c = "\'-+ #0";
for (int i=0; i<6; ++i)
if (flags & (1<<i))
s += c[i];
return s;
}
template<typename T>
std::tstring to_string(T n)
{
std::tstring s;
std::tstringstream str;
str << n;
str >> s;
return s;
}
int is_lengthmod(TCHAR c)
{
return
c == _T('h') ||
c == _T('l') ||
c == _T('j') ||
c == _T('z') ||
c == _T('t') ||
c == _T('L');
}
// _T2('l') == 'll'
#define _T2(a) ((a<<8) | a)
int type_size(TCHAR type, int length)
{
switch (type)
{
case 'd':
case 'i':
switch (length)
{
case _T ('l'): return sizeof(long);
case _T2('l'): return sizeof(long long);
case _T ('h'): return sizeof(short);
case _T2('h'): return sizeof(char);
default: return sizeof(int);
}
case 'o':
case 'u':
case 'x':
case 'X': return sizeof(unsigned);
case 'f':
case 'F':
case 'e':
case 'E':
case 'g':
case 'G':
case 'a':
case 'A':
if (length == _T('L'))
return sizeof(long double);
else
return sizeof(double);
case 'c':
// "%lc" is a wide character, passed as a wint_t (ushort)
if (length == _T('l'))
return sizeof(wint_t);
// "%c" is an int, apparently
else
return sizeof(int);
case 's':
if (length == _T('l'))
return sizeof(wchar_t*);
else
return sizeof(char*);
case 'p':
return sizeof(void*);
case 'n':
return sizeof(int*);
}
return 0;
}
int sys_vswprintf(TCHAR* buffer, size_t count, const TCHAR* format, va_list argptr)
{
// To help quickly detect incorrect 'count' values, fill the buffer with 0s
memset(buffer, 0, count*sizeof(TCHAR));
/*
Format 'variable' specifications are (in pseudo-Perl regexp syntax):
% (\d+$)? ['-+ #0]? (* | \d+)? (. (* | \d*) )? (hh|h|l|ll|j|z|t|L)? [diouxXfFeEgGaAcspnCS%]
position flags width precision length type
*/
/**** Parse the format string into constant/variable chunks ****/
std::vector<FormatChunk*> specs;
std::tstring stringchunk;
TCHAR chr;
#define readchar(x) if ((x = *format++) == '\0') { delete s; goto finished_reading; }
while ((chr = *format++) != '\0')
{
if (chr == _T('%'))
{
// Handle %% correctly
if (*format == _T('%'))
{
stringchunk += _T('%');
continue;
}
// End the current string and start a new spec chunk
if (stringchunk.length())
{
specs.push_back(new FormatString(stringchunk));
stringchunk = _T("");
}
FormatVariable *s = new FormatVariable;
s->position = -1;
s->flags = 0;
s->width = 0;
s->precision = 0;
s->length = 0;
s->type = 0;
// Read either the position or the width
int number = 0;
while (1)
{
readchar(chr);
// Read flags (but not if it's a 0 appearing after other digits)
if (!number && get_flag(chr))
s->flags = (char)(s->flags|get_flag(chr));
// Read decimal numbers (position or width)
else if (isdigit(chr))
number = number*10 + (chr-'0');
// If we've reached a $, 'number' was the position,
// so remember it and start getting the width
else if (chr == _T('$'))
{
s->position = number;
number = 0;
}
// End of the number section
else
{
// Remember the width
s->width = number;
// Start looking for a precision
if (chr == _T('.'))
{
// Found a precision: read the digits
number = 0;
while (1)
{
readchar(chr);
if (isdigit(chr))
number = number*10 + (chr-'0');
else
{
s->precision = number;
break;
}
}
}
// Finished dealing with any precision.
// Now check for length and type codes.
if (chr == _T('I'))
{
DEBUG_WARN_ERR(ERR::LOGIC); // MSVC-style \"%I64\" is not allowed!
}
if (is_lengthmod(chr))
{
s->length = chr;
// Check for ll and hh
if (chr == _T('l') || chr == _T('h'))
{
if (*format == chr)
{
s->length |= (chr << 8);
++format;
}
}
readchar(chr);
}
s->type = (char)chr;
specs.push_back(s);
break;
}
}
}
else
{
stringchunk += chr;
}
}
#undef readchar
finished_reading:
if (stringchunk.length())
{
specs.push_back(new FormatString(stringchunk));
stringchunk = _T("");
}
/**** Build a new format string (to pass to the system printf) ****/
std::tstring newformat;
std::vector<int> varsizes; // stores the size of each variable type, to allow stack twiddling
typedef std::vector<FormatChunk*>::iterator ChunkIt;
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
{
if ((*it)->ChunkType() == 0)
{
FormatVariable* s = static_cast<FormatVariable*>(*it);
if (s->position > 0)
{
// Grow if necessary
if (s->position >= (int)varsizes.size())
varsizes.resize(s->position+1, -1);
// Store the size of the current type
varsizes[s->position] = type_size(s->type, s->length);
}
newformat += _T("%");
if (s->flags)
newformat += flags_to_string(s->flags);
if (s->width == -1)
newformat += '*';
else if (s->width)
newformat += to_string(s->width);
if (s->precision)
{
newformat += '.';
if (s->precision == -1)
newformat += '*';
else
newformat += to_string(s->precision);
}
if (s->length)
{
if (s->length > 256)
{
if (s->length == 0x00006c6c)
#if USE_I64_FORMAT
newformat += "I64";
#else
newformat += _T("ll");
#endif
else if (s->length == 0x00006868)
newformat += _T("hh");
}
else
{
newformat += (char) s->length;
}
}
newformat += s->type;
}
else
{
FormatString* s = static_cast<FormatString*>(*it);
newformat += s->text;
}
}
/*
varargs on x86:
All types are padded to 4 bytes, so size-in-stack == _INTSIZEOF(type).
No special alignment is required.
first+_INTSIZEOF(first) == first item in stack
Keep adding _INTSIZEOF(item) to get the next item
*/
// Because of those dangerous assumptions about varargs:
#if !ARCH_IA32
#error SLIGHTLY FATAL ERROR: Only x86 is supported!
#endif
// Highly efficient buffer to store the rearranged copy of the stack
std::string newstack;
std::vector< std::pair<char*, char*> > stackitems;
va_list arglist = argptr;
//va_start(arglist, format);
const u8* newstackptr;
if (varsizes.size())
{
for (size_t i = 1; i < varsizes.size(); ++i)
{
if (varsizes[i] <= 0)
{
DEBUG_WARN_ERR(ERR::LOGIC); // Invalid variable type somewhere - make sure all variable things are positional and defined
return -1;
}
// Based on _INTSIZEOF in stdarg.h:
// (Warning - slightly non-portable. But we use gcc's default printf
// when portability matters.)
#define INTSIZE(n) ( (n + sizeof(int) - 1) & ~(sizeof(int) - 1) )
size_t size = INTSIZE(varsizes[i]);
stackitems.push_back( std::pair<char*, char*>( arglist, arglist+size ));
arglist += size;
}
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
{
FormatChunk* chunk = *it;
if (chunk->ChunkType() == 0)
{
FormatVariable* s = static_cast<FormatVariable*>(chunk);
if (s->position <= 0)
{
DEBUG_WARN_ERR(ERR::LOGIC); // Invalid use of positional elements - make sure all variable things are positional and defined
return -1;
}
newstack += std::string( stackitems[s->position-1].first, stackitems[s->position-1].second );
}
}
newstackptr = (const u8*)newstack.c_str();
}
else
{
newstackptr = (const u8*)arglist;
}
for (ChunkIt it = specs.begin(); it != specs.end(); ++it)
if ((*it)->ChunkType() == 0)
delete static_cast<FormatVariable*>(*it);
else
delete static_cast<FormatString*>(*it);
int ret = _vsntprintf(buffer, count, newformat.c_str(), (va_list)newstackptr);
// For consistency with GCC's vsnprintf, make sure the buffer is null-terminated
// and return an error if that truncates the output
if(count > 0)
buffer[count-1] = '\0';
if (ret == (int)count)
return -1;
return ret;
}
#endif

View File

@ -30,8 +30,6 @@
#include "lib/debug.h" // ErrorReactionInternal
#include "lib/os_path.h"
#include <cstdarg> // needed for sys_vswprintf
//
// output
@ -80,15 +78,6 @@ LIB_API bool sys_IsDebuggerPresent();
**/
LIB_API std::wstring sys_WideFromArgv(const char* argv_i);
/**
* sys_vswprintf: doesn't quite follow the standard for vswprintf, but works
* better across compilers:
* - handles positional parameters and %lld
* - always null-terminates the buffer, if count > 0
* - returns -1 on overflow (if the output string (including null) does not fit in the buffer)
**/
extern int sys_vswprintf(wchar_t* buffer, size_t count, const wchar_t* format, va_list argptr);
/**
* describe the current OS error state.
*

View File

@ -1,99 +0,0 @@
/* Copyright (c) 2010 Wildfire Games
*
* Permission is hereby granted, free of charge, to any person obtaining
* a copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to
* the following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#include "lib/self_test.h"
#include "lib/sysdep/sysdep.h"
class TestPrintf : public CxxTest::TestSuite
{
// Split some bits into separate functions, so we can get
// a legitimate va_list to pass to sys_vswprintf:
void _test_truncate(int buffer_size, const wchar_t* expected_output, int expected_return, /* wchar_t* input_string */...)
{
wchar_t buf[17] = L"................"; // fill with dots so null-termination is made obvious
va_list ap;
va_start(ap, expected_return);
int ret = sys_vswprintf(buf, buffer_size, L"%ls", ap);
TS_ASSERT_WSTR_EQUALS(buf, expected_output);
TS_ASSERT_EQUALS(ret, expected_return);
std::wstring past_buffer(buf + buffer_size);
TS_ASSERT(past_buffer.find_first_not_of('.') == past_buffer.npos);
va_end(ap);
}
void _test_sprintf(const wchar_t* expected_output, const wchar_t* format, ...)
{
wchar_t buf[256];
va_list ap;
va_start(ap, format);
sys_vswprintf(buf, ARRAY_SIZE(buf), format, ap);
TS_ASSERT_WSTR_EQUALS(buf, expected_output);
va_end(ap);
}
public:
void test_truncate()
{
_test_truncate(0, L"................", -1, L"1234");
_test_truncate(1, L"", -1, L"1234");
_test_truncate(8, L"1234", 4, L"1234");
_test_truncate(8, L"1234567", 7, L"1234567");
_test_truncate(8, L"1234567", -1, L"12345678");
_test_truncate(8, L"1234567", -1, L"123456789");
_test_truncate(8, L"1234567", -1, L"123456789abcdef");
}
void test_lld()
{
i64 z = 0;
i64 n = 65536;
_test_sprintf(L"0", L"%lld", z);
_test_sprintf(L"65536", L"%lld", n);
_test_sprintf(L"4294967296", L"%lld", n*n);
_test_sprintf(L"281474976710656", L"%lld", n*n*n);
_test_sprintf(L"-281474976710656", L"%lld", -n*n*n);
_test_sprintf(L"123 456 281474976710656 789", L"%d %d %lld %d", 123, 456, n*n*n, 789);
}
void test_pos()
{
_test_sprintf(L"a b", L"%1$c %2$c", 'a', 'b');
_test_sprintf(L"b a", L"%2$c %1$c", 'a', 'b');
}
void test_pos_lld()
{
_test_sprintf(L"1 2 3", L"%1$d %2$lld %3$d", 1, (i64)2, 3);
_test_sprintf(L"2 1 3", L"%2$lld %1$d %3$d", 1, (i64)2, 3);
}
};

View File

@ -81,6 +81,7 @@ class TestString_s : public CxxTest::TestSuite
const char* const s1;
const char* const s5;
const char* const s10;
const wchar_t* const ws10;
char d1[1];
char d2[2];
@ -88,6 +89,7 @@ class TestString_s : public CxxTest::TestSuite
char d5[5];
char d6[6];
char d10[10];
wchar_t wd10[10];
char d11[11];
char no_null[7];
@ -157,7 +159,7 @@ class TestString_s : public CxxTest::TestSuite
public:
TestString_s()
: s0(""), s1("a"), s5("abcde"), s10("abcdefghij")
: s0(""), s1("a"), s5("abcde"), s10("abcdefghij"), ws10(L"abcdefghij")
{
const char no_null_tmp[] = { 'n','o','_','n','u','l','l'};
memcpy(no_null, no_null_tmp, sizeof(no_null));
@ -294,13 +296,29 @@ public:
static void TEST_PRINTF(char* dst, size_t max_dst_chars, const char* dst_val,
int expected_ret, const char* expected_dst, const char* fmt, ...)
{
if (dst) strcpy(dst, dst_val);
if (dst)
strcpy(dst, dst_val);
va_list ap;
va_start(ap, fmt);
int ret = vsprintf_s(dst, max_dst_chars, fmt, ap);
va_end(ap);
TS_ASSERT_EQUALS(ret, expected_ret);
if (dst) TS_ASSERT_STR_EQUALS(dst, expected_dst);
if (dst)
TS_ASSERT_STR_EQUALS(dst, expected_dst);
}
static void TEST_WPRINTF(wchar_t* dst, size_t max_dst_chars, const wchar_t* dst_val,
int expected_ret, const wchar_t* expected_dst, const wchar_t* fmt, ...)
{
if (dst)
wcscpy(dst, dst_val);
va_list ap;
va_start(ap, fmt);
int ret = vswprintf_s(dst, max_dst_chars, fmt, ap);
va_end(ap);
TS_ASSERT_EQUALS(ret, expected_ret);
if (dst)
TS_ASSERT_WSTR_EQUALS(dst, expected_dst);
}
void test_printf()
@ -315,4 +333,17 @@ public:
TEST_PRINTF(NULL,0, NULL, -1, "", "%d", 1234);
TEST_PRINTF(d10,10, s10, -1, "abcdefghij", NULL);
}
void test_wprintf()
{
TEST_WPRINTF(wd10,10, ws10, 4, L"1234", L"%d", 1234);
TEST_WPRINTF(wd10,5, ws10, 4, L"1234", L"%d", 1234);
SuppressErrors suppress;
TEST_WPRINTF(wd10,4, ws10, -1, L"", L"%d", 1234);
TEST_WPRINTF(wd10,3, ws10, -1, L"", L"%d", 1234);
TEST_WPRINTF(wd10,0, ws10, -1, L"abcdefghij", L"%d", 1234);
TEST_WPRINTF(NULL,0, NULL, -1, L"", L"%d", 1234);
TEST_WPRINTF(wd10,10, ws10, -1, L"abcdefghij", NULL);
}
};

View File

@ -25,7 +25,6 @@
#include "lib/ogl.h"
#include "lib/timer.h"
#include "lib/utf8.h"
#include "lib/sysdep/sysdep.h"
#include "ps/Profile.h"
#include "renderer/Renderer.h"