summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMarco Trevisan (TreviƱo) <mail@3v1n0.net>2020-09-03 23:18:12 +0200
committerPhilip Chimento <philip.chimento@gmail.com>2020-09-20 15:03:03 -0700
commitcecbb4984967d3effb9bf84dd6e3c236d1b70e6f (patch)
tree3bf96302d74f76613d5596783e8fdd3d64c9ecca
parent7f8cd63e7adfac3c0843406af4a93249e5a94705 (diff)
downloadgjs-cecbb4984967d3effb9bf84dd6e3c236d1b70e6f.tar.gz
function: Use c++ features to handle GjsCallbackTrampoline
Make the struct a bit smarter, including auto pointers and methods to access to the internal values. Still manage the object creation and reference counting externally.
-rw-r--r--gi/arg-cache.cpp4
-rw-r--r--gi/function.cpp195
-rw-r--r--gi/function.h34
-rw-r--r--gi/object.cpp12
4 files changed, 137 insertions, 108 deletions
diff --git a/gi/arg-cache.cpp b/gi/arg-cache.cpp
index ac356abb..9107deaa 100644
--- a/gi/arg-cache.cpp
+++ b/gi/arg-cache.cpp
@@ -316,9 +316,9 @@ static bool gjs_marshal_callback_in(JSContext* cx, GjsArgumentCache* self,
return false;
}
- priv->associate_closure(cx, trampoline->js_function);
+ priv->associate_closure(cx, trampoline->js_function());
}
- closure = trampoline->closure;
+ closure = trampoline->closure();
}
if (self->has_callback_destroy()) {
diff --git a/gi/function.cpp b/gi/function.cpp
index 5adacc62..82fa810d 100644
--- a/gi/function.cpp
+++ b/gi/function.cpp
@@ -28,7 +28,6 @@
#include <string.h> // for strcmp, memset, size_t
#include <memory> // for unique_ptr
-#include <new>
#include <string>
#include <type_traits>
#include <vector>
@@ -102,14 +101,8 @@ GjsCallbackTrampoline* gjs_callback_trampoline_ref(
void
gjs_callback_trampoline_unref(GjsCallbackTrampoline *trampoline)
{
- if (g_atomic_ref_count_dec(&trampoline->ref_count)) {
- g_clear_pointer(&trampoline->js_function, g_closure_unref);
- if (trampoline->info && trampoline->closure)
- g_callable_info_free_closure(trampoline->info, trampoline->closure);
- g_clear_pointer(&trampoline->info, g_base_info_unref);
- g_free (trampoline->param_types);
- g_free(trampoline);
- }
+ if (g_atomic_ref_count_dec(&trampoline->ref_count))
+ delete trampoline;
}
template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
@@ -210,20 +203,14 @@ set_return_ffi_arg_from_giargument (GITypeInfo *ret_type,
}
}
-static void
-warn_about_illegal_js_callback(const GjsCallbackTrampoline *trampoline,
- const char *when,
- const char *reason)
-{
+void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when,
+ const char* reason) {
g_critical("Attempting to run a JS callback %s. This is most likely caused "
"by %s. Because it would crash the application, it has been "
"blocked.", when, reason);
- if (trampoline->info) {
- const char *name = g_base_info_get_name(trampoline->info);
- g_critical("The offending callback was %s()%s.", name,
- trampoline->is_vfunc ? ", a vfunc" : "");
- }
- return;
+ if (m_info)
+ g_critical("The offending callback was %s()%s.", m_info.name(),
+ m_is_vfunc ? ", a vfunc" : "");
}
/* This is our main entry point for ffi_closure callbacks.
@@ -233,30 +220,26 @@ warn_about_illegal_js_callback(const GjsCallbackTrampoline *trampoline,
* In other words, everything we need to call the JS function and
* getting the return value back.
*/
-static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
- void** ffi_args, void* data) {
+void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) {
JSContext *context;
int i, n_args, n_jsargs, n_outargs, c_args_offset = 0;
GITypeInfo ret_type;
bool success = false;
- auto args = reinterpret_cast<GIArgument **>(ffi_args);
-
- g_assert(data && "Trampoline data is not set");
- GjsAutoCallbackTrampoline trampoline(
- static_cast<GjsCallbackTrampoline*>(data), GjsAutoTakeOwnership());
- if (G_UNLIKELY(!gjs_closure_is_valid(trampoline->js_function))) {
- warn_about_illegal_js_callback(trampoline, "during shutdown",
+ if (G_UNLIKELY(!gjs_closure_is_valid(m_js_function))) {
+ warn_about_illegal_js_callback(
+ "during shutdown",
"destroying a Clutter actor or GTK widget with ::destroy signal "
"connected, or using the destroy(), dispose(), or remove() vfuncs");
gjs_dumpstack();
return;
}
- context = gjs_closure_get_context(trampoline->js_function);
+ context = gjs_closure_get_context(m_js_function);
GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
if (G_UNLIKELY(gjs->sweeping())) {
- warn_about_illegal_js_callback(trampoline, "during garbage collection",
+ warn_about_illegal_js_callback(
+ "during garbage collection",
"destroying a Clutter actor or GTK widget with ::destroy signal "
"connected, or using the destroy(), dispose(), or remove() vfuncs");
gjs_dumpstack();
@@ -264,21 +247,21 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
}
if (G_UNLIKELY(!gjs->is_owner_thread())) {
- warn_about_illegal_js_callback(trampoline, "on a different thread",
- "an API not intended to be used in JS");
+ warn_about_illegal_js_callback("on a different thread",
+ "an API not intended to be used in JS");
return;
}
- JSAutoRealm ar(context, JS_GetFunctionObject(gjs_closure_get_callable(
- trampoline->js_function)));
+ JSAutoRealm ar(
+ context, JS_GetFunctionObject(gjs_closure_get_callable(m_js_function)));
- bool can_throw_gerror = g_callable_info_can_throw_gerror(trampoline->info);
- n_args = g_callable_info_get_n_args(trampoline->info);
+ bool can_throw_gerror = g_callable_info_can_throw_gerror(m_info);
+ n_args = m_param_types.size();
g_assert(n_args >= 0);
JS::RootedObject this_object(context);
- if (trampoline->is_vfunc) {
+ if (m_is_vfunc) {
GObject* gobj = G_OBJECT(gjs_arg_get<GObject*>(args[0]));
if (gobj) {
this_object = ObjectInstance::wrapper_from_gobject(context, gobj);
@@ -301,7 +284,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
JS::RootedValue rval(context);
- g_callable_info_load_return_type(trampoline->info, &ret_type);
+ g_callable_info_load_return_type(m_info, &ret_type);
bool ret_type_is_void = g_type_info_get_tag (&ret_type) == GI_TYPE_TAG_VOID;
for (i = 0, n_jsargs = 0; i < n_args; i++) {
@@ -309,7 +292,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
GITypeInfo type_info;
GjsParamType param_type;
- g_callable_info_load_arg(trampoline->info, i, &arg_info);
+ g_callable_info_load_arg(m_info, i, &arg_info);
g_arg_info_load_type(&arg_info, &type_info);
/* Skip void * arguments */
@@ -324,7 +307,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT)
n_outargs++;
- param_type = trampoline->param_types[i];
+ param_type = m_param_types[i];
switch (param_type) {
case PARAM_SKIPPED:
@@ -335,7 +318,8 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
GITypeInfo arg_type_info;
JS::RootedValue length(context);
- g_callable_info_load_arg(trampoline->info, array_length_pos, &array_length_arg);
+ g_callable_info_load_arg(m_info, array_length_pos,
+ &array_length_arg);
g_arg_info_load_type(&array_length_arg, &arg_type_info);
if (!gjs_value_from_g_argument(context, &length, &arg_type_info,
args[array_length_pos + c_args_offset],
@@ -375,8 +359,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
}
}
- if (!gjs_closure_invoke(trampoline->js_function, this_object, jsargs, &rval,
- true))
+ if (!gjs_closure_invoke(m_js_function, this_object, jsargs, &rval, true))
goto out;
if (n_outargs == 0 && ret_type_is_void) {
@@ -385,7 +368,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
GIArgument argument;
GITransfer transfer;
- transfer = g_callable_info_get_caller_owns (trampoline->info);
+ transfer = g_callable_info_get_caller_owns(m_info);
/* non-void return value, no out args. Should
* be a single return value. */
if (!gjs_value_to_g_argument(context,
@@ -406,7 +389,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
* be a single return value. */
for (i = 0; i < n_args; i++) {
GIArgInfo arg_info;
- g_callable_info_load_arg(trampoline->info, i, &arg_info);
+ g_callable_info_load_arg(m_info, i, &arg_info);
if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
continue;
@@ -422,13 +405,12 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
goto out;
if (!is_array) {
- JSFunction* fn = gjs_closure_get_callable(trampoline->js_function);
+ JSFunction* fn = gjs_closure_get_callable(m_js_function);
gjs_throw(context,
"Function %s (%s.%s) returned unexpected value, "
"expecting an Array",
gjs_debug_string(JS_GetFunctionDisplayId(fn)).c_str(),
- g_base_info_get_namespace(trampoline->info),
- g_base_info_get_name(trampoline->info));
+ m_info.ns(), m_info.name());
goto out;
}
@@ -440,7 +422,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
if (!ret_type_is_void) {
GIArgument argument;
- GITransfer transfer = g_callable_info_get_caller_owns(trampoline->info);
+ GITransfer transfer = g_callable_info_get_caller_owns(m_info);
if (!JS_GetElement(context, out_array, elem_idx, &elem))
goto out;
@@ -459,7 +441,7 @@ static void gjs_callback_closure(ffi_cif* cif [[maybe_unused]], void* result,
for (i = 0; i < n_args; i++) {
GIArgInfo arg_info;
- g_callable_info_load_arg(trampoline->info, i, &arg_info);
+ g_callable_info_load_arg(m_info, i, &arg_info);
if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
continue;
@@ -488,17 +470,16 @@ out:
exit(code);
/* Some other uncatchable exception, e.g. out of memory */
- JSFunction* fn = gjs_closure_get_callable(trampoline->js_function);
+ JSFunction* fn = gjs_closure_get_callable(m_js_function);
g_error("Function %s (%s.%s) terminated with uncatchable exception",
gjs_debug_string(JS_GetFunctionDisplayId(fn)).c_str(),
- g_base_info_get_namespace(trampoline->info),
- g_base_info_get_name(trampoline->info));
+ m_info.ns(), m_info.name());
}
/* Fill in the result with some hopefully neutral value */
if (!ret_type_is_void) {
GIArgument argument = {};
- g_callable_info_load_return_type(trampoline->info, &ret_type);
+ g_callable_info_load_return_type(m_info, &ret_type);
gjs_gi_argument_init_default(&ret_type, &argument);
set_return_ffi_arg_from_giargument(&ret_type, result, &argument);
}
@@ -523,10 +504,10 @@ out:
gjs_log_exception_uncaught(context);
}
- if (trampoline->scope == GI_SCOPE_TYPE_ASYNC) {
+ if (m_scope == GI_SCOPE_TYPE_ASYNC) {
// We don't release the trampoline here as we've an extra ref that has
// been set in gjs_marshal_callback_in()
- completed_trampolines.emplace_back(trampoline.get());
+ completed_trampolines.emplace_back(this);
}
gjs->schedule_gc_if_needed();
@@ -536,31 +517,51 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(
JSContext* context, JS::HandleFunction function,
GICallableInfo* callable_info, GIScopeType scope, bool has_scope_object,
bool is_vfunc) {
- GjsAutoCallbackTrampoline trampoline;
- int n_args, i;
-
g_assert(function);
- trampoline = g_new(GjsCallbackTrampoline, 1);
- new (trampoline) GjsCallbackTrampoline();
- g_atomic_ref_count_init(&trampoline->ref_count);
- trampoline->info = callable_info;
- g_base_info_ref((GIBaseInfo*)trampoline->info);
+ GjsAutoCallbackTrampoline trampoline =
+ new GjsCallbackTrampoline(callable_info, scope, is_vfunc);
- /* Analyze param types and directions, similarly to init_cached_function_data */
- n_args = g_callable_info_get_n_args(trampoline->info);
- trampoline->param_types = g_new0(GjsParamType, n_args);
+ if (!trampoline->initialize(context, function, has_scope_object))
+ return nullptr;
- for (i = 0; i < n_args; i++) {
+ return trampoline.release();
+}
+
+GjsCallbackTrampoline::GjsCallbackTrampoline(GICallableInfo* callable_info,
+ GIScopeType scope, bool is_vfunc)
+ : m_info(g_base_info_ref(callable_info)),
+ m_scope(scope),
+ m_param_types(g_callable_info_get_n_args(callable_info), {}),
+ m_is_vfunc(is_vfunc) {
+ g_atomic_ref_count_init(&ref_count);
+}
+
+GjsCallbackTrampoline::~GjsCallbackTrampoline() {
+ g_assert(g_atomic_ref_count_compare(&ref_count, 0));
+
+ if (m_info && m_closure)
+ g_callable_info_free_closure(m_info, m_closure);
+}
+
+bool GjsCallbackTrampoline::initialize(JSContext* cx,
+ JS::HandleFunction function,
+ bool has_scope_object) {
+ g_assert(!m_js_function);
+ g_assert(!m_closure);
+
+ /* Analyze param types and directions, similarly to
+ * init_cached_function_data */
+ for (size_t i = 0; i < m_param_types.size(); i++) {
GIDirection direction;
GIArgInfo arg_info;
GITypeInfo type_info;
GITypeTag type_tag;
- if (trampoline->param_types[i] == PARAM_SKIPPED)
+ if (m_param_types[i] == PARAM_SKIPPED)
continue;
- g_callable_info_load_arg(trampoline->info, i, &arg_info);
+ g_callable_info_load_arg(m_info, i, &arg_info);
g_arg_info_load_type(&arg_info, &type_info);
direction = g_arg_info_get_direction(&arg_info);
@@ -578,52 +579,62 @@ GjsCallbackTrampoline* gjs_callback_trampoline_new(
g_type_info_get_interface(&type_info);
interface_type = g_base_info_get_type(interface_info);
if (interface_type == GI_INFO_TYPE_CALLBACK) {
- gjs_throw(context,
+ gjs_throw(cx,
"%s %s accepts another callback as a parameter. This "
"is not supported",
- is_vfunc ? "VFunc" : "Callback",
- g_base_info_get_name(callable_info));
- return NULL;
+ m_is_vfunc ? "VFunc" : "Callback", m_info.name());
+ return false;
}
} else if (type_tag == GI_TYPE_TAG_ARRAY) {
if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
int array_length_pos = g_type_info_get_array_length(&type_info);
- if (array_length_pos >= 0 && array_length_pos < n_args) {
+ if (array_length_pos < 0)
+ continue;
+
+ if (static_cast<size_t>(array_length_pos) <
+ m_param_types.size()) {
GIArgInfo length_arg_info;
- g_callable_info_load_arg(trampoline->info, array_length_pos, &length_arg_info);
+ g_callable_info_load_arg(m_info, array_length_pos,
+ &length_arg_info);
if (g_arg_info_get_direction(&length_arg_info) != direction) {
- gjs_throw(context,
+ gjs_throw(cx,
"%s %s has an array with different-direction "
"length argument. This is not supported",
- is_vfunc ? "VFunc" : "Callback",
- g_base_info_get_name(callable_info));
- return NULL;
+ m_is_vfunc ? "VFunc" : "Callback",
+ m_info.name());
+ return false;
}
- trampoline->param_types[array_length_pos] = PARAM_SKIPPED;
- trampoline->param_types[i] = PARAM_ARRAY;
+ m_param_types[array_length_pos] = PARAM_SKIPPED;
+ m_param_types[i] = PARAM_ARRAY;
}
}
}
}
- trampoline->closure = g_callable_info_prepare_closure(callable_info, &trampoline->cif,
- gjs_callback_closure, trampoline);
+ m_closure = g_callable_info_prepare_closure(
+ m_info, &m_cif,
+ [](ffi_cif*, void* result, void** ffi_args, void* data) {
+ auto** args = reinterpret_cast<GIArgument**>(ffi_args);
+ g_assert(data && "Trampoline data is not set");
+ GjsAutoCallbackTrampoline trampoline(
+ static_cast<GjsCallbackTrampoline*>(data),
+ GjsAutoTakeOwnership());
+
+ trampoline->callback_closure(args, result);
+ },
+ this);
// The rule is:
// - notify callbacks in GObject methods are traced from the scope object
// - async and call callbacks, and other notify callbacks, are rooted
// - vfuncs are traced from the GObject prototype
- bool should_root = scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object;
- trampoline->js_function = gjs_closure_new(
- context, function, g_base_info_get_name(callable_info), should_root);
-
- trampoline->scope = scope;
- trampoline->is_vfunc = is_vfunc;
+ bool should_root = m_scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object;
+ m_js_function = gjs_closure_new(cx, function, m_info.name(), should_root);
- return trampoline.release();
+ return true;
}
/* Intended for error messages. Return value must be freed */
diff --git a/gi/function.h b/gi/function.h
index f50b747a..75f71486 100644
--- a/gi/function.h
+++ b/gi/function.h
@@ -26,6 +26,8 @@
#include <config.h>
+#include <vector>
+
#include <ffi.h>
#include <girepository.h>
#include <glib-object.h>
@@ -49,17 +51,35 @@ typedef enum {
PARAM_UNKNOWN,
} GjsParamType;
+using GjsAutoGClosure =
+ GjsAutoPointer<GClosure, GClosure, g_closure_unref, g_closure_ref>;
+
struct GjsCallbackTrampoline {
+ GjsCallbackTrampoline(GICallableInfo* callable_info, GIScopeType scope,
+ bool is_vfunc);
+ ~GjsCallbackTrampoline();
+
+ constexpr GClosure* js_function() { return m_js_function; }
+ constexpr ffi_closure* closure() { return m_closure; }
+
gatomicrefcount ref_count;
- GICallableInfo *info;
- GClosure *js_function;
+ bool initialize(JSContext* cx, JS::HandleFunction function,
+ bool has_scope_object);
+
+ private:
+ void callback_closure(GIArgument** args, void* result);
+ void warn_about_illegal_js_callback(const char* when, const char* reason);
+
+ GjsAutoCallableInfo m_info;
+ GjsAutoGClosure m_js_function;
+
+ ffi_closure* m_closure = nullptr;
+ GIScopeType m_scope;
+ std::vector<GjsParamType> m_param_types;
- ffi_cif cif;
- ffi_closure *closure;
- GIScopeType scope;
- bool is_vfunc;
- GjsParamType *param_types;
+ bool m_is_vfunc;
+ ffi_cif m_cif;
};
GJS_JSAPI_RETURN_CONVENTION
diff --git a/gi/object.cpp b/gi/object.cpp
index d0fd4915..fd3292a2 100644
--- a/gi/object.cpp
+++ b/gi/object.cpp
@@ -1461,8 +1461,6 @@ static void invalidate_closure_list(std::forward_list<GClosure*>* closures) {
// invalidation mechanism, but adding a temporary reference to
// ensure that the closure is still valid when calling invalidation
// notify callbacks
- using GjsAutoGClosure =
- GjsAutoPointer<GClosure, GClosure, g_closure_unref, g_closure_ref>;
GjsAutoGClosure closure(closures->front(), GjsAutoTakeOwnership());
g_closure_invalidate(closure);
/* Erase element if not already erased */
@@ -2624,19 +2622,19 @@ bool ObjectPrototype::hook_up_vfunc_impl(JSContext* cx,
// This is traced, and will be cleared from the list when the closure is
// invalidated
g_assert(std::find(m_vfuncs.begin(), m_vfuncs.end(),
- trampoline->js_function) == m_vfuncs.end() &&
+ trampoline->js_function()) == m_vfuncs.end() &&
"This vfunc was already associated with this class");
- m_vfuncs.push_front(trampoline->js_function);
+ m_vfuncs.push_front(trampoline->js_function());
g_closure_add_invalidate_notifier(
- trampoline->js_function, this,
+ trampoline->js_function(), this,
&ObjectPrototype::vfunc_invalidated_notify);
g_closure_add_invalidate_notifier(
- trampoline->js_function, trampoline, [](void* data, GClosure*) {
+ trampoline->js_function(), trampoline, [](void* data, GClosure*) {
auto* trampoline = static_cast<GjsCallbackTrampoline*>(data);
gjs_callback_trampoline_unref(trampoline);
});
- *((ffi_closure **)method_ptr) = trampoline->closure;
+ *reinterpret_cast<ffi_closure**>(method_ptr) = trampoline->closure();
}
return true;