diff options
author | Campbell Barton <ideasman42@gmail.com> | 2011-03-01 17:53:26 +0300 |
---|---|---|
committer | Campbell Barton <ideasman42@gmail.com> | 2011-03-01 17:53:26 +0300 |
commit | 623822626a954fe9f83f18af4b3699c6e80b5313 (patch) | |
tree | 54afb70ab229403aa532e7f38cad26c6eebbc188 /source/blender/python/intern | |
parent | f0f639f8b8414a9574a12b4fcf5b76549524f540 (diff) |
Py/RNA Stability: don't allow python to reference freed ID's and crash.
Second method for not having python crash blender on invalid access (ifdef'd out ATM, so no functional change).
This uses a weakref list per ID, and invalidates all members of that list when the ID is freed.
the list is not stores in the ID pointer but using a hash table since storing python in DNA data is not acceptable.
This is more correct then the previous method but shows down execution of scripts significantly since its always adding and removing from lists when data is created and freed.
Diffstat (limited to 'source/blender/python/intern')
-rw-r--r-- | source/blender/python/intern/bpy_rna.c | 150 | ||||
-rw-r--r-- | source/blender/python/intern/bpy_rna.h | 19 |
2 files changed, 163 insertions, 6 deletions
diff --git a/source/blender/python/intern/bpy_rna.c b/source/blender/python/intern/bpy_rna.c index bc216207821..b1338f3b82d 100644 --- a/source/blender/python/intern/bpy_rna.c +++ b/source/blender/python/intern/bpy_rna.c @@ -37,11 +37,19 @@ #include "bpy_util.h" #include "bpy_rna_callback.h" +#ifdef USE_PYRNA_INVALIDATE_GC +#include "MEM_guardedalloc.h" +#endif + #include "BLI_dynstr.h" #include "BLI_string.h" #include "BLI_listbase.h" #include "BLI_utildefines.h" +#ifdef USE_PYRNA_INVALIDATE_GC +#include "BLI_ghash.h" +#endif + #include "RNA_enum_types.h" #include "RNA_define.h" /* RNA_def_property_free_identifier */ @@ -134,13 +142,138 @@ static void id_release_gc(struct ID *id) } #endif +#ifdef USE_PYRNA_INVALIDATE_WEAKREF +struct GHash *id_weakref_pool= NULL; +static PyObject *id_free_weakref_cb(PyObject *weakinfo_pair, PyObject *weakref); +static PyMethodDef id_free_weakref_cb_def= {"id_free_weakref_cb", (PyCFunction)id_free_weakref_cb, METH_O, NULL}; + +/* adds a reference to the list, remember ot decref */ +static PyObject *id_weakref_pool_get(ID *id) +{ + PyObject *weakinfo_list= NULL; + + if(id_weakref_pool) { + weakinfo_list= BLI_ghash_lookup(id_weakref_pool, (void *)id); + } + else { + /* first time, allocate pool */ + id_weakref_pool= BLI_ghash_new(BLI_ghashutil_ptrhash, BLI_ghashutil_ptrcmp, "copyArc gh"); + weakinfo_list= NULL; + } + + if(weakinfo_list==NULL) { + weakinfo_list= PyList_New(0); + BLI_ghash_insert(id_weakref_pool, (void *)id, weakinfo_list); + } + + return weakinfo_list; +} + +/* called from pyrna_struct_CreatePyObject() and pyrna_prop_CreatePyObject() */ +void id_weakref_pool_add(ID *id, BPy_DummyPointerRNA* pyrna) +{ + PyObject *weakref; + PyObject *weakref_cb_py; + + /* create a new function instance and insert the list as 'self' so we can remove ourself from it */ + PyObject *weakinfo_list= id_weakref_pool_get(id); /* new or existing */ + PyObject *weakinfo_pair= PyTuple_New(2); + PyTuple_SET_ITEM(weakinfo_pair, 0, weakinfo_list); + Py_INCREF(weakinfo_list); + PyTuple_SET_ITEM(weakinfo_pair, 1, PyCapsule_New(id, NULL, NULL)); + weakref_cb_py= PyCFunction_New(&id_free_weakref_cb_def, weakinfo_pair); + Py_DECREF(weakinfo_pair); /* function' 'self' now owns weakinfo_list now */ + + /* add weakref to weakinfo_list list */ + weakref= PyWeakref_NewRef((PyObject *)pyrna, weakref_cb_py); + Py_DECREF(weakref_cb_py); /* function owned by the weakref now */ + + /* important to add at the end, since first removal looks at the end */ + PyList_Append(weakinfo_list, weakref); + Py_DECREF(weakref); +} + +static void id_release_weakref(struct ID *id); +static PyObject *id_free_weakref_cb(PyObject *weakinfo_pair, PyObject *weakref) +{ + + /* important to search backwards */ + PyObject *weakinfo_list= PyTuple_GET_ITEM(weakinfo_pair, 0); + unsigned int i= PyList_GET_SIZE(weakinfo_list); + const unsigned int last_index= i - 1; + + while(i--) { + if(PyList_GET_ITEM(weakinfo_list, i) == weakref) { + /* swap */ + if(i != last_index) { + PyList_SET_ITEM(weakinfo_list, i, PyList_GET_ITEM(weakinfo_list, last_index)); + } + PyList_SET_ITEM(weakinfo_list, last_index, NULL); /* to avoid weakref issue */ + /* remove last item */ + PyList_SetSlice(weakinfo_list, last_index, PY_SSIZE_T_MAX, NULL); + Py_DECREF(weakref); + +/*DEBUG*/ //printf("bprna_weakref_cb: %p size is %d\n", weakref, last_index + 1); + + /* optional, free the list once its 0 size + * to keep the ID hash lookups fast by not allowing manu empty items to exist */ + if(last_index == 0) { + PyObject *weakinfo_id= PyTuple_GET_ITEM(weakinfo_pair, 1); + ID *id= PyCapsule_GetPointer(weakinfo_id, NULL); + + /* the list is empty, just free it */ + id_release_weakref(id); + } + break; + } + } + + Py_RETURN_NONE; +} + +static void id_release_weakref(struct ID *id) +{ + PyObject *weakinfo_list= BLI_ghash_lookup(id_weakref_pool, (void *)id); + if(weakinfo_list) { + unsigned int i= PyList_GET_SIZE(weakinfo_list); + +/*DEBUG*/ //printf("BPY_id_release: '%s', %d items\n", id->name, i); + + while(i--) { + PyObject *item= PyWeakref_GET_OBJECT(PyList_GET_ITEM(weakinfo_list, i)); + if(item != Py_None) { + pyrna_invalidate((BPy_DummyPointerRNA *)item); + } + } + + BLI_ghash_remove(id_weakref_pool, (void *)id, NULL, NULL); + Py_DECREF(weakinfo_list); + + if(BLI_ghash_size(id_weakref_pool) == 0) { +/*DEBUG*/ //printf("BPY_id_release: freeing global pool\n"); + BLI_ghash_free(id_weakref_pool, NULL, NULL); + id_weakref_pool= NULL; + } + } +} + +#endif /* USE_PYRNA_INVALIDATE_WEAKREF */ void BPY_id_release(struct ID *id) { - (void)id; #ifdef USE_PYRNA_INVALIDATE_GC id_release_gc(id); #endif + +#ifdef USE_PYRNA_INVALIDATE_WEAKREF + PyGILState_STATE gilstate = PyGILState_Ensure(); + + id_release_weakref(id); + + PyGILState_Release(gilstate); +#endif /* USE_PYRNA_INVALIDATE_WEAKREF */ + + (void)id; } #ifdef USE_PEDANTIC_WRITE @@ -5080,7 +5213,12 @@ PyObject *pyrna_struct_CreatePyObject( PointerRNA *ptr ) pyrna->freeptr= FALSE; // PyC_ObSpit("NewStructRNA: ", (PyObject *)pyrna); - + +#ifdef USE_PYRNA_INVALIDATE_WEAKREF + if(ptr->id.data) { + id_weakref_pool_add(ptr->id.data, (BPy_DummyPointerRNA *)pyrna); + } +#endif return ( PyObject * ) pyrna; } @@ -5124,7 +5262,13 @@ PyObject *pyrna_prop_CreatePyObject( PointerRNA *ptr, PropertyRNA *prop ) pyrna->ptr = *ptr; pyrna->prop = prop; - + +#ifdef USE_PYRNA_INVALIDATE_WEAKREF + if(ptr->id.data) { + id_weakref_pool_add(ptr->id.data, (BPy_DummyPointerRNA *)pyrna); + } +#endif + return ( PyObject * ) pyrna; } diff --git a/source/blender/python/intern/bpy_rna.h b/source/blender/python/intern/bpy_rna.h index 8d9fabf5627..be877344c48 100644 --- a/source/blender/python/intern/bpy_rna.h +++ b/source/blender/python/intern/bpy_rna.h @@ -44,12 +44,25 @@ extern PyTypeObject pyrna_prop_collection_Type; #define BPy_PropertyRNA_Check(v) (PyObject_TypeCheck(v, &pyrna_prop_Type)) #define BPy_PropertyRNA_CheckExact(v) (Py_TYPE(v) == &pyrna_prop_Type) -/* method to invalidate removed py data, XXX, slow to remove objects, otherwise no overhead */ -// #define USE_PYRNA_INVALIDATE_GC - /* play it safe and keep optional for now, need to test further now this affects looping on 10000's of verts for eg. */ // #define USE_WEAKREFS +/* method to invalidate removed py data, XXX, slow to remove objects, otherwise no overhead */ +//#define USE_PYRNA_INVALIDATE_GC + +/* different method */ +//#define USE_PYRNA_INVALIDATE_WEAKREF + + +/* sanity checks on above defs */ +#if defined(USE_PYRNA_INVALIDATE_WEAKREF) && !defined(USE_WEAKREFS) +#define USE_WEAKREFS +#endif + +#if defined(USE_PYRNA_INVALIDATE_GC) && defined(USE_PYRNA_INVALIDATE_WEAKREF) +#error "Only 1 reference check method at a time!" +#endif + typedef struct { PyObject_HEAD /* required python macro */ PointerRNA ptr; |