diff --git a/Makefile-test.am b/Makefile-test.am index 5ea78ad53cbcabbde612e43db4af3ecd55ebc997..5c9f428ce0d9c766d4e4b5e3b163df73694df323 100644 --- a/Makefile-test.am +++ b/Makefile-test.am @@ -199,6 +199,7 @@ common_jstests_files = \ installed-tests/js/testGIMarshalling.js \ installed-tests/js/testGLib.js \ installed-tests/js/testGObjectClass.js \ + installed-tests/js/testGObjectDestructionAccess.js \ installed-tests/js/testGObjectInterface.js \ installed-tests/js/testGTypeClass.js \ installed-tests/js/testGio.js \ diff --git a/gi/object.cpp b/gi/object.cpp index 94c8ee29ad50bc52d1fcd01752eba37be1519f2c..69bba126b240098c2f0b3bf34f38a8be9434c668 100644 --- a/gi/object.cpp +++ b/gi/object.cpp @@ -69,6 +69,7 @@ struct ObjectInstance { GTypeClass *klass; unsigned js_object_finalized : 1; + unsigned g_object_finalized : 1; }; static std::stack object_init_list; @@ -390,6 +391,15 @@ object_instance_get_prop(JSContext *context, if (priv->gobj == NULL) /* prototype, not an instance. */ return true; + if (priv->g_object_finalized) { + gjs_throw(context, "Object %s.%s (%p), has been already finalized. " + "Impossible to get any property from it.", + priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "", + priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype), + priv->gobj); + return false; + } + if (!get_prop_from_g_param(context, obj, priv, name, value_p)) return false; @@ -500,6 +510,16 @@ object_instance_set_prop(JSContext *context, if (priv->gobj == NULL) /* prototype, not an instance. */ return result.succeed(); + if (priv->g_object_finalized) { + gjs_throw(context, "Object %s.%s (%p), has been already finalized. " + "Impossible to set any property to it.", + priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "", + priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype), + priv->gobj); + + return false; + } + ret = set_g_param_from_prop(context, priv, name, g_param_was_set, value_p, result); if (g_param_was_set || !ret) return ret; @@ -737,6 +757,18 @@ object_instance_resolve(JSContext *context, return true; } + if (priv->g_object_finalized) { + *resolved = false; + + gjs_throw(context, "Object %s.%s (%p), has been already finalized. " + "Impossible to resolve it.", + priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "", + priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype), + priv->gobj); + + return false; + } + /* If we have no GIRepository information (we're a JS GObject subclass), * we need to look at exposing interfaces. Look up our interfaces through * GType data, and then hope that *those* are introspectable. */ @@ -920,7 +952,11 @@ static void wrapped_gobj_dispose_notify(gpointer data, GObject *where_the_object_was) { - wrapped_gobject_list.erase(static_cast(data)); + auto *priv = static_cast(data); + + priv->g_object_finalized = true; + priv->keep_alive.reset(); + wrapped_gobject_list.erase(priv); #if DEBUG_DISPOSE gjs_debug(GJS_DEBUG_GOBJECT, "Wrapped GObject %p disposed", where_the_object_was); #endif @@ -1421,6 +1457,15 @@ object_instance_trace(JSTracer *tracer, if (priv == NULL) return; + if (priv->g_object_finalized) { + g_debug("Object %s.%s (%p), has been already finalized. " + "Impossible to trace it.", + priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "", + priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype), + priv->gobj); + return; + } + for (GClosure *closure : priv->closures) gjs_closure_trace(closure, tracer); } @@ -2115,6 +2160,18 @@ gjs_typecheck_object(JSContext *context, return false; } + if (priv->g_object_finalized) { + gjs_throw(context, + "Object %s.%s (%p), has been already deallocated - impossible to access to it. " + "This might be caused by the fact that the object has been destroyed from C " + "code using something such as destroy(), dispose(), or remove() vfuncs", + priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "", + priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype), + priv->gobj); + + return false; + } + g_assert(priv->gtype == G_OBJECT_TYPE(priv->gobj)); if (expected_type != G_TYPE_NONE) diff --git a/installed-tests/js/testGObjectDestructionAccess.js b/installed-tests/js/testGObjectDestructionAccess.js new file mode 100644 index 0000000000000000000000000000000000000000..786abe42516e64e5f4e4d75c33510b820a9ce47f --- /dev/null +++ b/installed-tests/js/testGObjectDestructionAccess.js @@ -0,0 +1,41 @@ +// -*- mode: js; indent-tabs-mode: nil -*- +imports.gi.versions.Gtk = '3.0'; + +const Gtk = imports.gi.Gtk; + +describe('Access to destroyed GObject', () => { + let destroyedWindow; + + beforeAll(() => { + Gtk.init(null); + }); + + beforeEach(() => { + destroyedWindow = new Gtk.Window({type: Gtk.WindowType.TOPLEVEL}); + destroyedWindow.destroy(); + }); + + it('Get property', () => { + expect(() => { + let title = destroyedWindow.title; + }).toThrowError(/Object Gtk.Window \(0x[a-f0-9]+\), has been already finalized. Impossible to get any property from it./) + }); + + it('Set property', () => { + expect(() => { + destroyedWindow.title = 'I am dead'; + }).toThrowError(/Object Gtk.Window \(0x[a-f0-9]+\), has been already finalized. Impossible to set any property to it./) + }); + + it('Access to getter method', () => { + expect(() => { + let title = destroyedWindow.get_title(); + }).toThrowError(/Object Gtk.Window \(0x[a-f0-9]+\), has been already deallocated.*/) + }); + + it('Access to setter method', () => { + expect(() => { + destroyedWindow.set_title('I am dead'); + }).toThrowError(/Object Gtk.Window \(0x[a-f0-9]+\), has been already deallocated.*/) + }); +});