[gjs] Support for callback functions
- From: Johan Dahlin <johan src gnome org>
- To: svn-commits-list gnome org
- Cc:
- Subject: [gjs] Support for callback functions
- Date: Wed, 2 Dec 2009 21:21:28 +0000 (UTC)
commit 5231f84d62d48736f22dc97f1f8d5c5374fbef2e
Author: Maxim Ermilov <zaspire rambler ru>
Date: Wed Dec 2 20:08:28 2009 +0300
Support for callback functions
https://bugzilla.gnome.org/show_bug.cgi?id=563025
gi/function.c | 433 +++++++++++++++++++++++++++++++++++++++-
test/js/testEverythingBasic.js | 37 ++++
2 files changed, 462 insertions(+), 8 deletions(-)
---
diff --git a/gi/function.c b/gi/function.c
index 5bdaaee..45e99b3 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -35,6 +35,11 @@
#include <jsapi.h>
#include <girepository.h>
+#include <girffi.h>
+#include <sys/mman.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
typedef struct {
GIFunctionInfo *info;
@@ -43,6 +48,26 @@ typedef struct {
static struct JSClass gjs_function_class;
+typedef struct {
+ GSList *arguments; /* List of jsval, that need to be unrooted after call*/
+ GSList *invoke_infos; /* gjs_callback_invoke_info_free for each*/
+ gint arg_index;
+} GjsCallbackInfo;
+
+typedef struct {
+ JSContext *context;
+ GICallableInfo *info;
+ jsval function;
+ ffi_cif *cif;
+ gboolean need_free_arg_types;
+ ffi_closure *closure;
+ GjsCallbackInfo callback_info;
+/* We can not munmap closure in ffi_callback.
+ * This memory page will be used after it.
+ * So we need to know when GjsCallbackInvokeInfo becoming unneeded.*/
+ gboolean in_use;
+} GjsCallbackInvokeInfo;
+
GJS_DEFINE_PRIV_FROM_JS(Function, gjs_function_class)
/*
@@ -83,6 +108,333 @@ function_new_resolve(JSContext *context,
return JS_TRUE;
}
+static void gjs_callback_invoke_info_free_content(GjsCallbackInvokeInfo *invoke_info);
+
+static void
+gjs_callback_invoke_info_free (gpointer data)
+{
+ GjsCallbackInvokeInfo *invoke_info = (GjsCallbackInvokeInfo *)data;
+
+ gjs_callback_invoke_info_free_content(invoke_info);
+ if (invoke_info->info)
+ g_base_info_unref(invoke_info->info);
+ munmap(invoke_info->closure, sizeof(ffi_closure));
+ if (invoke_info->need_free_arg_types)
+ g_free(invoke_info->cif->arg_types);
+ g_slice_free(ffi_cif, invoke_info->cif);
+
+ g_slice_free(GjsCallbackInvokeInfo, data);
+}
+
+static void
+gjs_callback_invoke_info_free_content(GjsCallbackInvokeInfo *invoke_info)
+{
+ GSList *l;
+
+ for (l = invoke_info->callback_info.arguments; l; l = g_slist_next(l)) {
+ jsval *val = l->data;
+ JS_RemoveRoot(invoke_info->context, val);
+ g_slice_free(jsval, val);
+ }
+ g_slist_free(invoke_info->callback_info.arguments);
+ invoke_info->callback_info.arguments = NULL;
+
+ g_slist_foreach(invoke_info->callback_info.invoke_infos, gjs_callback_invoke_info_free, NULL);
+ g_slist_free(invoke_info->callback_info.invoke_infos);
+ invoke_info->callback_info.invoke_infos = NULL;
+}
+
+static void
+invoke_info_for_js_callback(ffi_cif *cif,
+ void *result,
+ void **args,
+ void *data)
+{
+ GjsCallbackInvokeInfo *invoke_info = (GjsCallbackInvokeInfo *) data;
+ int i, n_args;
+ jsval *jsargs, rval;
+ GITypeInfo *ret_type;
+
+ g_assert(invoke_info != NULL);
+
+ n_args = g_callable_info_get_n_args(invoke_info->info);
+
+ g_assert(n_args >= 0);
+
+ jsargs = (jsval*)g_new0(jsval, n_args);
+ for (i = 0; i < n_args; i++) {
+ GIArgInfo *arg_info;
+ GITypeInfo *type_info;
+
+ arg_info = g_callable_info_get_arg(invoke_info->info, i);
+ type_info = g_arg_info_get_type(arg_info);
+
+ if (g_type_info_get_tag(type_info) == GI_TYPE_TAG_VOID) {
+ jsargs[i] = *((jsval*)args[i]);
+ } else if (!gjs_value_from_g_argument(invoke_info->context,
+ &jsargs[i],
+ type_info,
+ args[i])) {
+ gjs_throw(invoke_info->context, "could not convert argument of callback");
+ g_free(jsargs);
+ return;
+ }
+ g_base_info_unref((GIBaseInfo*)arg_info);
+ g_base_info_unref((GIBaseInfo*)type_info);
+ }
+
+ if (!JS_CallFunctionValue(invoke_info->context,
+ NULL,
+ invoke_info->function,
+ n_args,
+ jsargs,
+ &rval)) {
+ gjs_throw(invoke_info->context, "Couldn't call callback");
+ g_free(jsargs);
+ return;
+ }
+
+ ret_type = g_callable_info_get_return_type(invoke_info->info);
+
+ if (!gjs_value_to_g_argument(invoke_info->context,
+ rval,
+ ret_type,
+ "callback",
+ GJS_ARGUMENT_RETURN_VALUE,
+ FALSE,
+ TRUE,
+ result)) {
+ gjs_throw(invoke_info->context, "Couldn't convert res value");
+ g_base_info_unref((GIBaseInfo*)ret_type);
+ g_free(jsargs);
+ result = NULL;
+ return;
+ }
+ g_base_info_unref((GIBaseInfo*)ret_type);
+ g_free(jsargs);
+
+ gjs_callback_invoke_info_free_content(invoke_info);
+ invoke_info->in_use = FALSE;
+}
+
+static void
+destroynotify_callback(ffi_cif *cif,
+ void *result,
+ void **args,
+ void *data)
+{
+ GjsCallbackInvokeInfo *info = (GjsCallbackInvokeInfo *)data;
+
+ info->in_use = FALSE;
+
+ gjs_callback_invoke_info_free_content(info);
+}
+
+static gboolean
+gjs_destroy_notify_create(GjsCallbackInvokeInfo *info)
+{
+ static const ffi_type *destroynotify_args[] = {&ffi_type_pointer};
+ ffi_status status;
+
+ g_assert(info);
+
+ info->need_free_arg_types = FALSE;
+
+ info->closure = mmap (NULL, sizeof (ffi_closure),
+ PROT_EXEC | PROT_READ | PROT_WRITE,
+ MAP_ANON | MAP_PRIVATE, -1, sysconf (_SC_PAGE_SIZE));
+ if (!info->closure) {
+ gjs_throw(info->context, "mmap failed\n");
+ return FALSE;
+ }
+
+ info->cif = g_slice_new(ffi_cif);
+
+ status = ffi_prep_cif (info->cif, FFI_DEFAULT_ABI,
+ 1, &ffi_type_void,
+ destroynotify_args);
+ if (status != FFI_OK) {
+ gjs_throw(info->context, "ffi_prep_cif failed: %d\n", status);
+ munmap(info->closure, sizeof (ffi_closure));
+ return FALSE;
+ }
+
+ status = ffi_prep_closure (info->closure, info->cif, destroynotify_callback, info);
+ if (status != FFI_OK) {
+ gjs_throw(info->context, "ffi_prep_cif failed: %d\n", status);
+ munmap(info->closure, sizeof (ffi_closure));
+ return FALSE;
+ }
+
+ if (mprotect(info->closure, sizeof (ffi_closure), PROT_READ | PROT_EXEC) == -1) {
+ gjs_throw(info->context, "ffi_prep_closure failed: %s\n", strerror(errno));
+ munmap(info->closure, sizeof (ffi_closure));
+ return FALSE;
+ }
+
+ info->in_use = TRUE;
+
+ return TRUE;
+}
+
+static GjsCallbackInvokeInfo*
+gjs_callback_invoke_prepare(JSContext *context,
+ jsval function,
+ GICallableInfo *callable_info)
+{
+ GjsCallbackInvokeInfo *invoke_info;
+
+ g_assert(JS_TypeOfValue(context, function) == JSTYPE_FUNCTION);
+
+ invoke_info = g_slice_new0(GjsCallbackInvokeInfo);
+ invoke_info->context = context;
+ invoke_info->info = callable_info;
+
+ g_base_info_ref((GIBaseInfo*)invoke_info->info);
+ invoke_info->need_free_arg_types = TRUE;
+ invoke_info->function = function;
+ invoke_info->cif = g_slice_new0(ffi_cif);
+ invoke_info->in_use = TRUE;
+ invoke_info->callback_info.arguments = NULL;
+ invoke_info->callback_info.invoke_infos = NULL;
+ invoke_info->closure = g_callable_info_prepare_closure(callable_info, invoke_info->cif,
+ invoke_info_for_js_callback, invoke_info);
+
+ g_assert(invoke_info->closure);
+
+ return invoke_info;
+}
+
+static void
+gjs_callback_info_free (gpointer data)
+{
+ g_slice_free(GjsCallbackInfo, data);
+}
+
+static inline void
+gjs_callback_info_add_argument(JSContext *context,
+ GjsCallbackInfo *callback_info,
+ jsval arg)
+{
+ jsval *v;
+ /*JS_AddRoot/JS_RemoveRoot pair require same pointer*/
+ v = g_slice_new(jsval);
+ *v = arg;
+ JS_AddRoot(context, v);
+ callback_info->arguments = g_slist_prepend(callback_info->arguments, v);
+}
+
+static inline gboolean
+gjs_callback_from_arguments(JSContext *context,
+ GIBaseInfo* interface_info,
+ GIArgInfo *arg_info,
+ int current_arg_pos,
+ int n_args,
+ int *argv_pos,
+ jsval *argv,
+ GList **all_invoke_infos,
+ GSList **data_for_notify,
+ GSList **call_free_list,
+ void **closure)
+{
+ GjsCallbackInvokeInfo *invoke_info;
+ GSList *l;
+ gboolean is_notify = FALSE;
+ GjsCallbackInfo *callback_info = NULL;
+ int arg_n;
+
+ for (l = *data_for_notify; l; l = l->next) {
+ GjsCallbackInfo *callback_info = l->data;
+ if (callback_info->arg_index != current_arg_pos)
+ continue;
+
+ invoke_info = g_slice_new0(GjsCallbackInvokeInfo);
+ invoke_info->context = context;
+
+ if (!gjs_destroy_notify_create(invoke_info)) {
+ g_slice_free(GjsCallbackInvokeInfo, invoke_info);
+ return FALSE;
+ }
+
+ gjs_callback_info_add_argument(context, callback_info, argv[*argv_pos]);
+ (*argv_pos)--;
+ is_notify = TRUE;
+ invoke_info->callback_info = *callback_info;
+ *all_invoke_infos = g_list_append(*all_invoke_infos, invoke_info);
+ break;
+ }
+
+ if (is_notify)
+ goto out;
+
+ invoke_info = gjs_callback_invoke_prepare(context,
+ argv[*argv_pos],
+ (GICallableInfo*)interface_info);
+
+ switch (g_arg_info_get_scope(arg_info)) {
+ case GI_SCOPE_TYPE_CALL:
+ *call_free_list = g_slist_prepend(*call_free_list, invoke_info);
+ break;
+ case GI_SCOPE_TYPE_NOTIFIED:
+ g_assert(callback_info == NULL);
+ callback_info = g_slice_new0(GjsCallbackInfo);
+
+ g_assert(g_arg_info_get_destroy(arg_info) < n_args);
+
+ gjs_callback_info_add_argument(context, callback_info, argv[*argv_pos]);
+ arg_n = g_arg_info_get_closure(arg_info);
+ if (arg_n > current_arg_pos && arg_n < n_args) {
+ gjs_callback_info_add_argument(context, callback_info, argv[arg_n]);
+ }
+ callback_info->arg_index = g_arg_info_get_destroy(arg_info);
+
+ callback_info->invoke_infos = g_slist_prepend(callback_info->invoke_infos, invoke_info);
+ *data_for_notify = g_slist_prepend(*data_for_notify, callback_info);
+ break;
+ case GI_SCOPE_TYPE_ASYNC:
+ gjs_callback_info_add_argument(context, &invoke_info->callback_info, argv[*argv_pos]);
+
+ arg_n = g_arg_info_get_closure(arg_info);
+ if (arg_n > current_arg_pos && arg_n < n_args) {
+ gjs_callback_info_add_argument(context, &invoke_info->callback_info, argv[arg_n]);
+ }
+ *all_invoke_infos = g_list_append(*all_invoke_infos, invoke_info);
+ break;
+ default:
+ gjs_throw(context, "Unknown callback scope");
+ gjs_callback_invoke_info_free(invoke_info);
+ return FALSE;
+ }
+
+out:
+ *closure = invoke_info->closure;
+
+ return TRUE;
+}
+
+static void
+free_unused_invoke_infos(GSList **invoke_infos)
+{
+ GSList *k;
+ GSList *node_for_delete = NULL;
+
+ for (k = *invoke_infos; k; k = g_slist_next(k)) {
+ GList *t;
+ GjsCallbackInvokeInfo *info = (GjsCallbackInvokeInfo*)k->data;
+
+ if (info->in_use)
+ continue;
+
+ gjs_callback_invoke_info_free(info);
+
+ node_for_delete = g_slist_prepend(node_for_delete, k);
+ }
+ for (k = node_for_delete; k; k = g_slist_next(k)) {
+ *invoke_infos = g_slist_delete_link(*invoke_infos, (GSList*)k->data);
+ }
+ g_slist_free(node_for_delete);
+}
+
JSBool
gjs_invoke_c_function(JSContext *context,
GIFunctionInfo *info,
@@ -100,6 +452,7 @@ gjs_invoke_c_function(JSContext *context,
int expected_out_argc;
int i;
int argv_pos;
+ int destroy_notify_argc = 0;
int in_args_pos;
int out_args_pos;
GError *error;
@@ -112,6 +465,12 @@ gjs_invoke_c_function(JSContext *context,
jsval *return_values;
int n_return_values;
int next_rval;
+ GSList *call_free_list = NULL; /* list of GjsCallbackInvokeInfo* */
+ GSList *data_for_notify = NULL; /* list of GjsCallbackInfo* */
+ GSList *callback_arg_indices = NULL; /* list of int */
+ static GSList *invoke_infos = NULL;
+
+ free_unused_invoke_infos(&invoke_infos);
flags = g_function_info_get_flags(info);
is_method = (flags & GI_FUNCTION_IS_METHOD) != 0;
@@ -123,9 +482,17 @@ gjs_invoke_c_function(JSContext *context,
for (i = 0; i < n_args; i++) {
GIDirection direction;
GIArgInfo *arg_info;
+ gint destroy;
arg_info = g_callable_info_get_arg( (GICallableInfo*) info, i);
+ destroy = g_arg_info_get_destroy(arg_info);
direction = g_arg_info_get_direction(arg_info);
+
+ if (destroy > 0 && destroy < n_args) {
+ expected_in_argc --;
+ destroy_notify_argc++;
+ }
+
if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT)
expected_in_argc += 1;
if (direction == GI_DIRECTION_OUT || direction == GI_DIRECTION_INOUT)
@@ -145,7 +512,7 @@ gjs_invoke_c_function(JSContext *context,
/* We allow too many args; convenient for re-using a function as a callback.
* But we don't allow too few args, since that would break.
*/
- if (argc < (unsigned) expected_in_argc) {
+ if (argc + destroy_notify_argc < (unsigned) expected_in_argc) {
gjs_throw(context, "Too few arguments to %s %s.%s expected %d got %d",
is_method ? "method" : "function",
g_base_info_get_namespace( (GIBaseInfo*) info),
@@ -203,11 +570,55 @@ gjs_invoke_c_function(JSContext *context,
if (direction == GI_DIRECTION_IN || direction == GI_DIRECTION_INOUT) {
GArgument in_value;
+ GITypeTag type_tag;
+ GITypeInfo *ainfo;
+ gboolean convert_argument = TRUE;
+
+ ainfo = g_arg_info_get_type (arg_info);
+ type_tag = g_type_info_get_tag(ainfo);
+
+ if (g_slist_find(callback_arg_indices, GINT_TO_POINTER(i)) != NULL) {
+ g_assert(type_tag == GI_TYPE_TAG_VOID);
+ convert_argument = FALSE;
+ in_value.v_pointer = (gpointer)argv[argv_pos];
+ } else if (type_tag == GI_TYPE_TAG_VOID) {
+ g_warning("Have incorrect annotation");
+ convert_argument = FALSE;
+ in_value.v_pointer = (gpointer)argv[argv_pos];
+ }
- if (!gjs_value_to_arg(context, argv[argv_pos], arg_info,
- &in_value)) {
- failed = TRUE;
+ if (type_tag == GI_TYPE_TAG_INTERFACE) {
+ GIBaseInfo* interface_info;
+ GIInfoType interface_type;
+
+ interface_info = g_type_info_get_interface(ainfo);
+
+ g_assert(interface_info != NULL);
+
+ interface_type = g_base_info_get_type(interface_info);
+ if (interface_type == GI_INFO_TYPE_CALLBACK) {
+ if (!gjs_callback_from_arguments(context, interface_info, arg_info,
+ i, n_args, &argv_pos, argv,
+ &invoke_infos,
+ &data_for_notify, &call_free_list,
+ &(in_value.v_pointer))) {
+ failed = TRUE;
+ break;
+ }
+ callback_arg_indices = g_slist_prepend(callback_arg_indices,
+ GINT_TO_POINTER(g_arg_info_get_closure(arg_info)));
+ convert_argument = FALSE;
+ }
+
+ g_base_info_unref(interface_info);
}
+ g_base_info_unref((GIBaseInfo*)ainfo);
+
+ if (convert_argument)
+ if (!gjs_value_to_arg(context, argv[argv_pos], arg_info,
+ &in_value)) {
+ failed = TRUE;
+ }
++argv_pos;
@@ -233,12 +644,12 @@ gjs_invoke_c_function(JSContext *context,
if (failed)
return JS_FALSE;
- g_assert(in_args_pos == expected_in_argc);
+ g_assert(in_args_pos == expected_in_argc + destroy_notify_argc);
g_assert(out_args_pos == expected_out_argc);
error = NULL;
invoke_ok = g_function_info_invoke( (GIFunctionInfo*) info,
- in_args, expected_in_argc,
+ in_args, expected_in_argc + destroy_notify_argc,
out_args, expected_out_argc,
&return_arg,
&error);
@@ -248,6 +659,12 @@ gjs_invoke_c_function(JSContext *context,
*/
failed = FALSE;
+ g_slist_foreach(call_free_list, (GFunc)gjs_callback_invoke_info_free, NULL);
+ g_slist_free(call_free_list);
+ g_slist_foreach(data_for_notify, (GFunc)gjs_callback_info_free, NULL);
+ g_slist_free(data_for_notify);
+ g_slist_free(callback_arg_indices);
+
return_info = g_callable_info_get_return_type( (GICallableInfo*) info);
g_assert(return_info != NULL);
@@ -305,7 +722,7 @@ gjs_invoke_c_function(JSContext *context,
arg_type_info = g_arg_info_get_type(arg_info);
if (direction == GI_DIRECTION_IN) {
- g_assert(in_args_pos < expected_in_argc);
+ g_assert(in_args_pos < expected_in_argc + destroy_notify_argc);
if (!gjs_g_argument_release_in_arg(context,
g_arg_info_get_ownership_transfer(arg_info),
@@ -351,7 +768,7 @@ gjs_invoke_c_function(JSContext *context,
g_assert(next_rval == n_return_values);
g_assert(out_args_pos == expected_out_argc);
- g_assert(in_args_pos == expected_in_argc);
+ g_assert(in_args_pos == expected_in_argc + destroy_notify_argc);
if (invoke_ok && n_return_values > 0) {
/* if we have 1 return value or out arg, return that item
diff --git a/test/js/testEverythingBasic.js b/test/js/testEverythingBasic.js
index 875c525..755ad45 100644
--- a/test/js/testEverythingBasic.js
+++ b/test/js/testEverythingBasic.js
@@ -185,6 +185,43 @@ function testClosureOneArg() {
assertEquals('callback with one arg return value', 42, i);
}
+function testCallback() {
+ let callback = function() {
+ return 42;
+ };
+ assertEquals('Callback', Everything.test_callback(callback), 42);
+}
+
+function testCallbackUserData() {
+ let callbackUserData = function(userData) {
+ return userData.foo.length;
+ };
+
+ let userData = {'foo': 'bar'};
+ assertEquals('CallbackUserData', Everything.test_callback_user_data(
+ callbackUserData, userData), userData.foo.length);
+}
+
+function testCallbackDestroyNotify() {
+ let called = 0;
+ let test = function(userData) {
+ called++;
+ return userData;
+ };
+ assertEquals('CallbackDestroyNotify', Everything.test_callback_destroy_notify(test, 42), 42);
+ assertEquals('CallbackDestroyNotify', called, 1);
+ assertEquals('CallbackDestroyNotify', Everything.test_callback_thaw_notifications(), 42);
+}
+
+function testCallbackAsync() {
+ let test = function(userData) {
+ return 44;
+ };
+ Everything.test_callback_async(test, 44);
+ let i = Everything.test_callback_thaw_async();
+ assertEquals('testCallbackAsyncFinish', 44, i);
+}
+
function testIntValueArg() {
let i = Everything.test_int_value_arg(42);
assertEquals('Method taking a GValue', 42, i);
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]