|
| 1 | +// tests/test_wide_errors.cpp — Widechar ODBC call tests |
| 2 | +// |
| 3 | +// Drives the fix for the Linux widechar conversion bugs in MainUnicode.cpp. |
| 4 | +// Covers both ConvertingString constructor scenarios: |
| 5 | +// |
| 6 | +// (a) Output path used by SQLGetDiagRecW / SQLErrorW |
| 7 | +// ConvertingString(length, sqlState) -> exercised by |
| 8 | +// GetDiagRecW_* and ErrorW_* tests below. |
| 9 | +// |
| 10 | +// (b) Input path used by SQLExecDirectW / SQLPrepareW |
| 11 | +// ConvertingString(connection, wcString, length) + |
| 12 | +// convUnicodeToString -> exercised by ExecDirectW_* / |
| 13 | +// PrepareW_* tests below. |
| 14 | +// |
| 15 | +// On Linux the current driver mis-handles both paths — the widechar |
| 16 | +// read-back is truncated to one SQLWCHAR, and a small output buffer can |
| 17 | +// smash the caller's stack. These tests are therefore SKIP'd on Linux |
| 18 | +// with a pointer to the follow-up Unicode-fix PR. Remove the skips once |
| 19 | +// that PR lands. |
| 20 | + |
| 21 | +#include "test_helpers.h" |
| 22 | +#include <cstring> |
| 23 | +#include <string> |
| 24 | +#include <vector> |
| 25 | + |
| 26 | +#ifndef _WIN32 |
| 27 | +// Shared skip body — all tests in this file depend on the same Linux |
| 28 | +// widechar bug. Keep the message pointing at issue #287 Tier 1b so |
| 29 | +// grepping finds every affected test at once. |
| 30 | +#define SKIP_ON_LINUX_WIDECHAR() \ |
| 31 | + do { \ |
| 32 | + GTEST_SKIP() << "Widechar conversion paths broken on Linux — " \ |
| 33 | + "see issue #287 Tier 1b. Un-skip once the " \ |
| 34 | + "Unicode-fix PR lands."; \ |
| 35 | + } while (0) |
| 36 | +#else |
| 37 | +#define SKIP_ON_LINUX_WIDECHAR() do {} while (0) |
| 38 | +#endif |
| 39 | + |
| 40 | +class WideErrorsTest : public OdbcConnectedTest {}; |
| 41 | + |
| 42 | +// ============================================================================ |
| 43 | +// (a) Output path — SQLGetDiagRecW on an error |
| 44 | +// ============================================================================ |
| 45 | + |
| 46 | +// Force a parse error, then read the SQLSTATE back via the widechar variant |
| 47 | +// with a tight 12-byte (6-SQLWCHAR) output buffer. This is the exact shape of |
| 48 | +// the ConvertingString(12, sqlState) construction inside SQLGetDiagRecW. |
| 49 | +TEST_F(WideErrorsTest, GetDiagRecW_SqlState) { |
| 50 | + SKIP_ON_LINUX_WIDECHAR(); |
| 51 | + |
| 52 | + // Trigger an error (column doesn't exist) |
| 53 | + SQLRETURN ret = SQLExecDirect(hStmt, |
| 54 | + (SQLCHAR*)"SELECT doesnotexist FROM RDB$DATABASE", SQL_NTS); |
| 55 | + ASSERT_FALSE(SQL_SUCCEEDED(ret)); |
| 56 | + |
| 57 | + SQLWCHAR sqlState[6] = {}; // 12 bytes, fits "HY000\0" |
| 58 | + SQLINTEGER nativeError = 0; |
| 59 | + SQLWCHAR messageBuf[SQL_MAX_MESSAGE_LENGTH] = {}; |
| 60 | + SQLSMALLINT messageLen = 0; |
| 61 | + |
| 62 | + ret = SQLGetDiagRecW(SQL_HANDLE_STMT, hStmt, 1, |
| 63 | + sqlState, &nativeError, |
| 64 | + messageBuf, SQL_MAX_MESSAGE_LENGTH, &messageLen); |
| 65 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)) |
| 66 | + << "SQLGetDiagRecW failed: " |
| 67 | + << GetOdbcError(SQL_HANDLE_STMT, hStmt); |
| 68 | + |
| 69 | + // SQLSTATE is exactly 5 characters. |
| 70 | + EXPECT_EQ(SqlWcharLen(sqlState), 5u) |
| 71 | + << "State decoded as: '" << FromSqlWchar(sqlState) << "'"; |
| 72 | + |
| 73 | + // Firebird returns 42S22 (column not found) or 42000 or HY000. |
| 74 | + std::string state = FromSqlWchar(sqlState); |
| 75 | + EXPECT_TRUE(state == "42S22" || state == "42000" || state == "HY000") |
| 76 | + << "Unexpected SQLSTATE: '" << state << "'"; |
| 77 | +} |
| 78 | + |
| 79 | +// Same as above but checks the message buffer, which uses the other |
| 80 | +// ConvertingString size (bufferLength can be large). This exercises the |
| 81 | +// widechar path with a reasonable output buffer. |
| 82 | +TEST_F(WideErrorsTest, GetDiagRecW_Message) { |
| 83 | + SKIP_ON_LINUX_WIDECHAR(); |
| 84 | + |
| 85 | + SQLRETURN ret = SQLExecDirect(hStmt, |
| 86 | + (SQLCHAR*)"SELECT doesnotexist FROM RDB$DATABASE", SQL_NTS); |
| 87 | + ASSERT_FALSE(SQL_SUCCEEDED(ret)); |
| 88 | + |
| 89 | + SQLWCHAR sqlState[6] = {}; |
| 90 | + SQLINTEGER nativeError = 0; |
| 91 | + SQLWCHAR messageBuf[512] = {}; |
| 92 | + SQLSMALLINT messageLen = 0; |
| 93 | + |
| 94 | + ret = SQLGetDiagRecW(SQL_HANDLE_STMT, hStmt, 1, |
| 95 | + sqlState, &nativeError, |
| 96 | + messageBuf, 512, &messageLen); |
| 97 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 98 | + |
| 99 | + // Message must be non-empty and readable as ASCII. |
| 100 | + std::string msg = FromSqlWchar(messageBuf); |
| 101 | + EXPECT_FALSE(msg.empty()) << "Error message was empty"; |
| 102 | + |
| 103 | + // And the driver must report the length correctly (in SQLWCHAR units |
| 104 | + // according to the spec). |
| 105 | + EXPECT_GT(messageLen, 0); |
| 106 | +} |
| 107 | + |
| 108 | +// Deliberately tight output buffer — the driver must not overrun it. |
| 109 | +// With sqlState sized at 6 SQLWCHARs (12 bytes) this is exactly the shape |
| 110 | +// that originally triggered the heap-buffer-overflow / stack-smash pair. |
| 111 | +TEST_F(WideErrorsTest, GetDiagRecW_SmallBuffer) { |
| 112 | + SKIP_ON_LINUX_WIDECHAR(); |
| 113 | + |
| 114 | + SQLRETURN ret = SQLExecDirect(hStmt, |
| 115 | + (SQLCHAR*)"SELECT doesnotexist FROM RDB$DATABASE", SQL_NTS); |
| 116 | + ASSERT_FALSE(SQL_SUCCEEDED(ret)); |
| 117 | + |
| 118 | + // Guard bytes either side; any write outside sqlState[] will trip them. |
| 119 | + SQLWCHAR guardBefore[4]; |
| 120 | + SQLWCHAR sqlState[6] = {}; |
| 121 | + SQLWCHAR guardAfter[4]; |
| 122 | + for (int i = 0; i < 4; ++i) { |
| 123 | + guardBefore[i] = (SQLWCHAR)0xBEEF; |
| 124 | + guardAfter[i] = (SQLWCHAR)0xBEEF; |
| 125 | + } |
| 126 | + |
| 127 | + SQLINTEGER nativeError = 0; |
| 128 | + SQLWCHAR messageBuf[16] = {}; // intentionally tiny |
| 129 | + SQLSMALLINT messageLen = 0; |
| 130 | + |
| 131 | + ret = SQLGetDiagRecW(SQL_HANDLE_STMT, hStmt, 1, |
| 132 | + sqlState, &nativeError, |
| 133 | + messageBuf, 16, &messageLen); |
| 134 | + ASSERT_TRUE(SQL_SUCCEEDED(ret) || ret == SQL_SUCCESS_WITH_INFO); |
| 135 | + |
| 136 | + // Guards must be untouched. |
| 137 | + for (int i = 0; i < 4; ++i) { |
| 138 | + EXPECT_EQ(guardBefore[i], (SQLWCHAR)0xBEEF) << "guardBefore[" << i << "]"; |
| 139 | + EXPECT_EQ(guardAfter[i], (SQLWCHAR)0xBEEF) << "guardAfter[" << i << "]"; |
| 140 | + } |
| 141 | + |
| 142 | + // State still decodes to 5 characters. |
| 143 | + EXPECT_EQ(SqlWcharLen(sqlState), 5u) |
| 144 | + << "State decoded as: '" << FromSqlWchar(sqlState) << "'"; |
| 145 | +} |
| 146 | + |
| 147 | +// Legacy ODBC 2.x SQLErrorW — same underlying ConvertingString(12, sqlState) |
| 148 | +// shape. Kept so the fix also covers that call site. |
| 149 | +TEST_F(WideErrorsTest, ErrorW_SqlState) { |
| 150 | + SKIP_ON_LINUX_WIDECHAR(); |
| 151 | + |
| 152 | + SQLRETURN ret = SQLExecDirect(hStmt, |
| 153 | + (SQLCHAR*)"SELECT doesnotexist FROM RDB$DATABASE", SQL_NTS); |
| 154 | + ASSERT_FALSE(SQL_SUCCEEDED(ret)); |
| 155 | + |
| 156 | + SQLWCHAR sqlState[6] = {}; |
| 157 | + SQLINTEGER nativeError = 0; |
| 158 | + SQLWCHAR messageBuf[512] = {}; |
| 159 | + SQLSMALLINT messageLen = 0; |
| 160 | + |
| 161 | + ret = SQLErrorW(hEnv, hDbc, hStmt, |
| 162 | + sqlState, &nativeError, |
| 163 | + messageBuf, 512, &messageLen); |
| 164 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 165 | + |
| 166 | + EXPECT_EQ(SqlWcharLen(sqlState), 5u) |
| 167 | + << "State decoded as: '" << FromSqlWchar(sqlState) << "'"; |
| 168 | +} |
| 169 | + |
| 170 | +// ============================================================================ |
| 171 | +// (b) Input path — SQLExecDirectW / SQLPrepareW |
| 172 | +// ============================================================================ |
| 173 | + |
| 174 | +class WideExecTest : public OdbcConnectedTest {}; |
| 175 | + |
| 176 | +// Pass an ASCII query as SQLWCHAR with SQL_NTS — exercises the convUnicodeToString |
| 177 | +// path's sqlwcharLen-equivalent branch (currently wcslen on a SQLWCHAR pointer, |
| 178 | +// which is wrong on Linux). |
| 179 | +TEST_F(WideExecTest, ExecDirectW_Ascii_Nts) { |
| 180 | + SKIP_ON_LINUX_WIDECHAR(); |
| 181 | + |
| 182 | + auto query = ToSqlWchar("SELECT 1 FROM RDB$DATABASE"); |
| 183 | + SQLRETURN ret = SQLExecDirectW(hStmt, query.data(), SQL_NTS); |
| 184 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)) |
| 185 | + << "SQLExecDirectW failed: " |
| 186 | + << GetOdbcError(SQL_HANDLE_STMT, hStmt); |
| 187 | + |
| 188 | + SQLINTEGER val = 0; |
| 189 | + SQLLEN ind = 0; |
| 190 | + SQLBindCol(hStmt, 1, SQL_C_SLONG, &val, 0, &ind); |
| 191 | + ret = SQLFetch(hStmt); |
| 192 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 193 | + EXPECT_EQ(val, 1); |
| 194 | +} |
| 195 | + |
| 196 | +// Same but with an explicit SQLWCHAR-unit length — exercises the other branch |
| 197 | +// of convUnicodeToString where we temporarily NUL-terminate input. |
| 198 | +TEST_F(WideExecTest, ExecDirectW_Ascii_ExplicitLength) { |
| 199 | + SKIP_ON_LINUX_WIDECHAR(); |
| 200 | + |
| 201 | + auto query = ToSqlWchar("SELECT 1 FROM RDB$DATABASE"); |
| 202 | + // Length is in characters (SQLWCHAR units), per the spec. |
| 203 | + SQLINTEGER len = (SQLINTEGER)(query.size() - 1); // exclude trailing NUL |
| 204 | + |
| 205 | + SQLRETURN ret = SQLExecDirectW(hStmt, query.data(), len); |
| 206 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)) |
| 207 | + << "SQLExecDirectW(len=" << len << ") failed: " |
| 208 | + << GetOdbcError(SQL_HANDLE_STMT, hStmt); |
| 209 | + |
| 210 | + SQLINTEGER val = 0; |
| 211 | + SQLLEN ind = 0; |
| 212 | + SQLBindCol(hStmt, 1, SQL_C_SLONG, &val, 0, &ind); |
| 213 | + ret = SQLFetch(hStmt); |
| 214 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 215 | + EXPECT_EQ(val, 1); |
| 216 | +} |
| 217 | + |
| 218 | +// SQLPrepareW twin — same input path as ExecDirectW. |
| 219 | +TEST_F(WideExecTest, PrepareW_Ascii_Nts) { |
| 220 | + SKIP_ON_LINUX_WIDECHAR(); |
| 221 | + |
| 222 | + auto query = ToSqlWchar("SELECT 1 FROM RDB$DATABASE"); |
| 223 | + SQLRETURN ret = SQLPrepareW(hStmt, query.data(), SQL_NTS); |
| 224 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)) |
| 225 | + << "SQLPrepareW failed: " |
| 226 | + << GetOdbcError(SQL_HANDLE_STMT, hStmt); |
| 227 | + |
| 228 | + ret = SQLExecute(hStmt); |
| 229 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 230 | + |
| 231 | + SQLINTEGER val = 0; |
| 232 | + SQLLEN ind = 0; |
| 233 | + SQLBindCol(hStmt, 1, SQL_C_SLONG, &val, 0, &ind); |
| 234 | + ret = SQLFetch(hStmt); |
| 235 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 236 | + EXPECT_EQ(val, 1); |
| 237 | +} |
| 238 | + |
| 239 | +// A query long enough that the narrow byteString allocation has to grow. |
| 240 | +// If the length accounting is wrong in SQLWCHAR vs wchar_t units, this is |
| 241 | +// where truncation/overflow bugs surface. |
| 242 | +TEST_F(WideExecTest, ExecDirectW_Ascii_LongQuery) { |
| 243 | + SKIP_ON_LINUX_WIDECHAR(); |
| 244 | + |
| 245 | + // 200+ byte query so the byteString buffer is a meaningful size. |
| 246 | + std::string q = "SELECT "; |
| 247 | + for (int i = 0; i < 40; ++i) q += "1+"; |
| 248 | + q += "0 FROM RDB$DATABASE"; |
| 249 | + |
| 250 | + auto query = ToSqlWchar(q.c_str()); |
| 251 | + SQLRETURN ret = SQLExecDirectW(hStmt, query.data(), SQL_NTS); |
| 252 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)) |
| 253 | + << "SQLExecDirectW(long) failed: " |
| 254 | + << GetOdbcError(SQL_HANDLE_STMT, hStmt); |
| 255 | + |
| 256 | + ret = SQLFetch(hStmt); |
| 257 | + ASSERT_TRUE(SQL_SUCCEEDED(ret)); |
| 258 | +} |
0 commit comments