Commit 83379f19 authored by Kevin Modzelewski's avatar Kevin Modzelewski

dict: the gc can resurrect objects after dealloc

so object's can be resurrected even with a non-resurrecting
tp_dealloc.  The only thing that can happen at that point is
that the gc_visit function will get called.

This shows up in the new sqlalchemy tests: they use a subclass
of dict (defaultdict), which defines a custom tp_dealloc.  We call that,
which ends up calling dict_cls->tp_dealloc, which calls ~DenseMap
on the internal DenseMap.  The GC will accidentally keep the object
alive (via a stack reference or something similar), and call its
visit function on the next GC.  But ~DenseMap does not leave the map
in a consistent state, so iterating over it will end up producing
garbage values.  To solve this, add a new function that does
all the same memory-freeing, but does a tiny bit of extra work to keep
the DenseMap in a valid state for future gc_visit calls.
parent 02f3744a
......@@ -600,6 +600,14 @@ public:
this->insert(I, E);
}
// Pyston addition:
// Frees all dynamically-allocated memory, but leaves the DenseMap in a valid state.
void freeAllMemory() {
this->destroyAll();
operator delete(Buckets);
init(0);
}
~DenseMap() {
this->destroyAll();
operator delete(Buckets);
......
......@@ -81,6 +81,12 @@ public:
TheMap.clear();
}
// Pyston addition:
// Frees all dynamically-allocated memory, but leaves the DenseSet in a valid state.
void freeAllMemory() {
TheMap.freeAllMemory();
}
/// Return 1 if the specified key is in the set, 0 otherwise.
size_type count(const ValueT &V) const {
return TheMap.count(V);
......
......@@ -161,6 +161,7 @@ public:
PyThread_release_lock(self->lock_lock);
PyThread_free_lock(self->lock_lock);
self->lock_lock = NULL;
}
}
......
......@@ -1055,13 +1055,15 @@ static PyObject* instance_index(PyObject* self) noexcept {
return res;
}
static void instance_dealloc(Box* _inst) {
static void instance_dealloc(Box* _inst) noexcept {
RELEASE_ASSERT(_inst->cls == instance_cls, "");
BoxedInstance* inst = static_cast<BoxedInstance*>(_inst);
// Note that trying to call __del__ as a finalizer does not fallback to
// __getattr__ unlike other attributes (like __index__). This is CPython's behavior.
static BoxedString* del_str = internStringImmortal("__del__");
// TODO: any exceptions here should get caught + printed, instead of causing a std::terminate:
Box* func = instanceGetattributeSimple(inst, del_str);
if (func)
runtimeCall(func, ArgPassSpec(0), NULL, NULL, NULL, NULL, NULL);
......
......@@ -771,7 +771,7 @@ static Box* dict_repr(PyObject* self) noexcept {
void BoxedDict::dealloc(Box* b) noexcept {
assert(PyDict_Check(b));
static_cast<BoxedDict*>(b)->d.~DictMap();
static_cast<BoxedDict*>(b)->d.freeAllMemory();
}
void setupDict() {
......
......@@ -587,7 +587,7 @@ extern "C" PyObject* PyFrozenSet_New(PyObject* iterable) noexcept {
void BoxedSet::dealloc(Box* b) noexcept {
assert(PyAnySet_Check(b));
static_cast<BoxedSet*>(b)->s.~Set();
static_cast<BoxedSet*>(b)->s.freeAllMemory();
}
using namespace pyston::set;
......
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