Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 57 additions & 8 deletions MainUnicode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,21 @@
extern FILE *logFile;
using namespace OdbcJdbcLibrary;

#ifndef _WINDOWS
// SQLWCHAR-aware length (in SQLWCHAR units), safe on Linux where
// sizeof(wchar_t) != sizeof(SQLWCHAR). Do NOT use wcslen() on SQLWCHAR
// data on Linux — it reads two SQLWCHARs per wchar_t and runs off the end.
static size_t sqlwcharLen( const SQLWCHAR *s )
{
size_t n = 0;
if ( !s )
return 0;
while ( s[n] )
++n;
return n;
}
#endif

#ifdef _WINDOWS
extern UINT codePage; // from Main.cpp
#endif
Expand Down Expand Up @@ -85,7 +100,7 @@ class ConvertingString
if ( length == SQL_NTS )
lengthString = 0;
else if ( retCountOfBytes )
lengthString = length / sizeof(wchar_t);
lengthString = length / sizeof(SQLWCHAR);
else
lengthString = length;
}
Expand Down Expand Up @@ -135,13 +150,30 @@ class ConvertingString
if ( len > 0 )
len--;
#else
len = mbstowcs( (wchar_t*)unicodeString, (const char*)byteString, lengthString );
// SQLWCHAR is 2 bytes on Linux (unixODBC defines it as unsigned short),
// but wchar_t is 4 bytes, so mbstowcs((wchar_t*)unicodeString, ...)
// both corrupts the output and risks overflowing the caller's buffer.
// Widen byte-by-byte into SQLWCHAR units, matching what unixODBC's
// ansi_to_unicode_copy() does internally. This is correct for the
// ASCII-only error/state strings that reach this code path; non-ASCII
// input will be handled by the broader ConvertingString rewrite tracked
// in issue #287 (Tier 9.1).
{
const SQLCHAR *src = byteString;
size_t i = 0;
while ( i < (size_t)lengthString && src[i] != 0 )
{
unicodeString[i] = (SQLWCHAR)( src[i] & 0xFF );
++i;
}
len = i;
}
#endif
}

if ( len > 0 )
{
*(LPWSTR)(unicodeString + len) = L'\0';
unicodeString[len] = 0;

if ( realLength )
{
Expand Down Expand Up @@ -170,12 +202,18 @@ class ConvertingString
wchar_t saveWC;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
wchar_t saveWC;
SQLWCHAR saveWC;


if ( length == SQL_NTS )
#ifdef _WINDOWS
length = (int)wcslen( (const wchar_t*)wcString );
else if ( wcString[length] != L'\0' )
#else
length = (int)sqlwcharLen( wcString );
#endif
else if ( wcString[length] != 0 )
{
ptEndWC = (wchar_t*)&wcString[length];
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ptEndWC = (wchar_t*)&wcString[length];
ptEndWC = wcString + length;

saveWC = *ptEndWC;
*ptEndWC = L'\0';
// Write a SQLWCHAR-sized NUL so we don't overrun the input by 2 bytes
// on Linux (wchar_t is 4 bytes there).
wcString[length] = 0;
}

if ( connection )
Expand All @@ -185,7 +223,10 @@ class ConvertingString
#ifdef _WINDOWS
bytesNeeded = WideCharToMultiByte( codePage, (DWORD)0, wcString, length, NULL, (int)0, NULL, NULL );
#else
bytesNeeded = wcstombs( NULL, (const wchar_t*)wcString, length );
// See the symmetric comment in the destructor above: wcstombs assumes
// wchar_t-sized input, which corrupts SQLWCHAR data on Linux. The
// byte-narrowing loop below produces exactly `length` output bytes.
bytesNeeded = (size_t)length;
#endif
}

Expand All @@ -198,7 +239,15 @@ class ConvertingString
#ifdef _WINDOWS
bytesNeeded = WideCharToMultiByte( codePage, 0, wcString, length, (LPSTR)byteString, (int)bytesNeeded, NULL, NULL );
#else
bytesNeeded = wcstombs( (char *)byteString, (const wchar_t*)wcString, bytesNeeded );
{
size_t i = 0;
while ( i < (size_t)length && wcString[i] != 0 )
{
byteString[i] = (SQLCHAR)( wcString[i] & 0xFF );
++i;
}
bytesNeeded = i;
}
#endif
}

Expand All @@ -220,7 +269,7 @@ class ConvertingString
if ( lengthString )
{
byteString = new SQLCHAR[ lengthString + 2 ];
memset(byteString, 0, lengthString + 2);
memset( byteString, 0, lengthString + 2 );
}
else
byteString = NULL;
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ add_executable(firebird_odbc_tests
test_catalogfunctions.cpp
test_server_version.cpp
test_scrollable_cursor.cpp
test_wide_errors.cpp

# Category C — all tests SKIP'd (features not yet on upstream master)
test_null_handles.cpp
Expand Down
32 changes: 32 additions & 0 deletions tests/test_helpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,38 @@ inline std::string GetSqlState(SQLSMALLINT handleType, SQLHANDLE handle) {
return "";
}

// Convert an ASCII C-string to a null-terminated SQLWCHAR vector. Cannot use
// L"..." literals — sizeof(wchar_t) != sizeof(SQLWCHAR) on Linux.
inline std::vector<SQLWCHAR> ToSqlWchar(const char* s) {
std::vector<SQLWCHAR> out;
while (*s) {
out.push_back((SQLWCHAR)(unsigned char)*s++);
}
out.push_back(0);
return out;
}

// Convert a null-terminated SQLWCHAR string to a narrow std::string (low byte
// only, so ASCII round-trips correctly; non-ASCII is lossy but these helpers
// are for tests, not production data).
inline std::string FromSqlWchar(const SQLWCHAR* s) {
std::string out;
if (!s) return out;
while (*s) {
out.push_back((char)(*s & 0xFF));
++s;
}
return out;
}

// Length of a null-terminated SQLWCHAR string, in SQLWCHAR units.
inline size_t SqlWcharLen(const SQLWCHAR* s) {
size_t n = 0;
if (!s) return 0;
while (s[n]) ++n;
return n;
}

// Base test fixture: ODBC environment + connection + auto-cleanup
class OdbcConnectedTest : public ::testing::Test {
public:
Expand Down
Loading
Loading