Commit 6fdb49bd authored by Stefan Behnel's avatar Stefan Behnel

move object finalisation code from tp_dealloc() into tp_finalize() in Py3.4

parent 4261a694
...@@ -8,6 +8,10 @@ Cython Changelog ...@@ -8,6 +8,10 @@ Cython Changelog
Features added Features added
-------------- --------------
* Starting with CPython 3.4, the user provided finalisation code in the
``__dealloc__()`` special method is called by ``tp_finalize()`` instead
of ``tp_dealloc()`` to provide a safer execution environment.
* During cyclic garbage collection, attributes of extension types that * During cyclic garbage collection, attributes of extension types that
cannot create reference cycles due to their type (e.g. strings) are cannot create reference cycles due to their type (e.g. strings) are
no longer considered for traversal or clearing. no longer considered for traversal or clearing.
......
...@@ -1006,6 +1006,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1006,6 +1006,7 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if scope: # could be None if there was an error if scope: # could be None if there was an error
self.generate_exttype_vtable(scope, code) self.generate_exttype_vtable(scope, code)
self.generate_new_function(scope, code, entry) self.generate_new_function(scope, code, entry)
self.generate_finalize_function(scope, code)
self.generate_dealloc_function(scope, code) self.generate_dealloc_function(scope, code)
if scope.needs_gc(): if scope.needs_gc():
self.generate_traverse_function(scope, code, entry) self.generate_traverse_function(scope, code, entry)
...@@ -1174,30 +1175,37 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1174,30 +1175,37 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
if tp_slot.slot_code(scope) != slot_func: if tp_slot.slot_code(scope) != slot_func:
return # never used return # never used
cpp_class_attrs = [entry for entry in scope.var_entries
if entry.type.is_cpp_class]
needs_finalisation = scope.needs_finalisation()
needs_gc = scope.needs_gc()
slot_func_cname = scope.mangle_internal("tp_dealloc") slot_func_cname = scope.mangle_internal("tp_dealloc")
code.putln("") code.putln("")
code.putln( code.putln(
"static void %s(PyObject *o) {" % slot_func_cname) "static void %s(PyObject *o) {" % slot_func_cname)
if cpp_class_attrs:
weakref_slot = scope.lookup_here("__weakref__")
_, (py_attrs, _, memoryview_slices) = scope.get_refcounted_entries()
cpp_class_attrs = [entry for entry in scope.var_entries if entry.type.is_cpp_class]
if (py_attrs
or cpp_class_attrs
or memoryview_slices
or weakref_slot in scope.var_entries):
self.generate_self_cast(scope, code) self.generate_self_cast(scope, code)
# We must mark ths object as (gc) untracked while tearing it down, lest if needs_gc and not base_type:
# the garbage collection is invoked while running this destructor.
if scope.needs_gc():
code.putln("PyObject_GC_UnTrack(o);") code.putln("PyObject_GC_UnTrack(o);")
# call the user's __dealloc__ if needs_finalisation:
self.generate_usr_dealloc_call(scope, code) # before Py3.4, tp_finalize() isn't available, so this is
if weakref_slot in scope.var_entries: # the earliest possible time where we can call it ourselves
code.putln("if (p->__weakref__) PyObject_ClearWeakRefs(o);") code.putln("#if PY_VERSION_HEX < 0x030400a1")
if needs_gc and base_type:
# We must mark ths object as (gc) untracked while tearing
# it down, lest the garbage collection is invoked while
# running this destructor.
code.putln("PyObject_GC_UnTrack(o);")
slot_func_cname = scope.mangle_internal("tp_finalize")
code.putln("%s(o);" % slot_func_cname)
if needs_gc and base_type:
# The base class deallocator probably expects this to be
# tracked, so undo the untracking above.
code.putln("PyObject_GC_Track(o);")
code.putln("#endif")
for entry in cpp_class_attrs: for entry in cpp_class_attrs:
split_cname = entry.type.cname.split('::') split_cname = entry.type.cname.split('::')
...@@ -1206,23 +1214,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1206,23 +1214,10 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
while destructor_name.count('<') != destructor_name.count('>'): while destructor_name.count('<') != destructor_name.count('>'):
destructor_name = split_cname.pop() + '::' + destructor_name destructor_name = split_cname.pop() + '::' + destructor_name
destructor_name = destructor_name.split('<',1)[0] destructor_name = destructor_name.split('<',1)[0]
code.putln("p->%s.%s::~%s();" % code.putln("p->%s.%s::~%s();" % (
(entry.cname, entry.type.declaration_code(""), destructor_name)) entry.cname, entry.type.declaration_code(""), destructor_name))
for entry in py_attrs:
code.put_xdecref_clear("p->%s" % entry.cname, entry.type, nanny=False,
clear_before_decref=True)
for entry in memoryview_slices:
code.put_xdecref_memoryviewslice("p->%s" % entry.cname,
have_gil=True)
if base_type: if base_type:
# The base class deallocator probably expects this to be tracked, so
# undo the untracking above.
if scope.needs_gc():
code.putln("PyObject_GC_Track(o);")
tp_dealloc = TypeSlots.get_base_slot_function(scope, tp_slot) tp_dealloc = TypeSlots.get_base_slot_function(scope, tp_slot)
if tp_dealloc is not None: if tp_dealloc is not None:
code.putln("%s(o);" % tp_dealloc) code.putln("%s(o);" % tp_dealloc)
...@@ -1234,7 +1229,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1234,7 +1229,8 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
# the module cleanup, which may already have cleared it. # the module cleanup, which may already have cleared it.
# In that case, fall back to traversing the type hierarchy. # In that case, fall back to traversing the type hierarchy.
base_cname = base_type.typeptr_cname base_cname = base_type.typeptr_cname
code.putln("if (likely(%s)) %s->tp_dealloc(o); else __Pyx_call_next_tp_dealloc(o, %s);" % ( code.putln("if (likely(%s)) %s->tp_dealloc(o); "
"else __Pyx_call_next_tp_dealloc(o, %s);" % (
base_cname, base_cname, slot_func_cname)) base_cname, base_cname, slot_func_cname))
code.globalstate.use_utility_code( code.globalstate.use_utility_code(
UtilityCode.load_cached("CallNextTpDealloc", "ExtensionTypes.c")) UtilityCode.load_cached("CallNextTpDealloc", "ExtensionTypes.c"))
...@@ -1256,28 +1252,86 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode): ...@@ -1256,28 +1252,86 @@ class ModuleNode(Nodes.Node, Nodes.BlockNode):
code.putln( code.putln(
"}") "}")
def generate_usr_dealloc_call(self, scope, code): def generate_finalize_function(self, scope, code):
if not scope.needs_finalisation():
return
entry = scope.lookup_here("__dealloc__") entry = scope.lookup_here("__dealloc__")
if entry: if entry:
code.putln( dealloc_user_cfunc = entry.func_cname
"{") else:
code.putln( dealloc_user_cfunc = None
"PyObject *etype, *eval, *etb;")
code.putln( weakref_slot = scope.lookup_here("__weakref__")
"PyErr_Fetch(&etype, &eval, &etb);") if weakref_slot not in scope.var_entries:
code.putln( weakref_slot = None
"++Py_REFCNT(o);") _, (py_attrs, _, memoryview_slices) = scope.get_refcounted_entries()
code.putln(
"%s(o);" % slot_func_cname = scope.mangle_internal("tp_finalize")
entry.func_cname) code.putln("")
code.putln( code.putln("static void %s(PyObject *o) {" % slot_func_cname)
"if (PyErr_Occurred()) PyErr_WriteUnraisable(o);")
code.putln( if py_attrs or memoryview_slices or weakref_slot:
"--Py_REFCNT(o);") self.generate_self_cast(scope, code)
code.putln(
"PyErr_Restore(etype, eval, etb);") if dealloc_user_cfunc:
code.putln( code.putln("PyObject *etype, *eval, *etb;")
"}") code.putln("PyErr_Fetch(&etype, &eval, &etb);")
code.putln("#if PY_VERSION_HEX < 0x030400a1")
code.putln("++Py_REFCNT(o);")
code.putln("#endif")
code.putln("%s(o);" % dealloc_user_cfunc)
code.putln("if (PyErr_Occurred()) PyErr_WriteUnraisable(o);")
code.putln("#if PY_VERSION_HEX < 0x030400a1")
code.putln("--Py_REFCNT(o);")
code.putln("#endif")
code.putln("PyErr_Restore(etype, eval, etb);")
if weakref_slot:
# Not using a preprocessor test here to avoid warning about
# "unused variable p". Py3.4+ already cleared the weak refs
# when we get here.
# FIXME: so did Py<3.4 I think? Isn't this redundant?
code.putln("if ((PY_VERSION_HEX < 0x030400a1) && p->__weakref__) "
"PyObject_ClearWeakRefs(o);")
for entry in py_attrs:
code.put_xdecref_clear("p->%s" % entry.cname, entry.type, nanny=False,
clear_before_decref=True)
for entry in memoryview_slices:
code.put_xdecref_memoryviewslice("p->%s" % entry.cname,
have_gil=True)
base_type = scope.parent_type.base_type
if base_type:
code.putln("#if PY_VERSION_HEX >= 0x030400a1")
# need to call base_type method
tp_finalize = TypeSlots.get_base_slot_function(
scope, TypeSlots.FinaliserSlot())
if tp_finalize is not None:
code.putln("%s(o);" % tp_finalize)
elif base_type.is_builtin_type:
code.putln("if (%s->tp_finalize) %s->tp_finalize(o);" % (
base_type.typeptr_cname, base_type.typeptr_cname))
else:
# This is an externally defined type. Calling through the
# cimported base type pointer directly interacts badly with
# the module cleanup, which may already have cleared it.
# In that case, fall back to traversing the type hierarchy.
base_cname = base_type.typeptr_cname
code.putln("if (likely(%s && %s->tp_finalize)) %s->tp_finalize(o); "
"else __Pyx_call_next_tp_finalize(o, %s);" % (
base_cname, base_cname, base_cname, slot_func_cname))
code.globalstate.use_utility_code(
UtilityCode.load_cached("CallNextTpFinalize", "ExtensionTypes.c"))
code.putln("#endif")
code.putln("}")
def generate_traverse_function(self, scope, code, cclass_entry): def generate_traverse_function(self, scope, code, cclass_entry):
tp_slot = TypeSlots.GCDependentSlot("tp_traverse") tp_slot = TypeSlots.GCDependentSlot("tp_traverse")
......
...@@ -832,25 +832,6 @@ class Scope(object): ...@@ -832,25 +832,6 @@ class Scope(object):
def add_include_file(self, filename): def add_include_file(self, filename):
self.outer_scope.add_include_file(filename) self.outer_scope.add_include_file(filename)
def get_refcounted_entries(self, include_weakref=False,
include_gc_simple=True):
py_attrs = []
py_buffers = []
memoryview_slices = []
for entry in self.var_entries:
if entry.type.is_pyobject:
if include_weakref or entry.name != "__weakref__":
if include_gc_simple or not entry.type.is_gc_simple:
py_attrs.append(entry)
elif entry.type == PyrexTypes.c_py_buffer_type:
py_buffers.append(entry)
elif entry.type.is_memoryviewslice:
memoryview_slices.append(entry)
have_entries = py_attrs or py_buffers or memoryview_slices
return have_entries, (py_attrs, py_buffers, memoryview_slices)
class PreImportScope(Scope): class PreImportScope(Scope):
...@@ -1804,6 +1785,33 @@ class CClassScope(ClassScope): ...@@ -1804,6 +1785,33 @@ class CClassScope(ClassScope):
return not self.parent_type.is_gc_simple return not self.parent_type.is_gc_simple
return False return False
def get_refcounted_entries(self, include_weakref=False,
include_gc_simple=True):
py_attrs = []
py_buffers = []
memoryview_slices = []
for entry in self.var_entries:
if entry.type.is_pyobject:
if include_weakref or entry.name != "__weakref__":
if include_gc_simple or not entry.type.is_gc_simple:
py_attrs.append(entry)
elif entry.type == PyrexTypes.c_py_buffer_type:
py_buffers.append(entry)
elif entry.type.is_memoryviewslice:
memoryview_slices.append(entry)
have_entries = py_attrs or py_buffers or memoryview_slices
return have_entries, (py_attrs, py_buffers, memoryview_slices)
def needs_finalisation(self):
if self.lookup_here("__dealloc__"):
return True
has_gc_entries, _ = self.get_refcounted_entries(include_weakref=True)
if has_gc_entries:
return True
return False
def declare_var(self, name, type, pos, def declare_var(self, name, type, pos,
cname = None, visibility = 'private', cname = None, visibility = 'private',
api = 0, in_pxd = 0, is_cdef = 0): api = 0, in_pxd = 0, is_cdef = 0):
...@@ -1865,7 +1873,6 @@ class CClassScope(ClassScope): ...@@ -1865,7 +1873,6 @@ class CClassScope(ClassScope):
self.namespace_cname = "(PyObject *)%s" % self.parent_type.typeptr_cname self.namespace_cname = "(PyObject *)%s" % self.parent_type.typeptr_cname
return entry return entry
def declare_pyfunction(self, name, pos, allow_redefine=False): def declare_pyfunction(self, name, pos, allow_redefine=False):
# Add an entry for a method. # Add an entry for a method.
if name in ('__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__'): if name in ('__eq__', '__ne__', '__lt__', '__gt__', '__le__', '__ge__'):
...@@ -2033,6 +2040,7 @@ class CClassScope(ClassScope): ...@@ -2033,6 +2040,7 @@ class CClassScope(ClassScope):
if base_entry.utility_code: if base_entry.utility_code:
entry.utility_code = base_entry.utility_code entry.utility_code = base_entry.utility_code
class CppClassScope(Scope): class CppClassScope(Scope):
# Namespace of a C++ class. # Namespace of a C++ class.
......
...@@ -331,6 +331,21 @@ class GCDependentSlot(InternalMethodSlot): ...@@ -331,6 +331,21 @@ class GCDependentSlot(InternalMethodSlot):
return InternalMethodSlot.slot_code(self, scope) return InternalMethodSlot.slot_code(self, scope)
class FinaliserSlot(InternalMethodSlot):
"""
Descriptor for tp_finalize().
"""
def __init__(self):
InternalMethodSlot.__init__(
self, 'tp_finalize',
ifdef="PY_VERSION_HEX >= 0x030400a1")
def slot_code(self, scope):
if not scope.needs_finalisation():
return '0'
return InternalMethodSlot.slot_code(self, scope)
class ConstructorSlot(InternalMethodSlot): class ConstructorSlot(InternalMethodSlot):
# Descriptor for tp_new and tp_dealloc. # Descriptor for tp_new and tp_dealloc.
...@@ -788,8 +803,7 @@ slot_table = ( ...@@ -788,8 +803,7 @@ slot_table = (
EmptySlot("tp_weaklist"), EmptySlot("tp_weaklist"),
EmptySlot("tp_del"), EmptySlot("tp_del"),
EmptySlot("tp_version_tag", ifdef="PY_VERSION_HEX >= 0x02060000"), EmptySlot("tp_version_tag", ifdef="PY_VERSION_HEX >= 0x02060000"),
# TODO: change __dealloc__ to be called by tp_finalize (PEP 442) FinaliserSlot(), # 'tp_finalize'
EmptySlot("tp_finalize", ifdef="PY_VERSION_HEX >= 0x03040a00"),
) )
#------------------------------------------------------------------------------------------ #------------------------------------------------------------------------------------------
......
...@@ -16,6 +16,27 @@ static void __Pyx_call_next_tp_dealloc(PyObject* obj, destructor current_tp_deal ...@@ -16,6 +16,27 @@ static void __Pyx_call_next_tp_dealloc(PyObject* obj, destructor current_tp_deal
type->tp_dealloc(obj); type->tp_dealloc(obj);
} }
/////////////// CallNextTpFinalize.proto ///////////////
#if PY_VERSION_HEX >= 0x030400a1
static void __Pyx_call_next_tp_finalize(PyObject* obj, destructor current_tp_finalize);
#endif
/////////////// CallNextTpFinalize ///////////////
#if PY_VERSION_HEX >= 0x030400a1
static void __Pyx_call_next_tp_finalize(PyObject* obj, destructor current_tp_finalize) {
PyTypeObject* type = Py_TYPE(obj);
/* try to find the first parent type that has a different tp_finalize() function */
while (type && type->tp_finalize != current_tp_finalize)
type = type->tp_base;
while (type && (!type->tp_finalize || type->tp_finalize == current_tp_finalize))
type = type->tp_base;
if (type)
type->tp_finalize(obj);
}
#endif
/////////////// CallNextTpTraverse.proto /////////////// /////////////// CallNextTpTraverse.proto ///////////////
static int __Pyx_call_next_tp_traverse(PyObject* obj, visitproc v, void *a, traverseproc current_tp_traverse); static int __Pyx_call_next_tp_traverse(PyObject* obj, visitproc v, void *a, traverseproc current_tp_traverse);
......
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