Non sono un programmatore professionista, ma nella vita mi occupo di gestire un azienda. Da anni ho coltivato comunque la passione per la programmazione in generale, iniziando da giovanissimo con il Basic, per poi passare al Pascal ed in fine prima al C poi al C++, i quali sono diventati i linguaggi da me preferiti negli ultimi 20 anni.
Premetto che trovo stupefacenti le possibilità che aprono le libreire QT, e le semplificazioni a cui portano per sviluppare piccoli applicativi aziendali.
Qualche anno fa ho iniziato a sviluppare diversi applicativi di appoggio per l'azienda, tali applicativi gestiscono varie funzione come l'impaginazione di documenti (Che su iSeries è pessima) etc.
Dovendo accedere a un Database presente su iSeries, ho utilizzato l'iSeriesAccess per linux rilasciato da IBM, il quale comprende il driver ODBC per l'accesso al database.
Fino a quando le varie distribuzioni linux si sono appoggiate ad iodbc non ho mai avuto problemi. I problemi sono iniziati da quando le principali distibuzioni sono passate ad unixODBC.
Accedendo con qualunque applicativo QT4 ad un database su iSeries, nel momento in cui si tenta di accedere ad un campo contenente una stringa si ottiene il seguente errore:
- Codice: Seleziona tutto
gconv.c: 75: __gconv: Assertion "outbuf! = ((void *) 0) & & * outbuf! = ((void *) 0) 'failed.
Aborted
Dopo avere segnalato il BUG qua https://bugs.launchpad.net/ubuntu/+source/qt4-x11/+bug/665188 e qua https://bugreports.qt-project.org/browse/QTBUG-23675, ho deciso di iniziare a cercare la causa del problema.
Dopo mille peripezzie sono arrivato a capire dove si originava il BUG.
Come riferimento ai numeri di riga prendo in esame le librerie QT 4.7.4, anche se il problema si presenta in tutte le versioni precedenti.
Nel file src/sql/drivers/odbc/qsql_odbc.cpp dalla linea 359 si trova il seguente codice:
- Codice: Seleziona tutto
static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
QSQLLEN lengthIndicator = 0;
// NB! colSize must be a multiple of 2 for unicode enabled DBs
if (colSize <= 0) {
colSize = 256;
} else if (colSize > 65536) { // limit buffer size to 64 KB
colSize = 65536;
} else {
colSize++; // make sure there is room for more than the 0 termination
}
if(unicode) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator/sizeof(SQLTCHAR) + 1;
QVarLengthArray<SQLTCHAR> buf(colSize);
memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
(SQLPOINTER)buf.data(),
colSize*sizeof(SQLTCHAR),
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
fieldVal.clear();
break;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
// colSize-1: remove 0 termination when there is more data to fetch
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator/sizeof(SQLTCHAR);
fieldVal += fromSQLTCHAR(buf, rSize);
if ((unsigned)lengthIndicator < colSize*sizeof(SQLTCHAR)) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
} else {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
NULL,
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator + 1;
QVarLengthArray<SQLCHAR> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
(SQLPOINTER)buf.data(),
colSize,
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
fieldVal.clear();
break;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
// colSize-1: remove 0 termination when there is more data to fetch
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize);
if (lengthIndicator < (unsigned int)colSize) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
}
return fieldVal;
}
Il problema sorge nella chiamata al metodo SQLGetData(hStmt, column+1, SQL_C_TCHAR, NULL, 0, &lengthIndicator);
In particolare con l'indirizzo del buffer NULL, il quale probabilmente non viene accettato correttamente da iconv.
Questa chiamata da quel poco che ho analizzato credo che serva a capire quale lunghezza effettiva ha il campo, in quanto nelle righe successive viene adattato colSize al valore lengthIndicator impostato dal metodo. Successivamente viene creato un buffer adatto a contenere la stringa e viene copiato il campo in tale buffer.
Modificando il codice come segue :
- Codice: Seleziona tutto
static QString qGetStringData(SQLHANDLE hStmt, int column, int colSize, bool unicode = false)
{
QString fieldVal;
SQLRETURN r = SQL_ERROR;
QSQLLEN lengthIndicator = 0;
// NB! colSize must be a multiple of 2 for unicode enabled DBs
if (colSize <= 0) {
colSize = 256;
} else if (colSize > 65536) { // limit buffer size to 64 KB
colSize = 65536;
} else {
colSize++; // make sure there is room for more than the 0 termination
}
if(unicode) {
QVarLengthArray<SQLTCHAR> buftmp(colSize);
memset(buftmp.data(), 0, colSize*sizeof(SQLTCHAR));
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
(SQLPOINTER)buftmp.data(),
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator/sizeof(SQLTCHAR) + 1;
QVarLengthArray<SQLTCHAR> buf(colSize);
memset(buf.data(), 0, colSize*sizeof(SQLTCHAR));
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_TCHAR,
(SQLPOINTER)buf.data(),
colSize*sizeof(SQLTCHAR),
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
fieldVal.clear();
break;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
// colSize-1: remove 0 termination when there is more data to fetch
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator/sizeof(SQLTCHAR);
fieldVal += fromSQLTCHAR(buf, rSize);
if ((unsigned)lengthIndicator < colSize*sizeof(SQLTCHAR)) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
} else {
QVarLengthArray<SQLTCHAR> buftmp(colSize);
memset(buftmp.data(), 0, colSize*sizeof(SQLCHAR));
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
(SQLPOINTER)buftmp.data(),
0,
&lengthIndicator);
if ((r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) && lengthIndicator > 0)
colSize = lengthIndicator + 1;
QVarLengthArray<SQLCHAR> buf(colSize);
while (true) {
r = SQLGetData(hStmt,
column+1,
SQL_C_CHAR,
(SQLPOINTER)buf.data(),
colSize,
&lengthIndicator);
if (r == SQL_SUCCESS || r == SQL_SUCCESS_WITH_INFO) {
if (lengthIndicator == SQL_NULL_DATA || lengthIndicator == SQL_NO_TOTAL) {
fieldVal.clear();
break;
}
// if SQL_SUCCESS_WITH_INFO is returned, indicating that
// more data can be fetched, the length indicator does NOT
// contain the number of bytes returned - it contains the
// total number of bytes that CAN be fetched
// colSize-1: remove 0 termination when there is more data to fetch
int rSize = (r == SQL_SUCCESS_WITH_INFO) ? colSize : lengthIndicator;
fieldVal += QString::fromUtf8((const char *)buf.constData(), rSize);
if (lengthIndicator < (unsigned int)colSize) {
// workaround for Drivermanagers that don't return SQL_NO_DATA
break;
}
} else if (r == SQL_NO_DATA) {
break;
} else {
qWarning() << "qGetStringData: Error while fetching data (" << qWarnODBCHandle(SQL_HANDLE_STMT, hStmt) << ')';
fieldVal.clear();
break;
}
}
}
return fieldVal;
}
Ricompilando il plugin odbc e testando gli applicativi da me creati o applicativi come "tora" non creato da me. Tutto funziona regolarmente.
Ora però mi sorge un dubbio!
Quello che ho fatto io è creare un buffer temporaneo e sostituire il suo indirizzo all'indirizzo NULL che da problemi.
Lo ho creato delle dimensioni del colSize moltiplicate per la dimensione del carattere, ma ho paura che in tal modo si possa incappare in un buffer overflow, in quanto se tale metodo era stato utilizzato per misurare l'effettiva dimensione prima di creare il buffer era proprio perchè colSize moltiplicato per la lunghezza del carattere avrebbe potuto essere minore di lengthIndicator.
Con questa correzione per ora funziona, ma non credo sia una soluzione poco pulita.
Chiedo a voi se avete idee migliori e se c'è un modo per contattare chi mantiene il file src/sql/drivers/odbc/qsql_odbc.cpp per segalare il problema. Purtroppo credo che altrimenti tramite i bug report possa passare molto tempo prima che venga realizzata un patch.
