[gjs/gnome-40: 8/30] object: Discard disposed GObject's and do not create wrappers for them
- From: Philip Chimento <pchimento src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs/gnome-40: 8/30] object: Discard disposed GObject's and do not create wrappers for them
- Date: Wed, 5 May 2021 20:41:26 +0000 (UTC)
commit 4b1a690032a880d04fb6ae68296d348a4ddb5db9
Author: Marco Trevisan (TreviƱo) <mail 3v1n0 net>
Date: Wed Mar 24 05:57:04 2021 +0100
object: Discard disposed GObject's and do not create wrappers for them
As per commit d37d6604 and commit c0003eb we may destroy an object
wrapper earlier than we used to do, this has some memory benefits but
we also need to make sure that we cleanup the object qdata so that it
never points to the just free'd object wrapper.
By doing this, however, we may end up re-associating a GObject that is
disposed but not yet finalized to another valid wrapper. This was allowed
before but wasn't really correct either because in such case it will be
just useless and won't be able to monitor its disposal again.
So, now during disposal notify save a qdata (that won't be removed
until finalization) on gobject marking it as disposed, then use this
data to prevent an object to be re-associated again to a valid wrapper.
In fact we just create a new wrapper that is marked as "disposed", and
so preventing most of actions to be performed.
We don't warn though, because this may just happen at the moment the
invalid object is incorrectly used.
As per this, we need to unset the object qdata only if the object is not
finalized, as we may still need to do it when it's finalized, or we'll
still crash when a disposed GObject will point to a wrapper that has been
already destroyed.
Added tests simulating this.
Fixes: #395
(cherry-picked from commit 1532cabd)
gi/arg.cpp | 11 +++-----
gi/function.cpp | 5 ++++
gi/object.cpp | 32 ++++++++++++++++++++--
gi/object.h | 4 +++
gi/value.cpp | 15 ++--------
installed-tests/js/testGObjectDestructionAccess.js | 14 ++++++++++
6 files changed, 59 insertions(+), 22 deletions(-)
---
diff --git a/gi/arg.cpp b/gi/arg.cpp
index 4574f98c..4f245c5a 100644
--- a/gi/arg.cpp
+++ b/gi/arg.cpp
@@ -2657,13 +2657,10 @@ gjs_value_from_g_argument (JSContext *context,
}
if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
- // Null arg is already handled above
- JSObject* obj = ObjectInstance::wrapper_from_gobject(
- context, G_OBJECT(gjs_arg_get<GObject*>(arg)));
- if (!obj)
- return false;
- value_p.setObject(*obj);
- return true;
+ g_assert(gjs_arg_get<void*>(arg) &&
+ "Null arg is already handled above");
+ return ObjectInstance::set_value_from_gobject(
+ context, gjs_arg_get<GObject*>(arg), value_p);
}
if (g_type_is_a(gtype, G_TYPE_BOXED) ||
diff --git a/gi/function.cpp b/gi/function.cpp
index c8d336de..e378f72b 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -365,6 +365,11 @@ void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) {
if (gobj) {
this_object = ObjectInstance::wrapper_from_gobject(context, gobj);
if (!this_object) {
+ if (g_object_get_qdata(gobj, ObjectBase::disposed_quark())) {
+ warn_about_illegal_js_callback(
+ "on disposed object",
+ "using the destroy(), dispose(), or remove() vfuncs");
+ }
gjs_log_exception(context);
return;
}
diff --git a/gi/object.cpp b/gi/object.cpp
index 74001f92..a7757770 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -10,6 +10,7 @@
#include <algorithm> // for find
#include <functional> // for mem_fn
+#include <limits>
#include <string>
#include <tuple> // for tie
#include <utility> // for move
@@ -46,6 +47,7 @@
#include "gi/object.h"
#include "gi/repo.h"
#include "gi/toggle.h"
+#include "gi/utils-inl.h" // for gjs_int_to_pointer
#include "gi/value.h"
#include "gi/wrapperutils.h"
#include "gjs/atoms.h"
@@ -260,7 +262,9 @@ ObjectInstance::set_object_qdata(void)
void
ObjectInstance::unset_object_qdata(void)
{
- g_object_steal_qdata(m_ptr, gjs_object_priv_quark());
+ auto priv_quark = gjs_object_priv_quark();
+ if (g_object_get_qdata(m_ptr, priv_quark) == this)
+ g_object_steal_qdata(m_ptr, priv_quark);
}
GParamSpec* ObjectPrototype::find_param_spec_from_id(JSContext* cx,
@@ -1125,6 +1129,7 @@ ObjectInstance::gobj_dispose_notify(void)
{
m_gobj_disposed = true;
+ unset_object_qdata();
track_gobject_finalization();
if (m_uses_toggle_ref) {
@@ -1479,11 +1484,13 @@ ObjectInstance::associate_js_gobject(JSContext *context,
m_ptr = gobj;
set_object_qdata();
m_wrapper = object;
+ m_gobj_disposed = !!g_object_get_qdata(gobj, ObjectBase::disposed_quark());
ensure_weak_pointer_callback(context);
link();
- g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this);
+ if (!G_UNLIKELY(m_gobj_disposed))
+ g_object_weak_ref(gobj, wrapped_gobj_dispose_notify, this);
}
void
@@ -1550,9 +1557,10 @@ ObjectInstance::disassociate_js_gobject(void)
m_ptr.get(), type_name());
}
- if (!m_gobj_disposed) {
+ if (!m_gobj_disposed)
g_object_weak_unref(m_ptr.get(), wrapped_gobj_dispose_notify, this);
+ if (!m_gobj_finalized) {
/* Fist, remove the wrapper pointer from the wrapped GObject */
unset_object_qdata();
}
@@ -2497,6 +2505,24 @@ JSObject* ObjectInstance::wrapper_from_gobject(JSContext* cx, GObject* gobj) {
return priv->wrapper();
}
+bool ObjectInstance::set_value_from_gobject(JSContext* cx, GObject* gobj,
+ JS::MutableHandleValue value_p) {
+ if (!gobj) {
+ value_p.setNull();
+ return true;
+ }
+
+ auto* wrapper = ObjectInstance::wrapper_from_gobject(cx, gobj);
+ if (wrapper) {
+ value_p.setObject(*wrapper);
+ return true;
+ }
+
+ gjs_throw(cx, "Failed to find JS object for GObject %p of type %s", gobj,
+ g_type_name(G_TYPE_FROM_INSTANCE(gobj)));
+ return false;
+}
+
// Replaces GIWrapperBase::to_c_ptr(). The GIWrapperBase version is deleted.
bool ObjectBase::to_c_ptr(JSContext* cx, JS::HandleObject obj, GObject** ptr) {
g_assert(ptr);
diff --git a/gi/object.h b/gi/object.h
index ef875391..8c1d255e 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -362,6 +362,10 @@ class ObjectInstance : public GIWrapperInstance<ObjectBase, ObjectPrototype,
GJS_JSAPI_RETURN_CONVENTION
static JSObject* wrapper_from_gobject(JSContext* cx, GObject* ptr);
+ GJS_JSAPI_RETURN_CONVENTION
+ static bool set_value_from_gobject(JSContext* cx, GObject*,
+ JS::MutableHandleValue);
+
/* Methods to manipulate the list of closures */
private:
diff --git a/gi/value.cpp b/gi/value.cpp
index 5447dc96..b54dea66 100644
--- a/gi/value.cpp
+++ b/gi/value.cpp
@@ -828,18 +828,9 @@ gjs_value_from_g_value_internal(JSContext *context,
v = g_value_get_boolean(gvalue);
value_p.setBoolean(!!v);
} else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
- GObject *gobj;
-
- gobj = (GObject*) g_value_get_object(gvalue);
-
- if (gobj) {
- JSObject* obj = ObjectInstance::wrapper_from_gobject(context, gobj);
- if (!obj)
- return false;
- value_p.setObject(*obj);
- } else {
- value_p.setNull();
- }
+ return ObjectInstance::set_value_from_gobject(
+ context, static_cast<GObject*>(g_value_get_object(gvalue)),
+ value_p);
} else if (gtype == G_TYPE_STRV) {
if (!gjs_array_from_strv (context,
value_p,
diff --git a/installed-tests/js/testGObjectDestructionAccess.js
b/installed-tests/js/testGObjectDestructionAccess.js
index 9a59ac0d..28901460 100644
--- a/installed-tests/js/testGObjectDestructionAccess.js
+++ b/installed-tests/js/testGObjectDestructionAccess.js
@@ -142,6 +142,20 @@ describe('Access to destroyed GObject', function () {
});
describe('Disposed or finalized GObject', function () {
+ it('is marked as disposed when it is a manually disposed property', function () {
+ const emblem = new Gio.EmblemedIcon({
+ gicon: new Gio.ThemedIcon({ name: 'alarm' }),
+ });
+
+ let { gicon } = emblem;
+ gicon.run_dispose();
+ gicon = null;
+ System.gc();
+
+ expect(emblem.gicon.toString()).toMatch(
+ /\[object \(DISPOSED\) instance wrapper .* jsobj@0x[a-f0-9]+ native@0x[a-f0-9]+\]/);
+ });
+
it('generates a warn on object garbage collection', function () {
Gio.File.new_for_path('/').unref();
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]