1
0
forked from 0ad/0ad

# Fixed string handling for Windows/Linux compatibility.

* vsnprintf2: Made compatible between GCC and MSVC - it now always
null-terminates the buffer, and returns -1 on overflow. Fixes #158.
Added tests.
 * MeshManager: Use shared_ptr.expired() instead of checking for
bad_weak_ptr exception.
 * Xeromyces: Added tests for GetXMBPath, because it does unusual things
in sscanf which MSVC's /analyze complains about.
 * ConfigDB, ScriptGlue: Replaced some asserts with return-on-failure,
to avoid invalid array accesses when continuing after the assert (as
complained about by /analyze).
 * CStr: Removed "using namespace std". Added tests for handling of
invalid UTF-8.

This was SVN commit r4625.
This commit is contained in:
Ykkrosh 2006-11-07 21:03:13 +00:00
parent 75f2f9fd5f
commit a265a441fd
18 changed files with 254 additions and 64 deletions

View File

@ -16,19 +16,11 @@ CModelDefPtr CMeshManager::GetMesh(const char *filename)
{
CStr fn(filename);
mesh_map::iterator iter = m_MeshMap.find(fn);
if (iter != m_MeshMap.end())
if (iter != m_MeshMap.end() && !iter->second.expired())
{
try
{
CModelDefPtr model (iter->second);
//LOG(MESSAGE, "mesh", "Loading mesh '%s%' (cached)...", filename);
return model;
}
// If the mesh has already been deleted, the weak_ptr -> shared_ptr
// conversion will throw bad_weak_ptr (and we need to reload the mesh)
catch (boost::bad_weak_ptr)
{
}
CModelDefPtr model (iter->second);
//LOG(MESSAGE, "mesh", "Loading mesh '%s%' (cached)...", filename);
return model;
}
try

View File

@ -1022,7 +1022,6 @@ void CGUI::ReportParseError(const char *str, ...)
va_start(argp, str);
vsnprintf2(buffer, sizeof(buffer), str, argp);
buffer[sizeof(buffer)-1] = '\0';
va_end(argp);
// Print header

View File

@ -188,5 +188,6 @@ extern bool self_test_active;
#define TS_ASSERT_OK(expr) TS_ASSERT_EQUALS((expr), INFO::OK)
#define TS_ASSERT_STR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::string(str1), std::string(str2))
#define TS_ASSERT_WSTR_EQUALS(str1, str2) TS_ASSERT_EQUALS(std::wstring(str1), std::wstring(str2))
#endif // #ifndef SELF_TEST_H__

View File

@ -27,6 +27,7 @@
#include "lib/debug.h" // ErrorReaction
#include <cmath> // see comments below about isfinite
#include <cstdarg> // needed for vsnprintf2
// some functions among the sysdep API are implemented as macros
// that redirect to the platform-dependent version. this is done where
@ -82,13 +83,12 @@
// C99 / SUSv3 emulation where needed
//-----------------------------------------------------------------------------
// vsnprintf2: handles positional parameters and %lld.
// already available on *nix, emulated on Win32.
#if OS_WIN
// vsnprintf2: doesn't quite follow the standard for vsnprintf, but works
// better across compilers:
// - handles positional parameters and %lld
// - always null-terminates the buffer
// - returns -1 on overflow (if the output string (including null) does not fit in the buffer)
extern int vsnprintf2(char* buffer, size_t count, const char* format, va_list argptr);
#else
#define vsnprintf2 vsnprintf
#endif
#if !MSC_VERSION
#define stricmp strcasecmp

View File

@ -0,0 +1,73 @@
#include "lib/lib.h"
#include "lib/self_test.h"
class TestPrintf : public CxxTest::TestSuite
{
// Split some bits into separate functions, so we can get
// a legitimate va_list to pass to vsnprintf2:
void _test_truncate(int buffer_size, char* expected_output, int expected_return, /* char* input_string */...)
{
char buf[17] = "................"; // fill with dots so null-termination is made obvious
va_list ap;
va_start(ap, expected_return);
int ret = vsnprintf2(buf, buffer_size, "%s", ap);
TS_ASSERT_STR_EQUALS(buf, expected_output);
TS_ASSERT_EQUALS(ret, expected_return);
std::string past_buffer (buf + buffer_size);
TS_ASSERT(past_buffer.find_first_not_of('.') == past_buffer.npos);
va_end(ap);
}
void _test_sprintf(char* expected_output, char* format, ...)
{
char buf[256];
va_list ap;
va_start(ap, format);
vsnprintf2(buf, sizeof(buf), format, ap);
TS_ASSERT_STR_EQUALS(buf, expected_output);
va_end(ap);
}
public:
void test_truncate()
{
_test_truncate(8, "1234", 4, "1234");
_test_truncate(8, "1234567", 7, "1234567");
_test_truncate(8, "1234567", -1, "12345678");
_test_truncate(8, "1234567", -1, "123456789");
_test_truncate(8, "1234567", -1, "123456789abcdef");
}
void test_lld()
{
i64 z = 0;
i64 n = 65536;
_test_sprintf("0", "%lld", z);
_test_sprintf("65536", "%lld", n);
_test_sprintf("4294967296", "%lld", n*n);
_test_sprintf("281474976710656", "%lld", n*n*n);
_test_sprintf("-281474976710656", "%lld", -n*n*n);
_test_sprintf("123 456 281474976710656 789", "%d %d %lld %d", 123, 456, n*n*n, 789);
}
void test_pos()
{
_test_sprintf("a b", "%1$c %2$c", 'a', 'b');
_test_sprintf("b a", "%2$c %1$c", 'a', 'b');
}
void test_pos_lld()
{
_test_sprintf("1 2 3", "%1$d %2$lld %3$d", 1, (i64)2, 3);
_test_sprintf("2 1 3", "%2$lld %1$d %3$d", 1, (i64)2, 3);
}
};

View File

@ -0,0 +1,26 @@
#include "precompiled.h"
#include <cstdio>
#include <cstdarg>
// See declaration in sysdep.h for explanation of need
int vsnprintf2(char* buffer, size_t count, const char* format, va_list argptr)
{
int ret = vsnprintf(buffer, count, format, argptr);
/*
"The glibc implementation of the functions snprintf() and vsnprintf() conforms
to the C99 standard ... since glibc version 2.1. Until glibc 2.0.6 they would
return -1 when the output was truncated."
- man printf
MSVC's _vsnprintf still returns -1, so we want this one to do the same (for
compatibility), if the output (including the terminating null) is truncated.
*/
if (ret >= (int)count)
return -1;
return ret;
}

View File

@ -9,7 +9,7 @@
/*
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 MSVC)
%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
@ -32,6 +32,12 @@
#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, // '
@ -363,8 +369,8 @@ finished_reading:
if (s->length > 256)
{
if (s->length == 0x00006c6c)
#if MSC_VERSION
newformat += "I64"; // MSVC compatibility
#if USE_I64_FORMAT
newformat += "I64";
#else
newformat += "ll";
#endif
@ -464,7 +470,11 @@ finished_reading:
int ret = _vsnprintf(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
buffer[count-1] = '\0';
if (ret == (int)count)
return -1;
return ret;
}

View File

@ -591,7 +591,7 @@ void CConsole::SetBuffer(const wchar_t* szMessage, ...)
m_iBufferPos = std::min(oldBufferPos, m_iBufferLength);
}
void CConsole::UseHistoryFile( const CStr& filename, int max_history_lines )
void CConsole::UseHistoryFile(const CStr& filename, int max_history_lines)
{
m_MaxHistoryLines = max_history_lines;
@ -599,7 +599,8 @@ void CConsole::UseHistoryFile( const CStr& filename, int max_history_lines )
LoadHistory();
}
void CConsole::ProcessBuffer(const wchar_t* szLine){
void CConsole::ProcessBuffer(const wchar_t* szLine)
{
if (szLine == NULL) return;
if (wcslen(szLine) <= 0) return;
@ -609,14 +610,15 @@ void CConsole::ProcessBuffer(const wchar_t* szLine){
SaveHistory(); // Do this each line for the moment; if a script causes
// a crash it's a useful record.
wchar_t szCommand[CONSOLE_BUFFER_SIZE];
memset(szCommand, '\0', sizeof(wchar_t) * CONSOLE_BUFFER_SIZE);
wchar_t szCommand[CONSOLE_BUFFER_SIZE] = { 0 };
std::map<std::wstring, fptr>::iterator Iter;
if (szLine[0] == '\\')
{
swscanf(szLine, L"\\%ls", szCommand);
if (swscanf(szLine, L"\\%ls", szCommand) != 1)
return;
Trim(szCommand);
ToLower(szCommand);

View File

@ -154,10 +154,8 @@ void CLogger::Log(ELogMethod method, const char* category, const char *fmt, ...)
va_list argp;
char buffer[512];
memset(buffer, 0, sizeof(buffer));
va_start(argp, fmt);
if (vsnprintf2(buffer, sizeof(buffer)-1, fmt, argp) == -1)
if (vsnprintf2(buffer, sizeof(buffer), fmt, argp) == -1)
{
// Buffer too small - ensure the string is nicely terminated
strcpy(buffer+sizeof(buffer)-4, "..."); // safe
@ -173,10 +171,8 @@ void CLogger::LogOnce(ELogMethod method, const char* category, const char *fmt,
va_list argp;
char buffer[512];
memset(buffer, 0, sizeof(buffer));
va_start(argp, fmt);
if (vsnprintf2(buffer, sizeof(buffer)-1, fmt, argp) == -1)
if (vsnprintf2(buffer, sizeof(buffer), fmt, argp) == -1)
{
// Buffer too small - ensure the string is nicely terminated
strcpy(buffer+sizeof(buffer)-4, "..."); // safe

View File

@ -38,7 +38,7 @@ CStr8 CStrW::ToUTF8() const
{
CStr8 result;
for (size_t i = 0; i < Length(); ++i)
for (size_t i = 0; i < length(); ++i)
{
unsigned short bytesToWrite;
wchar_t ch = (*this)[i];
@ -58,12 +58,14 @@ CStr8 CStrW::ToUTF8() const
case 2: *--target = ((ch | 0x80) & 0xBF); ch >>= 6;
case 1: *--target = (ch | firstByteMark[bytesToWrite]);
}
result += CStr(buf, bytesToWrite);
result += CStr8(buf, bytesToWrite);
}
return result;
}
static bool isLegalUTF8(const unsigned char *source, int length) {
static bool isLegalUTF8(const unsigned char *source, int length)
{
unsigned char a;
const unsigned char *srcptr = source+length;
@ -92,7 +94,7 @@ CStrW CStr8::FromUTF8() const
{
CStrW result;
if(empty())
if (empty())
return result;
const unsigned char* source = (const unsigned char*)&*begin();
@ -103,12 +105,12 @@ CStrW CStr8::FromUTF8() const
unsigned short extraBytesToRead = trailingBytesForUTF8[*source];
if (source + extraBytesToRead >= sourceEnd)
{
debug_warn("Invalid UTF-8 (fell off end)");
//debug_warn("Invalid UTF-8 (fell off end)");
return L"";
}
if (! isLegalUTF8(source, extraBytesToRead+1)) {
debug_warn("Invalid UTF-8 (illegal data)");
//debug_warn("Invalid UTF-8 (illegal data)");
return L"";
}
@ -134,7 +136,6 @@ CStrW CStr8::FromUTF8() const
// The following code is compiled twice, as CStrW then as CStr8:
#include "CStr.h"
using namespace std;
#include <sstream>
@ -282,7 +283,7 @@ long CStr::ReverseFind(const CStr& Str) const
// Lowercase and uppercase
CStr CStr::LowerCase() const
{
tstring NewString = *this;
std::tstring NewString = *this;
for (size_t i = 0; i < length(); i++)
NewString[i] = (tchar)_totlower((*this)[i]);
@ -291,7 +292,7 @@ CStr CStr::LowerCase() const
CStr CStr::UpperCase() const
{
tstring NewString = *this;
std::tstring NewString = *this;
for (size_t i = 0; i < length(); i++)
NewString[i] = (tchar)_totupper((*this)[i]);
@ -302,7 +303,7 @@ CStr CStr::UpperCase() const
// code duplication because return by value overhead if they were merely an alias
CStr CStr::LCase() const
{
tstring NewString = *this;
std::tstring NewString = *this;
for (size_t i = 0; i < length(); i++)
NewString[i] = (tchar)_totlower((*this)[i]);
@ -311,7 +312,7 @@ CStr CStr::LCase() const
CStr CStr::UCase() const
{
tstring NewString = *this;
std::tstring NewString = *this;
for (size_t i = 0; i < length(); i++)
NewString[i] = (tchar)_totupper((*this)[i]);

View File

@ -105,10 +105,11 @@ public:
CStrW(const CStr8 &asciiStr);
#endif
// Conversion to/from UTF-8, encoded in a CStr8. Non-ASCII characters are
// handled correctly.
// May fail, if converting from invalid UTF-8 data; the empty string will
// be returned.
// Conversion to/from UTF-8, encoded in a CStr8.
// Common non-ASCII characters are handled correctly.
// Characters outside the BMP (above 0xFFFF) are *not* handled correctly.
// FromUTF8 may fail, if converting from invalid UTF-8 data - the empty
// string will be returned.
#ifdef _UNICODE
CStr8 ToUTF8() const;
#else

View File

@ -200,7 +200,11 @@ CConfigValue *CConfigDB::GetValue(EConfigNamespace ns, const CStr& name)
CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name )
{
debug_assert(ns < CFG_LAST && ns >= 0);
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return NULL;
}
TConfigMap::iterator it = m_Map[CFG_COMMAND].find( name );
if( it != m_Map[CFG_COMMAND].end() )
@ -218,7 +222,11 @@ CConfigValueSet *CConfigDB::GetValues(EConfigNamespace ns, const CStr& name )
CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name)
{
debug_assert(ns < CFG_LAST && ns >= 0);
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return NULL;
}
CConfigValue *ret=GetValue(ns, name);
if (ret) return ret;
@ -229,7 +237,11 @@ CConfigValue *CConfigDB::CreateValue(EConfigNamespace ns, const CStr& name)
void CConfigDB::SetConfigFile(EConfigNamespace ns, bool useVFS, const CStr& path)
{
debug_assert(ns < CFG_LAST && ns >= 0);
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return;
}
m_ConfigFile[ns]=path;
m_UseVFS[ns]=useVFS;
@ -328,7 +340,11 @@ bool CConfigDB::Reload(EConfigNamespace ns)
bool CConfigDB::WriteFile(EConfigNamespace ns, bool useVFS, const CStr& path)
{
debug_assert(ns >= 0 && ns < CFG_LAST);
if (ns < 0 || ns >= CFG_LAST)
{
debug_warn("CConfigDB: Invalid ns value");
return false;
}
char realpath[PATH_MAX];
char nativepath[PATH_MAX];

View File

@ -172,7 +172,7 @@ void CXeromyces::Terminate()
// Find out write location of the XMB file corresponding to xmlFilename
void CXeromyces::getXMBPath(const char* xmlFilename, const char* xmbFilename,
void CXeromyces::GetXMBPath(const char* xmlFilename, const char* xmbFilename,
char* xmbPath)
{
// rationale:
@ -257,7 +257,7 @@ PSRETURN CXeromyces::Load(const char* filename)
xmbFilename += buf;
char xmbPath[PATH_MAX];
getXMBPath(filename, xmbFilename, xmbPath);
GetXMBPath(filename, xmbFilename, xmbPath);
// If the file exists, use it

View File

@ -19,6 +19,7 @@ ERROR_TYPE(Xeromyces, XMLParseError);
class CXeromyces : public XMBFile
{
friend class TestXeromyces;
public:
CXeromyces();
~CXeromyces();
@ -32,7 +33,7 @@ public:
private:
// Find out write location of the XMB file corresponding to xmlFilename
void getXMBPath(const char* xmlFilename, const char* xmbFilename,
static void GetXMBPath(const char* xmlFilename, const char* xmbFilename,
char* xmbPath);
bool ReadXMBFile(const char* filename);

View File

@ -0,0 +1,32 @@
#include "lib/self_test.h"
#include "ps/XML/Xeromyces.h"
#include "lib/res/file/vfs.h"
#include "lib/res/file/path.h"
#include "lib/res/file/trace.h"
class TestXeromyces : public CxxTest::TestSuite
{
public:
void test_paths()
{
file_init();
path_init();
file_set_root_dir(0, "../data");
vfs_init();
vfs_mount("", "mods/_tests", VFS_MOUNT_RECURSIVE);
vfs_set_write_target("mods/_tests");
char xmbPath[PATH_MAX];
CXeromyces::GetXMBPath("test1.xml", "test1.xmb", xmbPath);
TS_ASSERT_STR_EQUALS(xmbPath, "cache/mods/_tests/xmb/test1.xmb");
CXeromyces::GetXMBPath("a/b/test1.xml", "a/b/test1.xmb", xmbPath);
TS_ASSERT_STR_EQUALS(xmbPath, "cache/mods/_tests/xmb/a/b/test1.xmb");
vfs_shutdown();
path_reset_root_dir();
}
};

View File

@ -8,12 +8,46 @@ class TestCStr : public CxxTest::TestSuite
public:
void test_utf8_utf16_conversion()
{
const wchar_t chr_utf16[] = { 0x12, 0xff, 0x1234, 0x3456, 0x5678, 0x7890, 0x9abc, 0xbcde, 0xfffe };
const unsigned char chr_utf8[] = { 0x12, 0xc3, 0xbf, 0xe1, 0x88, 0xb4, 0xe3, 0x91, 0x96, 0xe5, 0x99, 0xb8, 0xe7, 0xa2, 0x90, 0xe9, 0xaa, 0xbc, 0xeb, 0xb3, 0x9e, 0xef, 0xbf, 0xbe };
const wchar_t chr_utf16[] = {
0x12,
0xff,
0x1234,
0x3456,
0x5678,
0x7890,
0x9abc,
0xbcde,
0xfffe
};
const unsigned char chr_utf8[] = {
0x12,
0xc3, 0xbf,
0xe1, 0x88, 0xb4,
0xe3, 0x91, 0x96,
0xe5, 0x99, 0xb8,
0xe7, 0xa2, 0x90,
0xe9, 0xaa, 0xbc,
0xeb, 0xb3, 0x9e,
0xef, 0xbf, 0xbe
};
CStrW str_utf16 (chr_utf16, sizeof(chr_utf16)/sizeof(wchar_t));
CStr8 str_utf8 = str_utf16.ToUTF8();
TS_ASSERT_EQUALS(str_utf8.length(), sizeof(chr_utf8));
TS_ASSERT_SAME_DATA(str_utf8.data(), chr_utf8, sizeof(chr_utf8));
TS_ASSERT_EQUALS(str_utf8.FromUTF8(), str_utf16);
CStrW str_utf16b = str_utf8.FromUTF8();
TS_ASSERT_EQUALS(str_utf16b, str_utf16);
}
void test_invalid_utf8()
{
const unsigned char chr_utf8_a[] = { 'a', 0xef };
const unsigned char chr_utf8_b[] = { 'b', 0xef, 0xbf };
const unsigned char chr_utf8_c[] = { 'c', 0xef, 0xbf, 0x01 };
TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_a, sizeof(chr_utf8_a)).FromUTF8(), L"");
TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_b, sizeof(chr_utf8_b)).FromUTF8(), L"");
TS_ASSERT_WSTR_EQUALS(CStr8((const char*)chr_utf8_c, sizeof(chr_utf8_c)).FromUTF8(), L"");
}
};

View File

@ -625,11 +625,12 @@ JSBool startXTimer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval
JSU_REQUIRE_PARAMS(1);
uint slot = ToPrimitive<uint>(argv[0]);
debug_assert(slot < MAX_XTIMERS);
if (slot >= MAX_XTIMERS)
return JS_FALSE;
debug_assert(xstart_times[slot] == 0);
xstart_times[slot] = xtimer_impl.get_timestamp();
return( JS_TRUE );
return JS_TRUE;
}
@ -637,13 +638,14 @@ JSBool stopXTimer(JSContext* cx, JSObject*, uint argc, jsval* argv, jsval* rval)
{
JSU_REQUIRE_PARAMS(1);
uint slot = ToPrimitive<uint>(argv[0]);
debug_assert(slot < MAX_XTIMERS);
if (slot >= MAX_XTIMERS)
return JS_FALSE;
debug_assert(xstart_times[slot] != 0);
XTimerImpl::unit dt = xtimer_impl.get_timestamp() - xstart_times[slot] - xoverhead;
xstart_times[slot] = 0;
timer_bill_client(&xclients[slot], dt);
return( JS_TRUE );
return JS_TRUE;
}

View File

@ -4,6 +4,10 @@
# define HAVE_PCH
#endif
#if defined(_MSC_VER) && _MSC_VER >= 1400
# pragma warning(disable: 6334)
#endif
#ifdef HAVE_PCH
// Exclude rarely-used stuff from Windows headers