Hi,
I have almost run out of hair to pull out, so I'd like a bit of help
on this bug.
I am facing memory corruption in my code that uses connector/C++ 1.05:
it looks like some memory is written to after it has been freed. I
suspect some automatically invoked destructor, but I am now running
out of idea. Here are the details:
If I delete the sql::ResultSet returned by
sql::PreparedStatement::executeQuery(), then memory is corrupted in
the chain of called destructors. I managed to trace it to this point
where I see a free block bein written over:
#0 0x059fa6b9 in sql::mysql::MySQL_ResultBind::~MySQL_ResultBind at
mysql_resultbind.cpp:119
#1 0x059f1aa7 in
std::auto_ptr<sql::mysql::MySQL_ResultBind>::~auto_ptr at memory:259
#2 0x059f8433 in
sql::mysql::MySQL_Prepared_ResultSet::~MySQL_Prepared_ResultSet at
mysql_ps_resultset.cpp:104
#3 0x05a2b4d7 in reducer::execute at RKTable4D.records.cpp:94
#4 0x05a2b5ce in reducer::execute at RKTable4D.records.cpp:81
#5 0x05a2a03a in RK::TableMirror4D::getRemoteStamp at
RKTable4D.records.cpp:116
This happens at the end of this routine:
MySQL_ResultBind::~MySQL_ResultBind()
{
if (rbind.get()) {
for (unsigned int i = 0; i < num_fields; ++i) {
delete[] (char *) rbind[i].buffer;
}
}
}
Here is a screen shot of my debugger showing the freed object
partially written over:
http://emberapp.com/jdmuys/images/xcode
Note: after being freed, blocks are filled by malloc() with a 0x55
pattern.
You can see that the beginning of the block at address 0x03c8d880 is
run over: length, is_null and buffer were written to. The step before,
they were 0x55.
For completeness, here are the last 2 steps of this block's
malloc_history:
ALLOC 0x3c8d880-0x3c8d8bf [size=64]: thread_b02e9000 [...]
RK::TableMirror4D::getRemoteStamp(sql::Connection*, long) |
reducer::execute(sql::Connection*, long) | reducer::execute
(sql::Connection*) | sql::mysql::MySQL_Prepared_Statement::executeQuery
() | sql::mysql::MySQL_Prepared_ResultSet::MySQL_Prepared_ResultSet
(st_mysql_stmt*, sql::mysql::MySQL_ResultBind*,
sql::ResultSet::enum_type, sql::mysql::MySQL_Prepared_Statement*,
sql::mysql::util::my_shared_ptr<sql::mysql::MySQL_DebugLogger>*) |
sql::mysql::MySQL_ResultBind::bindResult() | operator new[](unsigned
long) | operator new(unsigned long) | malloc | malloc_zone_malloc
----
FREE 0x3c8d880-0x3c8d8bf [size=64]: thread_b02e9000 [...]
RK::TableMirror4D::getRemoteStamp(sql::Connection*, long) |
reducer::execute(sql::Connection*, long) | reducer::execute
(sql::Connection*) |
sql::mysql::MySQL_Prepared_ResultSet::~MySQL_Prepared_ResultSet() |
std::auto_ptr<sql::mysql::MySQL_ResultBind>::~auto_ptr() |
sql::mysql::MySQL_ResultBind::~MySQL_ResultBind() |
sql::mysql::util::my_array_guard<st_mysql_bind>::~my_array_guard() |
free
In summary, I am seeing Connector/C++ write into a block it's just
freed!
The code that demonstrates this is the following snippet:
class reducer {
public:
sql::Connection *_mySQL; // the connection used to prepare the
statement
TableMirror4D *_mirror; // the mirror that owns this query
std::string _queryAsString; // the cached text of the query
sql::PreparedStatement *_preparedStmt; // the prepared statement;
sql::ResultSet *_results;
reducer(TableMirror4D *mirror) {
_mirror = mirror;
_queryAsString = "";
_mySQL = NULL;
_preparedStmt = NULL;
_results = NULL;
}
virtual string *getQueryAsString(char* comScheme = NULL)
{
if (this->_queryAsString == "" ) {
stringstream sql;
sql.str("");
sql << "SELECT `STAMP` FROM `Wish List`";
sql << " WHERE `RKid`=? and RKDeleted=0";
this->_queryAsString = sql.str();
}
return &this->_queryAsString;
}
virtual void prepare(sql::Connection *conn)
{
if (conn != this->_mySQL) {
if (this->_preparedStmt)
delete this->_preparedStmt;
string *q = this->getQueryAsString();
this->_preparedStmt = conn->prepareStatement(*q);
this->_mySQL = conn;
}
}
virtual long execute(sql::Connection *conn, long condValue)
{
this->_preparedStmt->setInt(1, condValue);
long result = this->execute(conn);
return result;
}
virtual long execute(sql::Connection *conn)
{
this->_results = this->_preparedStmt->executeQuery();
long result = 0;
long rowCount = this->_results->rowsCount();
if (rowCount > 0) {
this->_results->first();
result = this->_results->getUInt(1);
}
delete this->_results; // memory corruption goes away if I remove
this line
return result;
}
};
long TableMirror4D::getRemoteStamp(sql::Connection *conn, long rkid)
{
long stamp = 0;
static reducer *select = NULL;
if (select == NULL)
select = new reducer(this);
zoneCheck();
select->prepare(conn);
zoneCheck();
stamp = select->execute(conn, rkid);
}
Note the "static" is important: the memory corruption never happens
through the first use of the reducer instance.
Note also that *no* exception are involved in this code.
I am suppose the bug is mine, but I really can't see what is wrong in
my code above.
So could it be an obscure bug in connector/C++? Under which conditions
could client code lead Connector/C++ to write into a block it has just
freed?
Thanks for any help.
Jean-Denis