Fix UniformCollection owner validity check (closes #272)
UniformCollection accessor methods checked the raw collection pointer but never checked if the owning object was still alive. Changed owner field from weak_ptr<UIDrawable> to weak_ptr<void> (type-erased) so both UIDrawable and UIEntity owners can be tracked. Set owner in both get_uniforms() paths. All accessors now check owner.lock() before dereferencing the raw collection pointer. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
394e79ce88
commit
34c84ce50a
4 changed files with 37 additions and 14 deletions
|
|
@ -151,7 +151,7 @@ PyObject* PyUniformCollectionType::repr(PyObject* obj) {
|
||||||
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
||||||
std::ostringstream ss;
|
std::ostringstream ss;
|
||||||
ss << "<UniformCollection";
|
ss << "<UniformCollection";
|
||||||
if (self->collection) {
|
if (self->owner.lock() && self->collection) {
|
||||||
auto names = self->collection->getNames();
|
auto names = self->collection->getNames();
|
||||||
ss << " [";
|
ss << " [";
|
||||||
for (size_t i = 0; i < names.size(); ++i) {
|
for (size_t i = 0; i < names.size(); ++i) {
|
||||||
|
|
@ -166,15 +166,15 @@ PyObject* PyUniformCollectionType::repr(PyObject* obj) {
|
||||||
|
|
||||||
Py_ssize_t PyUniformCollectionType::mp_length(PyObject* obj) {
|
Py_ssize_t PyUniformCollectionType::mp_length(PyObject* obj) {
|
||||||
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
||||||
if (!self->collection) return 0;
|
if (!self->owner.lock() || !self->collection) return 0;
|
||||||
return static_cast<Py_ssize_t>(self->collection->getNames().size());
|
return static_cast<Py_ssize_t>(self->collection->getNames().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyUniformCollectionType::mp_subscript(PyObject* obj, PyObject* key) {
|
PyObject* PyUniformCollectionType::mp_subscript(PyObject* obj, PyObject* key) {
|
||||||
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
||||||
|
|
||||||
if (!self->collection) {
|
if (!self->owner.lock() || !self->collection) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "UniformCollection is not valid");
|
PyErr_SetString(PyExc_RuntimeError, "UniformCollection is not valid (owner destroyed)");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -239,8 +239,8 @@ PyObject* PyUniformCollectionType::mp_subscript(PyObject* obj, PyObject* key) {
|
||||||
int PyUniformCollectionType::mp_ass_subscript(PyObject* obj, PyObject* key, PyObject* value) {
|
int PyUniformCollectionType::mp_ass_subscript(PyObject* obj, PyObject* key, PyObject* value) {
|
||||||
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
||||||
|
|
||||||
if (!self->collection) {
|
if (!self->owner.lock() || !self->collection) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, "UniformCollection is not valid");
|
PyErr_SetString(PyExc_RuntimeError, "UniformCollection is not valid (owner destroyed)");
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -330,7 +330,7 @@ int PyUniformCollectionType::mp_ass_subscript(PyObject* obj, PyObject* key, PyOb
|
||||||
int PyUniformCollectionType::sq_contains(PyObject* obj, PyObject* key) {
|
int PyUniformCollectionType::sq_contains(PyObject* obj, PyObject* key) {
|
||||||
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
PyUniformCollectionObject* self = (PyUniformCollectionObject*)obj;
|
||||||
|
|
||||||
if (!self->collection) return 0;
|
if (!self->owner.lock() || !self->collection) return 0;
|
||||||
|
|
||||||
if (!PyUnicode_Check(key)) return 0;
|
if (!PyUnicode_Check(key)) return 0;
|
||||||
|
|
||||||
|
|
@ -339,7 +339,7 @@ int PyUniformCollectionType::sq_contains(PyObject* obj, PyObject* key) {
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyUniformCollectionType::keys(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
PyObject* PyUniformCollectionType::keys(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (!self->collection) {
|
if (!self->owner.lock() || !self->collection) {
|
||||||
return PyList_New(0);
|
return PyList_New(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +352,7 @@ PyObject* PyUniformCollectionType::keys(PyUniformCollectionObject* self, PyObjec
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyUniformCollectionType::values(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
PyObject* PyUniformCollectionType::values(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (!self->collection) {
|
if (!self->owner.lock() || !self->collection) {
|
||||||
return PyList_New(0);
|
return PyList_New(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +372,7 @@ PyObject* PyUniformCollectionType::values(PyUniformCollectionObject* self, PyObj
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyUniformCollectionType::items(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
PyObject* PyUniformCollectionType::items(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (!self->collection) {
|
if (!self->owner.lock() || !self->collection) {
|
||||||
return PyList_New(0);
|
return PyList_New(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -395,7 +395,7 @@ PyObject* PyUniformCollectionType::items(PyUniformCollectionObject* self, PyObje
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyUniformCollectionType::clear(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
PyObject* PyUniformCollectionType::clear(PyUniformCollectionObject* self, PyObject* Py_UNUSED(ignored)) {
|
||||||
if (self->collection) {
|
if (self->owner.lock() && self->collection) {
|
||||||
auto names = self->collection->getNames();
|
auto names = self->collection->getNames();
|
||||||
for (const auto& name : names) {
|
for (const auto& name : names) {
|
||||||
self->collection->remove(name);
|
self->collection->remove(name);
|
||||||
|
|
|
||||||
|
|
@ -83,8 +83,8 @@ private:
|
||||||
// Python object structure for UniformCollection
|
// Python object structure for UniformCollection
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
UniformCollection* collection; // Owned by UIDrawable, not by this object
|
UniformCollection* collection; // Owned by UIDrawable/UIEntity, not by this object
|
||||||
std::weak_ptr<UIDrawable> owner; // For checking validity
|
std::weak_ptr<void> owner; // Type-erased weak ref for validity checking
|
||||||
PyObject* weakreflist;
|
PyObject* weakreflist;
|
||||||
} PyUniformCollectionObject;
|
} PyUniformCollectionObject;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,28 @@ static UIDrawable* extractDrawable(PyObject* self, PyObjectsEnum objtype) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper to extract shared_ptr<UIDrawable> for ownership tracking
|
||||||
|
static std::shared_ptr<UIDrawable> extractDrawableShared(PyObject* self, PyObjectsEnum objtype) {
|
||||||
|
switch (objtype) {
|
||||||
|
case PyObjectsEnum::UIFRAME:
|
||||||
|
return ((PyUIFrameObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UICAPTION:
|
||||||
|
return ((PyUICaptionObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UISPRITE:
|
||||||
|
return ((PyUISpriteObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UIGRID:
|
||||||
|
return ((PyUIGridObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UILINE:
|
||||||
|
return ((PyUILineObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UICIRCLE:
|
||||||
|
return ((PyUICircleObject*)self)->data;
|
||||||
|
case PyObjectsEnum::UIARC:
|
||||||
|
return ((PyUIArcObject*)self)->data;
|
||||||
|
default:
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; }
|
UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; }
|
||||||
|
|
||||||
UIDrawable::UIDrawable(const UIDrawable& other)
|
UIDrawable::UIDrawable(const UIDrawable& other)
|
||||||
|
|
@ -1271,7 +1293,7 @@ PyObject* UIDrawable::get_uniforms(PyObject* self, void* closure) {
|
||||||
|
|
||||||
collection->collection = drawable->uniforms.get();
|
collection->collection = drawable->uniforms.get();
|
||||||
collection->weakreflist = NULL;
|
collection->weakreflist = NULL;
|
||||||
// Note: owner weak_ptr could be set here if we had access to shared_ptr
|
collection->owner = extractDrawableShared(self, objtype);
|
||||||
|
|
||||||
return (PyObject*)collection;
|
return (PyObject*)collection;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -140,6 +140,7 @@ static PyObject* UIEntity_get_uniforms(PyUIEntityObject* self, void* closure)
|
||||||
|
|
||||||
// The collection is owned by the sprite, we just provide a view
|
// The collection is owned by the sprite, we just provide a view
|
||||||
uniforms_obj->collection = self->data->sprite.uniforms.get();
|
uniforms_obj->collection = self->data->sprite.uniforms.get();
|
||||||
|
uniforms_obj->owner = self->data; // Entity shared_ptr keeps collection alive
|
||||||
uniforms_obj->weakreflist = NULL;
|
uniforms_obj->weakreflist = NULL;
|
||||||
|
|
||||||
return (PyObject*)uniforms_obj;
|
return (PyObject*)uniforms_obj;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue