Kaydet (Commit) dc5af706 authored tarafından Gregory P. Smith's avatar Gregory P. Smith

SF patch / bug #967763

Fix memory leaks revealed by valgrind and ensuing code inspection.

In the existing test suite valgrind revealed two memory leaks (DB_get
and DBC_set_range).  Code inspection revealed that there were many other
potential similar leaks (many on odd code error paths such as passing
something other than a DBTxn object for a txn= parameter or in the face
of an out of memory error).  The most common case that would cause a
leak was when using recno or queue format databases with integer keys,
sometimes only with an exception exit.
üst c2b151c6
...@@ -133,6 +133,13 @@ class SimpleRecnoTestCase(unittest.TestCase): ...@@ -133,6 +133,13 @@ class SimpleRecnoTestCase(unittest.TestCase):
if verbose: if verbose:
print rec print rec
# test that non-existant key lookups work (and that
# DBC_set_range doesn't have a memleak under valgrind)
rec = c.set_range(999999)
assert rec == None
if verbose:
print rec
c.close() c.close()
d.close() d.close()
...@@ -177,6 +184,8 @@ class SimpleRecnoTestCase(unittest.TestCase): ...@@ -177,6 +184,8 @@ class SimpleRecnoTestCase(unittest.TestCase):
""" """
source = os.path.join(os.path.dirname(sys.argv[0]), source = os.path.join(os.path.dirname(sys.argv[0]),
'db_home/test_recno.txt') 'db_home/test_recno.txt')
if not os.path.isdir('db_home'):
os.mkdir('db_home')
f = open(source, 'w') # create the file f = open(source, 'w') # create the file
f.close() f.close()
......
...@@ -297,7 +297,7 @@ staticforward PyTypeObject DB_Type, DBCursor_Type, DBEnv_Type, DBTxn_Type, DBLoc ...@@ -297,7 +297,7 @@ staticforward PyTypeObject DB_Type, DBCursor_Type, DBEnv_Type, DBTxn_Type, DBLoc
#define CLEAR_DBT(dbt) (memset(&(dbt), 0, sizeof(dbt))) #define CLEAR_DBT(dbt) (memset(&(dbt), 0, sizeof(dbt)))
#define FREE_DBT(dbt) if ((dbt.flags & (DB_DBT_MALLOC|DB_DBT_REALLOC)) && \ #define FREE_DBT(dbt) if ((dbt.flags & (DB_DBT_MALLOC|DB_DBT_REALLOC)) && \
dbt.data != NULL) { free(dbt.data); } dbt.data != NULL) { free(dbt.data); dbt.data = NULL; }
static int makeDBError(int err); static int makeDBError(int err);
...@@ -330,7 +330,7 @@ static int make_dbt(PyObject* obj, DBT* dbt) ...@@ -330,7 +330,7 @@ static int make_dbt(PyObject* obj, DBT* dbt)
} }
else if (!PyArg_Parse(obj, "s#", &dbt->data, &dbt->size)) { else if (!PyArg_Parse(obj, "s#", &dbt->data, &dbt->size)) {
PyErr_SetString(PyExc_TypeError, PyErr_SetString(PyExc_TypeError,
"Key and Data values must be of type string or None."); "Data values must be of type string or None.");
return 0; return 0;
} }
return 1; return 1;
...@@ -340,7 +340,7 @@ static int make_dbt(PyObject* obj, DBT* dbt) ...@@ -340,7 +340,7 @@ static int make_dbt(PyObject* obj, DBT* dbt)
/* Recno and Queue DBs can have integer keys. This function figures out /* Recno and Queue DBs can have integer keys. This function figures out
what's been given, verifies that it's allowed, and then makes the DBT. what's been given, verifies that it's allowed, and then makes the DBT.
Caller should call FREE_DBT(key) when done. */ Caller MUST call FREE_DBT(key) when done. */
static int static int
make_key_dbt(DBObject* self, PyObject* keyobj, DBT* key, int* pflags) make_key_dbt(DBObject* self, PyObject* keyobj, DBT* key, int* pflags)
{ {
...@@ -1298,11 +1298,15 @@ DB_delete(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1298,11 +1298,15 @@ DB_delete(DBObject* self, PyObject* args, PyObject* kwargs)
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, NULL)) if (!make_key_dbt(self, keyobj, &key, NULL))
return NULL; return NULL;
if (!checkTxnObj(txnobj, &txn)) if (!checkTxnObj(txnobj, &txn)) {
FREE_DBT(key);
return NULL; return NULL;
}
if (-1 == _DB_delete(self, txn, &key, 0)) if (-1 == _DB_delete(self, txn, &key, 0)) {
FREE_DBT(key);
return NULL; return NULL;
}
FREE_DBT(key); FREE_DBT(key);
RETURN_NONE(); RETURN_NONE();
...@@ -1348,16 +1352,20 @@ DB_get(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1348,16 +1352,20 @@ DB_get(DBObject* self, PyObject* args, PyObject* kwargs)
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, &flags)) if (!make_key_dbt(self, keyobj, &key, &flags))
return NULL; return NULL;
if (!checkTxnObj(txnobj, &txn)) if (!checkTxnObj(txnobj, &txn)) {
FREE_DBT(key);
return NULL; return NULL;
}
CLEAR_DBT(data); CLEAR_DBT(data);
if (CHECK_DBFLAG(self, DB_THREAD)) { if (CHECK_DBFLAG(self, DB_THREAD)) {
/* Tell BerkeleyDB to malloc the return value (thread safe) */ /* Tell BerkeleyDB to malloc the return value (thread safe) */
data.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC;
} }
if (!add_partial_dbt(&data, dlen, doff)) if (!add_partial_dbt(&data, dlen, doff)) {
FREE_DBT(key);
return NULL; return NULL;
}
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->db->get(self->db, txn, &key, &data, flags); err = self->db->get(self->db, txn, &key, &data, flags);
...@@ -1379,9 +1387,9 @@ DB_get(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1379,9 +1387,9 @@ DB_get(DBObject* self, PyObject* args, PyObject* kwargs)
data.size); data.size);
else /* return just the data */ else /* return just the data */
retval = PyString_FromStringAndSize((char*)data.data, data.size); retval = PyString_FromStringAndSize((char*)data.data, data.size);
FREE_DBT(key);
FREE_DBT(data); FREE_DBT(data);
} }
FREE_DBT(key);
RETURN_IF_ERR(); RETURN_IF_ERR();
return retval; return retval;
...@@ -1406,8 +1414,10 @@ DB_get_size(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1406,8 +1414,10 @@ DB_get_size(DBObject* self, PyObject* args, PyObject* kwargs)
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, &flags)) if (!make_key_dbt(self, keyobj, &key, &flags))
return NULL; return NULL;
if (!checkTxnObj(txnobj, &txn)) if (!checkTxnObj(txnobj, &txn)) {
FREE_DBT(key);
return NULL; return NULL;
}
CLEAR_DBT(data); CLEAR_DBT(data);
/* We don't allocate any memory, forcing a ENOMEM error and thus /* We don't allocate any memory, forcing a ENOMEM error and thus
...@@ -1449,10 +1459,12 @@ DB_get_both(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1449,10 +1459,12 @@ DB_get_both(DBObject* self, PyObject* args, PyObject* kwargs)
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, NULL)) if (!make_key_dbt(self, keyobj, &key, NULL))
return NULL; return NULL;
if (!make_dbt(dataobj, &data)) if ( !make_dbt(dataobj, &data) ||
return NULL; !checkTxnObj(txnobj, &txn) )
if (!checkTxnObj(txnobj, &txn)) {
FREE_DBT(key);
return NULL; return NULL;
}
flags |= DB_GET_BOTH; flags |= DB_GET_BOTH;
...@@ -1719,10 +1731,15 @@ DB_put(DBObject* self, PyObject* args, PyObject* kwargs) ...@@ -1719,10 +1731,15 @@ DB_put(DBObject* self, PyObject* args, PyObject* kwargs)
return NULL; return NULL;
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, NULL)) return NULL; if (!make_key_dbt(self, keyobj, &key, NULL))
if (!make_dbt(dataobj, &data)) return NULL; return NULL;
if (!add_partial_dbt(&data, dlen, doff)) return NULL; if ( !make_dbt(dataobj, &data) ||
if (!checkTxnObj(txnobj, &txn)) return NULL; !add_partial_dbt(&data, dlen, doff) ||
!checkTxnObj(txnobj, &txn) )
{
FREE_DBT(key);
return NULL;
}
if (-1 == _DB_put(self, txn, &key, &data, flags)) { if (-1 == _DB_put(self, txn, &key, &data, flags)) {
FREE_DBT(key); FREE_DBT(key);
...@@ -2390,8 +2407,10 @@ DB_has_key(DBObject* self, PyObject* args) ...@@ -2390,8 +2407,10 @@ DB_has_key(DBObject* self, PyObject* args)
CHECK_DB_NOT_CLOSED(self); CHECK_DB_NOT_CLOSED(self);
if (!make_key_dbt(self, keyobj, &key, NULL)) if (!make_key_dbt(self, keyobj, &key, NULL))
return NULL; return NULL;
if (!checkTxnObj(txnobj, &txn)) if (!checkTxnObj(txnobj, &txn)) {
FREE_DBT(key);
return NULL; return NULL;
}
/* This causes ENOMEM to be returned when the db has the key because /* This causes ENOMEM to be returned when the db has the key because
it has a record but can't allocate a buffer for the data. This saves it has a record but can't allocate a buffer for the data. This saves
...@@ -2692,21 +2711,24 @@ DBC_get(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -2692,21 +2711,24 @@ DBC_get(DBCursorObject* self, PyObject* args, PyObject *kwargs)
if (keyobj && !make_key_dbt(self->mydb, keyobj, &key, NULL)) if (keyobj && !make_key_dbt(self->mydb, keyobj, &key, NULL))
return NULL; return NULL;
if (dataobj && !make_dbt(dataobj, &data)) if ( (dataobj && !make_dbt(dataobj, &data)) ||
return NULL; (!add_partial_dbt(&data, dlen, doff)) )
if (!add_partial_dbt(&data, dlen, doff)) {
FREE_DBT(key);
return NULL; return NULL;
}
if (CHECK_DBFLAG(self->mydb, DB_THREAD)) { if (CHECK_DBFLAG(self->mydb, DB_THREAD)) {
data.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC;
key.flags = DB_DBT_MALLOC; if (!(key.flags & DB_DBT_REALLOC)) {
key.flags |= DB_DBT_MALLOC;
}
} }
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags); err = self->dbc->c_get(self->dbc, &key, &data, flags);
MYDB_END_ALLOW_THREADS; MYDB_END_ALLOW_THREADS;
if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.getReturnsNone) { if ((err == DB_NOTFOUND) && self->mydb->moduleFlags.getReturnsNone) {
Py_INCREF(Py_None); Py_INCREF(Py_None);
retval = Py_None; retval = Py_None;
...@@ -2731,9 +2753,9 @@ DBC_get(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -2731,9 +2753,9 @@ DBC_get(DBCursorObject* self, PyObject* args, PyObject *kwargs)
data.data, data.size); data.data, data.size);
break; break;
} }
FREE_DBT(key);
FREE_DBT(data); FREE_DBT(data);
} }
FREE_DBT(key);
return retval; return retval;
} }
...@@ -2810,9 +2832,12 @@ DBC_put(DBCursorObject* self, PyObject* args, PyObject* kwargs) ...@@ -2810,9 +2832,12 @@ DBC_put(DBCursorObject* self, PyObject* args, PyObject* kwargs)
if (!make_key_dbt(self->mydb, keyobj, &key, NULL)) if (!make_key_dbt(self->mydb, keyobj, &key, NULL))
return NULL; return NULL;
if (!make_dbt(dataobj, &data)) if (!make_dbt(dataobj, &data) ||
!add_partial_dbt(&data, dlen, doff) )
{
FREE_DBT(key);
return NULL; return NULL;
if (!add_partial_dbt(&data, dlen, doff)) return NULL; }
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_put(self->dbc, &key, &data, flags); err = self->dbc->c_put(self->dbc, &key, &data, flags);
...@@ -2848,8 +2873,10 @@ DBC_set(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -2848,8 +2873,10 @@ DBC_set(DBCursorObject* self, PyObject* args, PyObject *kwargs)
/* Tell BerkeleyDB to malloc the return value (thread safe) */ /* Tell BerkeleyDB to malloc the return value (thread safe) */
data.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC;
} }
if (!add_partial_dbt(&data, dlen, doff)) if (!add_partial_dbt(&data, dlen, doff)) {
FREE_DBT(key);
return NULL; return NULL;
}
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET); err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET);
...@@ -2878,9 +2905,9 @@ DBC_set(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -2878,9 +2905,9 @@ DBC_set(DBCursorObject* self, PyObject* args, PyObject *kwargs)
data.data, data.size); data.data, data.size);
break; break;
} }
FREE_DBT(key);
FREE_DBT(data); FREE_DBT(data);
} }
FREE_DBT(key);
return retval; return retval;
} }
...@@ -2906,13 +2933,18 @@ DBC_set_range(DBCursorObject* self, PyObject* args, PyObject* kwargs) ...@@ -2906,13 +2933,18 @@ DBC_set_range(DBCursorObject* self, PyObject* args, PyObject* kwargs)
return NULL; return NULL;
CLEAR_DBT(data); CLEAR_DBT(data);
if (!add_partial_dbt(&data, dlen, doff)) {
FREE_DBT(key);
return NULL;
}
if (CHECK_DBFLAG(self->mydb, DB_THREAD)) { if (CHECK_DBFLAG(self->mydb, DB_THREAD)) {
/* Tell BerkeleyDB to malloc the return value (thread safe) */ /* Tell BerkeleyDB to malloc the return value (thread safe) */
data.flags = DB_DBT_MALLOC; data.flags |= DB_DBT_MALLOC;
key.flags = DB_DBT_MALLOC; /* only BTREE databases will return anything in the key */
if (!(key.flags & DB_DBT_REALLOC) && _DB_get_type(self->mydb) == DB_BTREE) {
key.flags |= DB_DBT_MALLOC;
}
} }
if (!add_partial_dbt(&data, dlen, doff))
return NULL;
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RANGE); err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RANGE);
MYDB_END_ALLOW_THREADS; MYDB_END_ALLOW_THREADS;
...@@ -2940,17 +2972,14 @@ DBC_set_range(DBCursorObject* self, PyObject* args, PyObject* kwargs) ...@@ -2940,17 +2972,14 @@ DBC_set_range(DBCursorObject* self, PyObject* args, PyObject* kwargs)
data.data, data.size); data.data, data.size);
break; break;
} }
if (_DB_get_type(self->mydb) == DB_BTREE) { FREE_DBT(key);
/* the only time a malloced key is returned is when we
* call this on a BTree database because it performs
* partial matching and needs to return the real key.
* All others leave key untouched [where calling free()
* on it would often segfault].
*/
FREE_DBT(key);
}
FREE_DBT(data); FREE_DBT(data);
} }
/* the only time REALLOC should be set is if we used an integer
* key that make_dbt_key malloc'd for us. always free these. */
if (key.flags & DB_DBT_REALLOC) {
FREE_DBT(key);
}
return retval; return retval;
} }
...@@ -2966,8 +2995,10 @@ _DBC_get_set_both(DBCursorObject* self, PyObject* keyobj, PyObject* dataobj, ...@@ -2966,8 +2995,10 @@ _DBC_get_set_both(DBCursorObject* self, PyObject* keyobj, PyObject* dataobj,
/* the caller did this: CHECK_CURSOR_NOT_CLOSED(self); */ /* the caller did this: CHECK_CURSOR_NOT_CLOSED(self); */
if (!make_key_dbt(self->mydb, keyobj, &key, NULL)) if (!make_key_dbt(self->mydb, keyobj, &key, NULL))
return NULL; return NULL;
if (!make_dbt(dataobj, &data)) if (!make_dbt(dataobj, &data)) {
FREE_DBT(key);
return NULL; return NULL;
}
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_GET_BOTH); err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_GET_BOTH);
...@@ -3104,8 +3135,10 @@ DBC_set_recno(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -3104,8 +3135,10 @@ DBC_set_recno(DBCursorObject* self, PyObject* args, PyObject *kwargs)
/* Tell BerkeleyDB to malloc the return value (thread safe) */ /* Tell BerkeleyDB to malloc the return value (thread safe) */
data.flags = DB_DBT_MALLOC; data.flags = DB_DBT_MALLOC;
} }
if (!add_partial_dbt(&data, dlen, doff)) if (!add_partial_dbt(&data, dlen, doff)) {
FREE_DBT(key);
return NULL; return NULL;
}
MYDB_BEGIN_ALLOW_THREADS; MYDB_BEGIN_ALLOW_THREADS;
err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RECNO); err = self->dbc->c_get(self->dbc, &key, &data, flags|DB_SET_RECNO);
...@@ -3120,9 +3153,9 @@ DBC_set_recno(DBCursorObject* self, PyObject* args, PyObject *kwargs) ...@@ -3120,9 +3153,9 @@ DBC_set_recno(DBCursorObject* self, PyObject* args, PyObject *kwargs)
else { /* Can only be used for BTrees, so no need to return int key */ else { /* Can only be used for BTrees, so no need to return int key */
retval = Py_BuildValue("s#s#", key.data, key.size, retval = Py_BuildValue("s#s#", key.data, key.size,
data.data, data.size); data.data, data.size);
FREE_DBT(key);
FREE_DBT(data); FREE_DBT(data);
} }
FREE_DBT(key);
return retval; return retval;
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment