Skip to content

Commit

Permalink
Fix class-freed-before-instance-bug.
Browse files Browse the repository at this point in the history
In some rare instances, class objects can be freed before the last
instance of that class, causing a problem in the sweep phase where
we look at the class of the object being freed.

So we keep unreachable classes around for an extra collection to be
safe.
  • Loading branch information
rudi-c committed Jul 2, 2015
1 parent b6579c0 commit 7dffffb
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 10 deletions.
42 changes: 37 additions & 5 deletions src/gc/collector.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ FILE* trace_fp;
static std::unordered_set<void*> roots;
static std::vector<std::pair<void*, void*>> potential_root_ranges;

// BoxedClasses in the program that are still needed.
static std::unordered_set<BoxedClass*> class_objects;

static std::unordered_set<void*> nonheap_roots;
// Track the highest-addressed nonheap root; the assumption is that the nonheap roots will
// typically all have lower addresses than the heap roots, so this can serve as a cheap
Expand Down Expand Up @@ -135,8 +138,12 @@ class TraceStack {
pop_chunk();
assert(cur == end);
return *--cur; // no need for any bounds checks here since we're guaranteed we're CHUNK_SIZE from the start
} else {
// We emptied the stack, but we should prepare a new chunk in case another item
// gets added onto the stack.
get_chunk();
return NULL;
}
return NULL;
}


Expand Down Expand Up @@ -209,7 +216,7 @@ bool isValidGCObject(void* p) {
return al->user_data == p && (al->kind_id == GCKind::CONSERVATIVE_PYTHON || al->kind_id == GCKind::PYTHON);
}

void setIsPythonObject(Box* b) {
void registerPythonObject(Box* b) {
assert(isValidGCMemory(b));
auto al = GCAllocation::fromUserData(b);

Expand All @@ -218,6 +225,11 @@ void setIsPythonObject(Box* b) {
} else {
assert(al->kind_id == GCKind::PYTHON);
}

assert(b->cls);
if (b->cls == type_cls) {
class_objects.insert((BoxedClass*)b);
}
}

GCRootHandle::GCRootHandle() {
Expand Down Expand Up @@ -287,7 +299,7 @@ void GCVisitor::visitPotentialRange(void* const* start, void* const* end) {
}
}

static inline void visitByGCKind(void* p, GCVisitor& visitor) {
static __attribute__((always_inline)) void visitByGCKind(void* p, GCVisitor& visitor) {
assert(((intptr_t)p) % 8 == 0);

GCAllocation* al = GCAllocation::fromUserData(p);
Expand Down Expand Up @@ -327,7 +339,7 @@ static inline void visitByGCKind(void* p, GCVisitor& visitor) {
}
}

static void getMarkPhaseRoots(GCVisitor& visitor) {
static void markRoots(GCVisitor& visitor) {
GC_TRACE_LOG("Looking at the stack\n");
threading::visitAllStacks(&visitor);

Expand Down Expand Up @@ -392,10 +404,30 @@ static void markPhase() {
TraceStack stack(roots);
GCVisitor visitor(&stack);

getMarkPhaseRoots(visitor);
markRoots(visitor);

graphTraversalMarking(stack, visitor);

// Some classes might be unreachable. Unfortunately, we have to keep them around for
// one more collection, because during the sweep phase, instances of unreachable
// classes might still end up looking at the class. So we visit those unreachable
// classes and remove it from the list of class objects so that it can be freed
// in the next collection.
std::vector<BoxedClass*> classes_to_remove;
for (BoxedClass* cls : class_objects) {
GCAllocation* al = GCAllocation::fromUserData(cls);
if (!isMarked(al)) {
classes_to_remove.push_back(cls);
visitor.visit(cls);
}
}

graphTraversalMarking(stack, visitor);

for (BoxedClass* cls : classes_to_remove) {
class_objects.erase(cls);
}

#if TRACE_GC_MARKING
fclose(trace_fp);
trace_fp = NULL;
Expand Down
2 changes: 1 addition & 1 deletion src/gc/collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ void enableGC();
bool isValidGCMemory(void* p); // if p is a valid gc-allocated pointer (or a non-heap root)
bool isValidGCObject(void* p); // whether p is valid gc memory and is set to have Python destructor semantics applied
bool isNonheapRoot(void* p);
void setIsPythonObject(Box* b);
void registerPythonObject(Box* b);

// Debugging/validation helpers: if a GC should not happen in certain sections (ex during unwinding),
// use these functions to mark that. This is different from disableGC/enableGC, since it causes an
Expand Down
2 changes: 2 additions & 0 deletions src/gc/heap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ void _bytesAllocatedTripped() {
return;

threading::GLPromoteRegion _lock;

runCollection();
}

Expand All @@ -140,6 +141,7 @@ __attribute__((always_inline)) bool _doFree(GCAllocation* al, std::vector<Box*>*
VALGRIND_ENABLE_ERROR_REPORTING;
#endif

assert(b->cls);
if (PyType_SUPPORTS_WEAKREFS(b->cls)) {
PyWeakReference** list = (PyWeakReference**)PyObject_GET_WEAKREFS_LISTPTR(b);
if (list && *list) {
Expand Down
8 changes: 4 additions & 4 deletions src/runtime/types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2737,11 +2737,11 @@ extern "C" PyVarObject* PyObject_InitVar(PyVarObject* op, PyTypeObject* tp, Py_s
assert(gc::isValidGCMemory(op));
assert(gc::isValidGCObject(tp));

gc::setIsPythonObject(op);

Py_TYPE(op) = tp;
op->ob_size = size;

gc::registerPythonObject(op);

return op;
}

Expand All @@ -2752,10 +2752,10 @@ extern "C" PyObject* PyObject_Init(PyObject* op, PyTypeObject* tp) noexcept {
assert(gc::isValidGCMemory(op));
assert(gc::isValidGCObject(tp));

gc::setIsPythonObject(op);

Py_TYPE(op) = tp;

gc::registerPythonObject(op);

if (PyType_SUPPORTS_WEAKREFS(tp)) {
*PyObject_GET_WEAKREFS_LISTPTR(op) = NULL;
}
Expand Down
25 changes: 25 additions & 0 deletions test/tests/gc_type_object.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import gc

# Dynamically create new classes and instances of those classes in such a way
# that both the class object and the instance object will be freed in the same
# garbage collection pass. Hope that this doesn't cause any problems.
def generateClassAndInstances():
for i in xrange(5000):
def method(self, x):
return x + self.i
NewType1 = type("Class1_" + str(i), (),
dict(a={}, b=range(10), i=1, f=method))
NewType2 = type("Class2_" + str(i), (object,),
dict(a={}, b=range(10), i=2, f=method))
NewType3 = type("Class3_" + str(i), (NewType2,), {})
NewType4 = type("Class4_" + str(i), (NewType3,), {})
NewType5 = type("Class5_" + str(i), (NewType4,), {})
obj1 = NewType1()
obj2 = NewType2()
obj3 = NewType3()
obj4 = NewType4()
obj5 = NewType5()

generateClassAndInstances()
gc.collect()
gc.collect()

0 comments on commit 7dffffb

Please sign in to comment.