Commit 8f8ed3f5 authored by Kevin Modzelewski's avatar Kevin Modzelewski

Detect tp_alloc usage bugs and fix some that were found

Apparently we can run quite well in the presence of GC bugs.
(We were scanning most extension objects using our Pyston scanner
instead of the conservative one.)  I am both relieved and terrified.
parent 790f24d2
......@@ -170,7 +170,7 @@ PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t) PYSTON_NO
( Py_SIZE(op) = (size), PyObject_INIT((op), (typeobj)) )
#endif
#define PyObject_INIT(op, typeobj) PyObject_Init((PyObject*)(op), (PyTypeObject*)(typeobj))
#define PyObject_INIT_VAR(op, typeobj, size) PyObject_InitVar((PyObject*)(op), (PyTypeObject*)(typeobj), size)
#define PyObject_INIT_VAR(op, typeobj, size) PyObject_InitVar((PyVarObject*)(op), (PyTypeObject*)(typeobj), size)
#define _PyObject_SIZE(typeobj) ( (typeobj)->tp_basicsize )
......@@ -241,6 +241,9 @@ PyAPI_FUNC(PyVarObject *) _PyObject_NewVar(PyTypeObject *, Py_ssize_t) PYSTON_NO
/* C equivalent of gc.collect(). */
PyAPI_FUNC(Py_ssize_t) PyGC_Collect(void) PYSTON_NOEXCEPT;
// Pyston changes: everything is GC tracked now
#if 0
/* Test if a type has a GC head */
#define PyType_IS_GC(t) PyType_HasFeature((t), Py_TPFLAGS_HAVE_GC)
......@@ -309,13 +312,22 @@ extern PyGC_Head *_PyGC_generation0;
(PyObject_IS_GC(obj) && \
(!PyTuple_CheckExact(obj) || _PyObject_GC_IS_TRACKED(obj)))
PyAPI_FUNC(PyObject *) _PyObject_GC_Malloc(size_t) PYSTON_NOEXCEPT;
PyAPI_FUNC(PyObject *) _PyObject_GC_New(PyTypeObject *) PYSTON_NOEXCEPT;
PyAPI_FUNC(PyVarObject *) _PyObject_GC_NewVar(PyTypeObject *, Py_ssize_t) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyObject_GC_Track(void *) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyObject_GC_UnTrack(void *) PYSTON_NOEXCEPT;
PyAPI_FUNC(void) PyObject_GC_Del(void *) PYSTON_NOEXCEPT;
#endif
#define PyType_IS_GC(t) ((t),1)
#define _PyObject_GC_TRACK(o) ((void)(o))
#define _PyObject_GC_UNTRACK(o) ((void)(o))
#define _PyObject_GC_Malloc(size) ((PyObject*)PyObject_MALLOC(size))
#define _PyObject_GC_New _PyObject_New
#define PyObject_GC_Track(o) ((void)(o))
#define PyObject_GC_UnTrack(o) ((void)(o))
#define PyObject_GC_Del(o) ((void)(o))
#define PyObject_GC_New(type, typeobj) \
( (type *) _PyObject_GC_New(typeobj) )
......
......@@ -1528,7 +1528,8 @@ static void inherit_slots(PyTypeObject* type, PyTypeObject* base) noexcept {
* didn't define tp_free, and the base uses the
* default non-gc tp_free.
*/
type->tp_free = PyObject_GC_Del;
// Pyston change: don't do this:
// type->tp_free = PyObject_GC_Del;
}
/* else they didn't agree about gc, and there isn't something
* obvious to be done -- the type is on its own.
......
......@@ -102,7 +102,7 @@ void registerNonheapRootObject(void* obj) {
max_nonheap_root = std::max(obj, max_nonheap_root);
}
static bool isNonheapRoot(void* p) {
bool isNonheapRoot(void* p) {
return p <= max_nonheap_root && nonheap_roots.count(p) != 0;
}
......
......@@ -46,7 +46,9 @@ public:
void runCollection();
// These are mostly for debugging:
bool isValidGCObject(void* p);
bool isNonheapRoot(void* p);
}
}
......
......@@ -142,45 +142,11 @@ extern "C" PyVarObject* PyObject_InitVar(PyVarObject* op, PyTypeObject* tp, Py_s
return op;
}
extern "C" PyObject* _PyObject_New(PyTypeObject* cls) noexcept {
assert(cls->tp_itemsize == 0);
auto rtn = (PyObject*)gc_alloc(cls->tp_basicsize, gc::GCKind::PYTHON);
// no memset for this function
PyObject_Init(rtn, cls);
return rtn;
}
extern "C" void PyObject_Free(void* p) noexcept {
gc::gc_free(p);
ASSERT(0, "I think this is good enough but I'm not sure; should test");
}
extern "C" PyObject* _PyObject_GC_Malloc(size_t) noexcept {
Py_FatalError("unimplemented");
}
extern "C" PyObject* _PyObject_GC_New(PyTypeObject* cls) noexcept {
return _PyObject_New(cls);
}
extern "C" PyVarObject* _PyObject_GC_NewVar(PyTypeObject*, Py_ssize_t) noexcept {
Py_FatalError("unimplemented");
}
extern "C" void PyObject_GC_Track(void*) noexcept {
// TODO do we have to do anything to support the C API GC protocol?
}
extern "C" void PyObject_GC_UnTrack(void*) noexcept {
// TODO do we have to do anything to support the C API GC protocol?
}
extern "C" void PyObject_GC_Del(void*) noexcept {
Py_FatalError("unimplemented");
}
extern "C" PyObject* PyObject_CallObject(PyObject* obj, PyObject* args) noexcept {
RELEASE_ASSERT(args, ""); // actually it looks like this is allowed to be NULL
RELEASE_ASSERT(args->cls == tuple_cls, "");
......
......@@ -345,7 +345,8 @@ void BoxedClass::freeze() {
BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset, int instance_size,
bool is_user_defined)
: BoxVar(0), gc_visit(gc_visit), attrs_offset(attrs_offset), is_constant(false), is_user_defined(is_user_defined) {
: BoxVar(0), gc_visit(gc_visit), attrs_offset(attrs_offset), is_constant(false), is_user_defined(is_user_defined),
is_pyston_class(true) {
// Zero out the CPython tp_* slots:
memset(&tp_name, 0, (char*)(&tp_version_tag + 1) - (char*)(&tp_name));
......@@ -357,7 +358,13 @@ BoxedClass::BoxedClass(BoxedClass* base, gcvisit_func gc_visit, int attrs_offset
tp_base = base;
tp_alloc = PyType_GenericAlloc;
if (tp_base) {
assert(tp_base->tp_alloc);
tp_alloc = tp_base->tp_alloc;
} else {
assert(object_cls == NULL);
tp_alloc = PystonType_GenericAlloc;
}
if (cls == NULL) {
assert(type_cls == NULL);
......@@ -1803,12 +1810,22 @@ extern "C" i64 unboxedLen(Box* obj) {
extern "C" void dump(void* p) {
printf("\n");
printf("Raw address: %p\n", p);
bool is_gc = (gc::global_heap.getAllocationFromInteriorPointer(p) != NULL);
bool is_gc = gc::isValidGCObject(p);
if (!is_gc) {
printf("non-gc memory\n");
return;
}
if (gc::isNonheapRoot(p)) {
printf("Non-heap GC object\n");
printf("Assuming it's a class object...\n");
PyTypeObject* type = (PyTypeObject*)(p);
printf("tp_name: %s\n", type->tp_name);
return;
}
gc::GCAllocation* al = gc::GCAllocation::fromUserData(p);
if (al->kind_id == gc::GCKind::UNTRACKED) {
printf("gc-untracked object\n");
......@@ -3331,13 +3348,14 @@ Box* typeNew(Box* _cls, Box* arg1, Box* arg2, Box** _args) {
// Note: make sure to do this after assigning the attrs, since it will overwrite any defined __name__
made->setattr("__name__", name, NULL);
// TODO should this function (typeNew) call PyType_Ready?
made->tp_new = base->tp_new;
made->tp_alloc = reinterpret_cast<decltype(cls->tp_alloc)>(PyType_GenericAlloc);
PystonType_Ready(made);
fixup_slot_dispatchers(made);
made->tp_alloc = base->tp_alloc;
assert(made->tp_alloc);
return made;
}
......
......@@ -57,13 +57,30 @@ bool IN_SHUTDOWN = false;
#define SLICE_STOP_OFFSET ((char*)&(((BoxedSlice*)0x01)->stop) - (char*)0x1)
#define SLICE_STEP_OFFSET ((char*)&(((BoxedSlice*)0x01)->step) - (char*)0x1)
extern "C" PyObject* PyType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noexcept {
// Analogue of PyType_GenericAlloc (default tp_alloc), but should only be used for Pyston classes!
PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noexcept {
assert(cls);
RELEASE_ASSERT(nitems == 0, "");
RELEASE_ASSERT(cls->tp_itemsize == 0, "");
const size_t size = cls->tp_basicsize;
#ifndef NDEBUG
#if 0
assert(cls->tp_bases);
// TODO this should iterate over all subclasses
for (auto e : cls->tp_bases->pyElements()) {
assert(e->cls == type_cls); // what about old style classes?
assert(static_cast<BoxedClass*>(e)->is_pyston_class);
}
#endif
BoxedClass* b = cls;
while (b) {
ASSERT(b->is_pyston_class, "%s (%s)", cls->tp_name, b->tp_name);
b = b->tp_base;
}
#endif
// Maybe we should only zero the extension memory?
// I'm not sure we have the information at the moment, but when we were in Box::operator new()
// we knew which memory was beyond C++ class.
......@@ -78,6 +95,42 @@ extern "C" PyObject* PyType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noe
return rtn;
}
extern "C" PyObject* PyType_GenericAlloc(PyTypeObject* type, Py_ssize_t nitems) noexcept {
PyObject* obj;
const size_t size = _PyObject_VAR_SIZE(type, nitems + 1);
/* note that we need to add one, for the sentinel */
if (PyType_IS_GC(type))
obj = _PyObject_GC_Malloc(size);
else
obj = (PyObject*)PyObject_MALLOC(size);
if (obj == NULL)
return PyErr_NoMemory();
memset(obj, '\0', size);
if (type->tp_flags & Py_TPFLAGS_HEAPTYPE)
Py_INCREF(type);
if (type->tp_itemsize == 0)
PyObject_INIT(obj, type);
else
(void)PyObject_INIT_VAR((PyVarObject*)obj, type, nitems);
if (PyType_IS_GC(type))
_PyObject_GC_TRACK(obj);
return obj;
}
extern "C" PyObject* _PyObject_New(PyTypeObject* tp) noexcept {
PyObject* op;
op = (PyObject*)PyObject_MALLOC(_PyObject_SIZE(tp));
if (op == NULL)
return PyErr_NoMemory();
return PyObject_INIT(op, tp);
}
// Analogue of PyType_GenericNew
void* Box::operator new(size_t size, BoxedClass* cls) {
assert(cls);
......
......@@ -211,6 +211,11 @@ public:
// this is used mostly for debugging.
bool is_user_defined;
// Whether this is a Pyston-defined class (as opposed to an extension-defined class).
// We can ensure certain behavior about our Pyston classes (in particular around GC support)
// that we can't rely on for extension classes.
bool is_pyston_class;
// will need to update this once we support tp_getattr-style overriding:
bool hasGenericGetattr() { return true; }
......@@ -571,5 +576,8 @@ extern "C" BoxedClass* Exception, *AssertionError, *AttributeError, *TypeError,
*StopIteration, *GeneratorExit, *SyntaxError;
Box* makeAttrWrapper(Box* b);
// Our default for tp_alloc:
PyObject* PystonType_GenericAlloc(BoxedClass* cls, Py_ssize_t nitems) noexcept;
}
#endif
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