[gjs] Introduce special marshalling for GErrors
- From: Giovanni Campagna <gcampagna src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gjs] Introduce special marshalling for GErrors
- Date: Thu, 7 Jun 2012 18:41:45 +0000 (UTC)
commit 2ab1b3f090edc4ac64075fb1a7f387e724ba63ff
Author: Giovanni Campagna <gcampagna src gnome org>
Date: Fri Mar 2 21:17:13 2012 +0100
Introduce special marshalling for GErrors
Previously GErrors were transformed into plain Error with a custom
message, which removed the code and domain metadata. This commit
introduces a new class hierarchy (derived from GLib.Error) for each
enumeration representing an error domain, and modifies existing
code to throw instances of that when a function fails.
https://bugzilla.gnome.org/show_bug.cgi?id=591480
Makefile.am | 6 +-
gi/arg.c | 41 +++-
gi/enumeration.c | 62 +++--
gi/enumeration.h | 3 +
gi/function.c | 15 +-
gi/gerror.c | 589 ++++++++++++++++++++++++++++++++++++++++
gi/gerror.h | 53 ++++
gi/repo.c | 8 +
gi/value.c | 18 ++-
gjs/jsapi-util-error.c | 73 ++----
gjs/mem.c | 2 +
gjs/mem.h | 1 +
modules/overrides/GLib.js | 5 +
test/js/testEverythingBasic.js | 29 ++
util/log.c | 3 +
util/log.h | 3 +-
16 files changed, 823 insertions(+), 88 deletions(-)
---
diff --git a/Makefile.am b/Makefile.am
index 4d964bf..648b57f 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -53,7 +53,8 @@ nobase_gjs_module_include_HEADERS = \
gi/function.h \
gi/keep-alive.h \
gi/interface.h \
- gi/gtype.h
+ gi/gtype.h \
+ gi/gerror.h
noinst_HEADERS += \
gjs/jsapi-private.h \
@@ -137,7 +138,8 @@ libgjs_la_SOURCES += \
gi/union.c \
gi/value.c \
gi/interface.c \
- gi/gtype.c
+ gi/gtype.c \
+ gi/gerror.c
# Also, these files used to be a separate library
gdbus_wrapper_source_files = \
diff --git a/gi/arg.c b/gi/arg.c
index 90182e0..af1750d 100644
--- a/gi/arg.c
+++ b/gi/arg.c
@@ -31,6 +31,7 @@
#include "union.h"
#include "param.h"
#include "value.h"
+#include "gerror.h"
#include "gjs/byteArray.h"
#include <gjs/gjs-module.h>
#include <gjs/compat.h>
@@ -1327,7 +1328,6 @@ gjs_value_to_g_argument(JSContext *context,
arg->v_pointer = NULL;
wrong = TRUE;
}
-
} else if (JSVAL_IS_NULL(value) &&
interface_type != GI_INFO_TYPE_ENUM &&
interface_type != GI_INFO_TYPE_FLAGS) {
@@ -1337,8 +1337,16 @@ gjs_value_to_g_argument(JSContext *context,
if ((interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) &&
/* We special case Closures later, so skip them here */
!g_type_is_a(gtype, G_TYPE_CLOSURE)) {
- arg->v_pointer = gjs_c_struct_from_boxed(context,
- JSVAL_TO_OBJECT(value));
+
+ /* special case GError too */
+ if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+ arg->v_pointer = gjs_gerror_from_error(context,
+ JSVAL_TO_OBJECT(value));
+ } else {
+ arg->v_pointer = gjs_c_struct_from_boxed(context,
+ JSVAL_TO_OBJECT(value));
+ }
+
if (transfer != GI_TRANSFER_NOTHING) {
if (g_type_is_a(gtype, G_TYPE_BOXED))
arg->v_pointer = g_boxed_copy (gtype, arg->v_pointer);
@@ -2368,6 +2376,20 @@ gjs_value_from_g_argument (JSContext *context,
return JS_TRUE;
}
+ case GI_TYPE_TAG_ERROR:
+ {
+ if (arg->v_pointer) {
+ JSObject *obj = gjs_error_from_gerror(context, arg->v_pointer);
+ if (obj) {
+ *value_p = OBJECT_TO_JSVAL(obj);
+ return JS_TRUE;
+ }
+
+ return JS_FALSE;
+ }
+ return JS_TRUE;
+ }
+
case GI_TYPE_TAG_INTERFACE:
{
jsval value;
@@ -2427,13 +2449,24 @@ gjs_value_from_g_argument (JSContext *context,
"gtype of INTERFACE is %s", g_type_name(gtype));
- /* Test GValue before Struct, or it will be handled as the latter */
+ /* Test GValue and GError before Struct, or it will be handled as the latter */
if (g_type_is_a(gtype, G_TYPE_VALUE)) {
if (!gjs_value_from_g_value(context, &value, arg->v_pointer))
value = JSVAL_VOID; /* Make sure error is flagged */
goto out;
}
+ if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+ JSObject *obj;
+
+ obj = gjs_error_from_gerror(context, arg->v_pointer);
+ if (obj)
+ value = OBJECT_TO_JSVAL(obj);
+ else
+ value = JSVAL_VOID;
+
+ goto out;
+ }
if (interface_type == GI_INFO_TYPE_STRUCT || interface_type == GI_INFO_TYPE_BOXED) {
JSObject *obj;
diff --git a/gi/enumeration.c b/gi/enumeration.c
index fac9854..d4d96a3 100644
--- a/gi/enumeration.c
+++ b/gi/enumeration.c
@@ -103,17 +103,50 @@ gjs_define_enum_value(JSContext *context,
}
JSBool
+gjs_define_enum_values(JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info)
+{
+ GType gtype;
+ int i, n_values;
+ jsval value;
+
+ /* Fill in enum values first, so we don't define the enum itself until we're
+ * sure we can finish successfully.
+ */
+ n_values = g_enum_info_get_n_values(info);
+ for (i = 0; i < n_values; ++i) {
+ GIValueInfo *value_info = g_enum_info_get_value(info, i);
+ gboolean failed;
+
+ failed = !gjs_define_enum_value(context, in_object, value_info);
+
+ g_base_info_unref( (GIBaseInfo*) value_info);
+
+ if (failed) {
+ return JS_FALSE;
+ }
+ }
+
+ gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info);
+ value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, gtype));
+ JS_DefineProperty(context, in_object, "$gtype", value,
+ NULL, NULL, JSPROP_PERMANENT);
+
+ return JS_TRUE;
+}
+
+
+JSBool
gjs_define_enumeration(JSContext *context,
JSObject *in_object,
GIEnumInfo *info,
JSObject **enumeration_p)
{
const char *enum_name;
- GType gtype;
JSObject *enum_obj;
jsval value;
- int i;
- int n_values;
+
/* An enumeration is simply an object containing integer attributes for
* each enum value. It does not have a special JSClass.
@@ -150,27 +183,8 @@ gjs_define_enumeration(JSContext *context,
JS_SetParent(context, enum_obj,
gjs_get_import_global (context));
- /* Fill in enum values first, so we don't define the enum itself until we're
- * sure we can finish successfully.
- */
- n_values = g_enum_info_get_n_values(info);
- for (i = 0; i < n_values; ++i) {
- GIValueInfo *value_info = g_enum_info_get_value(info, i);
- gboolean failed;
-
- failed = !gjs_define_enum_value(context, enum_obj, value_info);
-
- g_base_info_unref( (GIBaseInfo*) value_info);
-
- if (failed) {
- return JS_FALSE;
- }
- }
-
- gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info);
- value = OBJECT_TO_JSVAL(gjs_gtype_create_gtype_wrapper(context, gtype));
- JS_DefineProperty(context, enum_obj, "$gtype", value,
- NULL, NULL, JSPROP_PERMANENT);
+ if (!gjs_define_enum_values(context, enum_obj, info))
+ return JS_FALSE;
gjs_debug(GJS_DEBUG_GENUM,
"Defining %s.%s as %p",
diff --git a/gi/enumeration.h b/gi/enumeration.h
index 3015a0c..3642def 100644
--- a/gi/enumeration.h
+++ b/gi/enumeration.h
@@ -32,6 +32,9 @@
G_BEGIN_DECLS
+JSBool gjs_define_enum_values (JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info);
JSBool gjs_define_enumeration (JSContext *context,
JSObject *in_object,
GIEnumInfo *info,
diff --git a/gi/function.c b/gi/function.c
index 195bc0d..1f8ac3d 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -28,6 +28,7 @@
#include "object.h"
#include "boxed.h"
#include "union.h"
+#include "gerror.h"
#include <gjs/gjs-module.h>
#include <gjs/compat.h>
@@ -674,7 +675,13 @@ gjs_invoke_c_function(JSContext *context,
g_assert_cmpuint(0, <, c_argc);
if (type == GI_INFO_TYPE_STRUCT || type == GI_INFO_TYPE_BOXED) {
- in_arg_cvalues[0].v_pointer = gjs_c_struct_from_boxed(context, obj);
+ GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *)container);
+
+ /* GError must be special cased */
+ if (g_type_is_a(gtype, G_TYPE_ERROR))
+ in_arg_cvalues[0].v_pointer = gjs_gerror_from_error(context, obj);
+ else
+ in_arg_cvalues[0].v_pointer = gjs_c_struct_from_boxed(context, obj);
} else if (type == GI_INFO_TYPE_UNION) {
in_arg_cvalues[0].v_pointer = gjs_c_union_from_union(context, obj);
} else { /* by fallback is always object */
@@ -1210,11 +1217,7 @@ release:
}
if (!failed && did_throw_gerror) {
- gjs_throw(context, "Error invoking %s.%s: %s",
- g_base_info_get_namespace( (GIBaseInfo*) function->info),
- g_base_info_get_name( (GIBaseInfo*) function->info),
- local_error->message);
- g_error_free(local_error);
+ gjs_throw_g_error(context, local_error);
return JS_FALSE;
} else if (failed) {
return JS_FALSE;
diff --git a/gi/gerror.c b/gi/gerror.c
new file mode 100644
index 0000000..5d50cd5
--- /dev/null
+++ b/gi/gerror.c
@@ -0,0 +1,589 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <string.h>
+
+#include <gjs/gjs-module.h>
+#include <gjs/compat.h>
+#include "boxed.h"
+#include "enumeration.h"
+#include "repo.h"
+#include "gerror.h"
+
+#include <util/log.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+typedef struct {
+ GIEnumInfo *info;
+ GQuark domain;
+ GError *gerror; /* NULL if we are the prototype and not an instance */
+} Error;
+
+enum {
+ PROP_0,
+ PROP_DOMAIN,
+ PROP_CODE,
+ PROP_MESSAGE
+};
+
+static struct JSClass gjs_error_class;
+
+GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(Error, gjs_error_class)
+
+GJS_NATIVE_CONSTRUCTOR_DECLARE(error)
+{
+ GJS_NATIVE_CONSTRUCTOR_VARIABLES(error)
+ Error *priv;
+ Error *proto_priv;
+ JSObject *proto;
+ jsval v_message, v_code;
+ gchar *message;
+
+ /* Check early to avoid allocating memory for nothing */
+ if (argc != 1 || !JSVAL_IS_OBJECT(argv[0])) {
+ gjs_throw(context, "Invalid parameters passed to GError constructor, expected one object");
+ return JS_FALSE;
+ }
+
+ GJS_NATIVE_CONSTRUCTOR_PRELUDE(error);
+
+ priv = g_slice_new0(Error);
+
+ GJS_INC_COUNTER(gerror);
+
+ g_assert(priv_from_js(context, object) == NULL);
+ JS_SetPrivate(context, object, priv);
+
+ gjs_debug_lifecycle(GJS_DEBUG_GERROR,
+ "GError constructor, obj %p priv %p",
+ object, priv);
+
+ proto = JS_GetPrototype(context, object);
+ gjs_debug_lifecycle(GJS_DEBUG_GERROR, "GError instance __proto__ is %p", proto);
+
+ /* If we're the prototype, then post-construct we'll fill in priv->info.
+ * If we are not the prototype, though, then we'll get ->info from the
+ * prototype and then create a GObject if we don't have one already.
+ */
+ proto_priv = priv_from_js(context, proto);
+ if (proto_priv == NULL) {
+ gjs_debug(GJS_DEBUG_GERROR,
+ "Bad prototype set on GError? Must match JSClass of object. JS error should have been reported.");
+ return JS_FALSE;
+ }
+
+ priv->info = proto_priv->info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+ priv->domain = proto_priv->domain;
+
+ if (!gjs_object_require_property (context, JSVAL_TO_OBJECT(argv[0]),
+ "GError constructor", "message", &v_message))
+ return JS_FALSE;
+ if (!gjs_object_require_property (context, JSVAL_TO_OBJECT(argv[0]),
+ "GError constructor", "code", &v_code))
+ return JS_FALSE;
+ if (!gjs_string_to_utf8 (context, v_message, &message))
+ return JS_FALSE;
+
+ priv->gerror = g_error_new_literal (priv->domain, JSVAL_TO_INT(v_code),
+ message);
+
+ g_free (message);
+
+ GJS_NATIVE_CONSTRUCTOR_FINISH(boxed);
+
+ return JS_TRUE;
+}
+
+static void
+error_finalize(JSContext *context,
+ JSObject *obj)
+{
+ Error *priv;
+
+ priv = priv_from_js(context, obj);
+ gjs_debug_lifecycle(GJS_DEBUG_GERROR,
+ "finalize, obj %p priv %p", obj, priv);
+ if (priv == NULL)
+ return; /* wrong class? */
+
+ g_clear_error (&priv->gerror);
+
+ if (priv->info) {
+ g_base_info_unref( (GIBaseInfo*) priv->info);
+ priv->info = NULL;
+ }
+
+ GJS_DEC_COUNTER(gerror);
+ g_slice_free(Error, priv);
+}
+
+static JSBool
+error_get_domain(JSContext *context, JSObject *obj, jsid id, jsval *vp)
+{
+ Error *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE;
+
+ *vp = INT_TO_JSVAL(priv->domain);
+ return JS_TRUE;
+}
+
+static JSBool
+error_get_message(JSContext *context, JSObject *obj, jsid id, jsval *vp)
+{
+ Error *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE;
+
+ if (priv->gerror == NULL) {
+ /* Object is prototype, not instance */
+ gjs_throw(context, "Can't get a field from a GError prototype");
+ return JS_FALSE;
+ }
+
+ return gjs_string_from_utf8(context, priv->gerror->message, -1, vp);
+}
+
+static JSBool
+error_get_code(JSContext *context, JSObject *obj, jsid id, jsval *vp)
+{
+ Error *priv;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return JS_FALSE;
+
+ if (priv->gerror == NULL) {
+ /* Object is prototype, not instance */
+ gjs_throw(context, "Can't get a field from a GError prototype");
+ return JS_FALSE;
+ }
+
+ *vp = INT_TO_JSVAL(priv->gerror->code);
+ return JS_TRUE;
+}
+
+static JSBool
+error_to_string(JSContext *context, uintN argc, jsval *vp)
+{
+ jsval v_self;
+ JSObject *self;
+ Error *priv;
+ jsval v_out;
+ gchar *descr;
+ JSBool retval;
+
+ v_self = JS_THIS(context, vp);
+ if (!JSVAL_IS_OBJECT(v_self)) {
+ /* Lie a bit here... */
+ gjs_throw(context, "GLib.Error.prototype.toString() called on a non object");
+ return JS_FALSE;
+ }
+
+ self = JSVAL_TO_OBJECT(v_self);
+ priv = priv_from_js(context, self);
+
+ if (priv == NULL)
+ return JS_FALSE;
+
+ v_out = JSVAL_VOID;
+ retval = JS_FALSE;
+
+ /* We follow the same pattern as standard JS errors, at the expense of
+ hiding some useful information */
+
+ if (priv->gerror == NULL) {
+ descr = g_strdup_printf("%s.%s",
+ g_base_info_get_namespace(priv->info),
+ g_base_info_get_name(priv->info));
+
+ if (!gjs_string_from_utf8(context, descr, -1, &v_out))
+ goto out;
+ } else {
+ descr = g_strdup_printf("%s.%s: %s",
+ g_base_info_get_namespace(priv->info),
+ g_base_info_get_name(priv->info),
+ priv->gerror->message);
+
+ if (!gjs_string_from_utf8(context, descr, -1, &v_out))
+ goto out;
+ }
+
+ JS_SET_RVAL(context, vp, v_out);
+ retval = JS_TRUE;
+
+ out:
+ g_free(descr);
+ return retval;
+}
+
+static JSBool
+error_constructor_value_of(JSContext *context, uintN argc, jsval *vp)
+{
+ jsval v_self, v_prototype;
+ Error *priv;
+ jsval v_out;
+
+ v_self = JS_THIS(context, vp);
+ if (!JSVAL_IS_OBJECT(v_self)) {
+ /* Lie a bit here... */
+ gjs_throw(context, "GLib.Error.valueOf() called on a non object");
+ return JS_FALSE;
+ }
+
+ if (!gjs_object_require_property(context,
+ JSVAL_TO_OBJECT(v_self),
+ "constructor",
+ "prototype",
+ &v_prototype))
+ return JS_FALSE;
+
+ if (!JSVAL_IS_OBJECT(v_prototype)) {
+ gjs_throw(context, "GLib.Error.valueOf() called on something that is not"
+ " a constructor");
+ return JS_FALSE;
+ }
+
+ priv = priv_from_js(context, JSVAL_TO_OBJECT(v_prototype));
+
+ if (priv == NULL)
+ return JS_FALSE;
+
+ v_out = INT_TO_JSVAL(priv->domain);
+
+ JS_SET_RVAL(context, vp, v_out);
+ return TRUE;
+}
+
+
+/* The bizarre thing about this vtable is that it applies to both
+ * instances of the object, and to the prototype that instances of the
+ * class have.
+ */
+static struct JSClass gjs_error_class = {
+ NULL, /* dynamic class, no name here */
+ JSCLASS_HAS_PRIVATE |
+ JSCLASS_NEW_RESOLVE |
+ JSCLASS_NEW_RESOLVE_GETS_START,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_PropertyStub,
+ JS_StrictPropertyStub,
+ JS_EnumerateStub,
+ JS_ResolveStub,
+ JS_ConvertStub,
+ error_finalize,
+ NULL,
+ NULL,
+ NULL,
+ NULL, NULL, NULL, NULL, NULL
+};
+
+/* We need to shadow all fields of GError, to prevent calling the getter from GBoxed
+ (which would trash memory accessing the instance private data) */
+static JSPropertySpec gjs_error_proto_props[] = {
+ { "domain", PROP_DOMAIN, GJS_MODULE_PROP_FLAGS | JSPROP_READONLY, error_get_domain, NULL },
+ { "code", PROP_CODE, GJS_MODULE_PROP_FLAGS | JSPROP_READONLY, error_get_code, NULL },
+ { "message", PROP_MESSAGE, GJS_MODULE_PROP_FLAGS | JSPROP_READONLY, error_get_message, NULL },
+ { NULL }
+};
+
+static JSFunctionSpec gjs_error_proto_funcs[] = {
+ { "toString", error_to_string, 0, GJS_MODULE_PROP_FLAGS },
+ JS_FS_END
+};
+
+static JSFunctionSpec gjs_error_constructor_funcs[] = {
+ { "valueOf", error_constructor_value_of, 0, GJS_MODULE_PROP_FLAGS },
+ JS_FS_END
+};
+
+JSObject*
+gjs_lookup_error_constructor(JSContext *context,
+ GIEnumInfo *info)
+{
+ JSObject *ns;
+ JSObject *constructor;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ constructor = NULL;
+ if (gjs_define_error_class(context, ns, info,
+ &constructor, NULL))
+ return constructor;
+ else
+ return NULL;
+}
+
+JSObject*
+gjs_lookup_error_prototype(JSContext *context,
+ GIEnumInfo *info)
+{
+ JSObject *ns;
+ JSObject *proto;
+
+ ns = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
+
+ if (ns == NULL)
+ return NULL;
+
+ proto = NULL;
+ if (gjs_define_error_class(context, ns, info, NULL, &proto))
+ return proto;
+ else
+ return NULL;
+}
+
+JSClass*
+gjs_lookup_error_class(JSContext *context,
+ GIEnumInfo *info)
+{
+ JSObject *prototype;
+
+ prototype = gjs_lookup_error_prototype(context, info);
+
+ return JS_GET_CLASS(context, prototype);
+}
+
+JSBool
+gjs_define_error_class(JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p)
+{
+ const char *constructor_name;
+ GIBoxedInfo *glib_error_info;
+ JSObject *prototype, *parent_proto;
+ JSObject *constructor;
+ jsval value;
+ Error *priv;
+
+ /* See the comment in gjs_define_boxed_class() for an
+ * explanation of how this all works; Error is pretty much the
+ * same as Boxed (except that we inherit from GLib.Error).
+ */
+
+ constructor_name = g_base_info_get_name( (GIBaseInfo*) info);
+
+ if (gjs_object_get_property(context, in_object, constructor_name, &value)) {
+ JSObject *constructor;
+
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Existing property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+
+ constructor = JSVAL_TO_OBJECT(value);
+
+ gjs_object_get_property(context, constructor, "prototype", &value);
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "error %s prototype property does not appear to exist or has wrong type", constructor_name);
+ return JS_FALSE;
+ } else {
+ if (prototype_p)
+ *prototype_p = JSVAL_TO_OBJECT(value);
+ if (constructor_p)
+ *constructor_p = constructor;
+
+ return JS_TRUE;
+ }
+ }
+
+ g_irepository_require(NULL, "GLib", "2.0", 0, NULL);
+ glib_error_info = (GIBoxedInfo*) g_irepository_find_by_name(NULL, "GLib", "Error");
+ parent_proto = gjs_lookup_boxed_prototype(context, glib_error_info);
+ g_base_info_unref((GIBaseInfo*)glib_error_info);
+
+ prototype = gjs_init_class_dynamic(context, in_object,
+ parent_proto,
+ g_base_info_get_namespace( (GIBaseInfo*) info),
+ constructor_name,
+ &gjs_error_class,
+ gjs_error_constructor,
+ /* number of constructor args (less can be passed) */
+ 1,
+ /* props of prototype */
+ &gjs_error_proto_props[0],
+ /* funcs of prototype */
+ &gjs_error_proto_funcs[0],
+ /* props of constructor, MyConstructor.myprop */
+ NULL,
+ /* funcs of constructor, MyConstructor.myfunc() */
+ &gjs_error_constructor_funcs[0]);
+ if (prototype == NULL) {
+ gjs_log_exception(context, NULL);
+ gjs_fatal("Can't init class %s", constructor_name);
+ }
+
+ g_assert(gjs_object_has_property(context, in_object, constructor_name));
+
+ priv = g_slice_new0(Error);
+ priv->info = info;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+ priv->domain = g_quark_from_string (g_enum_info_get_error_domain(priv->info));
+
+ JS_SetPrivate(context, prototype, priv);
+
+ gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p",
+ constructor_name, prototype, JS_GET_CLASS(context, prototype), in_object);
+
+ constructor = NULL;
+ gjs_object_get_property(context, in_object, constructor_name, &value);
+ if (!JSVAL_IS_VOID(value)) {
+ if (!JSVAL_IS_OBJECT(value)) {
+ gjs_throw(context, "Property '%s' does not look like a constructor",
+ constructor_name);
+ return JS_FALSE;
+ }
+ }
+
+ constructor = JSVAL_TO_OBJECT(value);
+
+ gjs_define_enum_values(context, constructor, priv->info);
+
+ if (constructor_p)
+ *constructor_p = constructor;
+
+ if (prototype_p)
+ *prototype_p = prototype;
+
+ return JS_TRUE;
+}
+
+static GIEnumInfo *
+find_error_domain_info(GQuark domain)
+{
+ GIEnumInfo *info;
+
+ /* first an attempt without loading extra libraries */
+ info = g_irepository_find_by_error_domain(NULL, domain);
+ if (info)
+ return info;
+
+ /* load standard stuff */
+ g_irepository_require(NULL, "GLib", "2.0", 0, NULL);
+ g_irepository_require(NULL, "GObject", "2.0", 0, NULL);
+ g_irepository_require(NULL, "Gio", "2.0", 0, NULL);
+ info = g_irepository_find_by_error_domain(NULL, domain);
+ if (info)
+ return info;
+
+ /* last attempt: load GIRepository (for invoke errors, rarely
+ needed) */
+ g_irepository_require(NULL, "GIRepository", "1.0", 0, NULL);
+ info = g_irepository_find_by_error_domain(NULL, domain);
+
+ return info;
+}
+
+JSObject*
+gjs_error_from_gerror(JSContext *context,
+ GError *gerror)
+{
+ JSObject *obj;
+ JSObject *proto;
+ Error *priv;
+ Error *proto_priv;
+ GIEnumInfo *info;
+
+ if (gerror == NULL)
+ return NULL;
+
+ info = find_error_domain_info(gerror->domain);
+
+ if (!info) {
+ /* We don't have error domain metadata */
+ /* Marshal the error as a plain GError */
+ GIBaseInfo *glib_boxed;
+ JSObject *retval;
+
+ glib_boxed = g_irepository_find_by_name(NULL, "GLib", "Error");
+ retval = gjs_boxed_from_c_struct(context, glib_boxed, gerror, 0);
+
+ g_base_info_unref(glib_boxed);
+ return retval;
+ }
+
+ gjs_debug_marshal(GJS_DEBUG_GBOXED,
+ "Wrapping struct %s %p with JSObject",
+ g_base_info_get_name((GIBaseInfo *)info), gboxed);
+
+ proto = gjs_lookup_error_prototype(context, info);
+ proto_priv = priv_from_js(context, proto);
+
+ obj = JS_NewObjectWithGivenProto(context,
+ JS_GET_CLASS(context, proto), proto,
+ gjs_get_import_global (context));
+
+ priv = g_slice_new0(Error);
+ JS_SetPrivate(context, obj, priv);
+ priv->info = info;
+ priv->domain = proto_priv->domain;
+ g_base_info_ref( (GIBaseInfo*) priv->info);
+ priv->gerror = g_error_copy(gerror);
+
+ return obj;
+}
+
+GError*
+gjs_gerror_from_error(JSContext *context,
+ JSObject *obj)
+{
+ Error *priv;
+
+ if (obj == NULL)
+ return NULL;
+
+ priv = priv_from_js(context, obj);
+
+ if (priv == NULL)
+ return NULL;
+
+ if (priv->gerror == NULL) {
+ gjs_throw(context,
+ "Object is %s.%s.prototype, not an object instance - cannot convert to a boxed instance",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ return NULL;
+ }
+
+ return priv->gerror;
+}
diff --git a/gi/gerror.h b/gi/gerror.h
new file mode 100644
index 0000000..0d93c44
--- /dev/null
+++ b/gi/gerror.h
@@ -0,0 +1,53 @@
+/* -*- mode: C; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
+/*
+ * Copyright (c) 2008 litl, LLC
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to
+ * deal in the Software without restriction, including without limitation the
+ * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+#ifndef __GJS_ERROR_H__
+#define __GJS_ERROR_H__
+
+#include <glib.h>
+
+#include <jsapi.h>
+
+#include <girepository.h>
+
+G_BEGIN_DECLS
+
+JSBool gjs_define_error_class (JSContext *context,
+ JSObject *in_object,
+ GIEnumInfo *info,
+ JSObject **constructor_p,
+ JSObject **prototype_p);
+JSObject* gjs_lookup_error_constructor (JSContext *context,
+ GIEnumInfo *info);
+JSObject* gjs_lookup_error_prototype (JSContext *context,
+ GIEnumInfo *info);
+JSClass* gjs_lookup_error_class (JSContext *context,
+ GIEnumInfo *info);
+GError* gjs_gerror_from_error (JSContext *context,
+ JSObject *obj);
+JSObject* gjs_error_from_gerror (JSContext *context,
+ GError *gerror);
+
+G_END_DECLS
+
+#endif /* __GJS_ERROR_H__ */
diff --git a/gi/repo.c b/gi/repo.c
index 8edcedf..55c2cfa 100644
--- a/gi/repo.c
+++ b/gi/repo.c
@@ -34,6 +34,7 @@
#include "arg.h"
#include "foreign.h"
#include "interface.h"
+#include "gerror.h"
#include <gjs/compat.h>
@@ -499,6 +500,13 @@ gjs_define_info(JSContext *context,
return JS_FALSE;
break;
case GI_INFO_TYPE_ENUM:
+ if (g_enum_info_get_error_domain((GIEnumInfo*) info)) {
+ /* define as GError subclass */
+ if (!gjs_define_error_class(context, in_object, (GIEnumInfo*) info, NULL, NULL))
+ return JS_FALSE;
+ }
+ /* fall through */
+
case GI_INFO_TYPE_FLAGS:
if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info, NULL))
return JS_FALSE;
diff --git a/gi/value.c b/gi/value.c
index 71fd39b..c64564f 100644
--- a/gi/value.c
+++ b/gi/value.c
@@ -34,6 +34,7 @@
#include "boxed.h"
#include "union.h"
#include "gtype.h"
+#include "gerror.h"
#include <gjs/gjs-module.h>
#include <gjs/compat.h>
@@ -388,7 +389,13 @@ gjs_value_to_g_value_internal(JSContext *context,
} else if (JSVAL_IS_OBJECT(value)) {
JSObject *obj;
obj = JSVAL_TO_OBJECT(value);
- gboxed = gjs_c_struct_from_boxed(context, obj);
+
+ if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+ /* special case GError */
+ gboxed = gjs_gerror_from_error(context, obj);
+ } else {
+ gboxed = gjs_c_struct_from_boxed(context, obj);
+ }
} else {
gjs_throw(context,
"Wrong type %s; boxed type %s expected",
@@ -666,6 +673,14 @@ gjs_value_from_g_value_internal(JSContext *context,
gboxed = g_value_get_variant(gvalue);
boxed_flags = GJS_BOXED_CREATION_NONE;
+ /* special case GError */
+ if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+ obj = gjs_error_from_gerror(context, gboxed);
+ *value_p = OBJECT_TO_JSVAL(obj);
+
+ return TRUE;
+ }
+
/* The only way to differentiate unions and structs is from
* their g-i info as both GBoxed */
info = g_irepository_find_by_gtype(g_irepository_get_default(),
@@ -705,6 +720,7 @@ gjs_value_from_g_value_internal(JSContext *context,
g_base_info_unref(info);
return JS_FALSE;
}
+
*value_p = OBJECT_TO_JSVAL(obj);
g_base_info_unref(info);
} else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
diff --git a/gjs/jsapi-util-error.c b/gjs/jsapi-util-error.c
index db3188f..8bce07e 100644
--- a/gjs/jsapi-util-error.c
+++ b/gjs/jsapi-util-error.c
@@ -25,6 +25,7 @@
#include "jsapi-util.h"
#include "compat.h"
+#include "gi/gerror.h"
#include <util/log.h>
@@ -46,13 +47,9 @@ gjs_throw_valist(JSContext *context,
va_list args)
{
char *s;
- jsval retval;
- jsval argv[1];
- JSFunction *func;
- const char *body;
JSBool result;
- const char *names[] = { "message" };
- guint options;
+ jsval v_constructor, v_message;
+ JSObject *err_obj;
s = g_strdup_vprintf(format, args);
@@ -79,53 +76,20 @@ gjs_throw_valist(JSContext *context,
(void)JS_EnterLocalRootScope(context);
- if (!gjs_string_from_utf8(context, s, -1, &argv[0])) {
+ if (!gjs_string_from_utf8(context, s, -1, &v_message)) {
JS_ReportError(context, "Failed to copy exception string");
goto out;
}
- body = "throw new Error(message);";
- func = JS_CompileFunction(context,
- JS_GetGlobalObject(context), /* parent object (scope chain) */
- NULL, /* name of function if we wanted to define it in parent */
- 1, /* nargs */
- &names[0], /* array of arg names if we had args */
- body,
- strlen(body),
- "gjs_throw", /* file */
- 0); /* line */
-
- if (func == NULL) {
- JS_ReportError(context, "Failed to compile function");
+ if (!gjs_object_get_property(context, JS_GetGlobalObject(context),
+ "Error", &v_constructor)) {
+ JS_ReportError(context, "??? Missing Error constructor in global object?");
goto out;
}
- /* we need JS_CallFunctionValue() to leave the exception set */
- options = JS_GetOptions(context);
- if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) {
- JS_SetOptions(context, options | JSOPTION_DONT_REPORT_UNCAUGHT);
- }
-
- retval = JSVAL_VOID;
-
- /* note the return value is whether function succeeded, which it shouldn't, since it
- * throws...
- */
- JS_CallFunctionValue(context,
- JS_GetGlobalObject(context),
- OBJECT_TO_JSVAL(JS_GetFunctionObject(func)),
- 1, &argv[0],
- &retval);
-
- if (!(options & JSOPTION_DONT_REPORT_UNCAUGHT)) {
- JS_SetOptions(context, options);
- }
-
- if (!JS_IsExceptionPending(context)) {
- JS_ReportError(context,
- "Failed to set exception by calling our exception-setting function");
- goto out;
- }
+ /* throw new Error(message) */
+ err_obj = JS_New(context, JSVAL_TO_OBJECT(v_constructor), 1, &v_message);
+ JS_SetPendingException(context, OBJECT_TO_JSVAL(err_obj));
result = JS_TRUE;
@@ -182,17 +146,26 @@ gjs_throw_literal(JSContext *context,
* gjs_throw_g_error:
*
* Convert a GError into a JavaScript Exception, and
- * frees the GError. Like gjs_throw(), will not overwrite
- * an already pending exception.
+ * frees the GError. Differently from gjs_throw(), it
+ * will overwrite an existing exception, as it is used
+ * to report errors from C functions.
*/
void
gjs_throw_g_error (JSContext *context,
GError *error)
{
+ JSObject *err_obj;
+
if (error == NULL)
return;
- gjs_throw_literal(context, error->message);
- g_error_free (error);
+
+ JS_BeginRequest(context);
+
+ err_obj = gjs_error_from_gerror(context, error);
+ if (err_obj)
+ JS_SetPendingException(context, OBJECT_TO_JSVAL(err_obj));
+
+ JS_EndRequest(context);
}
#if GJS_BUILD_TESTS
diff --git a/gjs/mem.c b/gjs/mem.c
index 6122a55..e6a65e3 100644
--- a/gjs/mem.c
+++ b/gjs/mem.c
@@ -36,6 +36,7 @@
GJS_DEFINE_COUNTER(everything)
GJS_DEFINE_COUNTER(boxed)
+GJS_DEFINE_COUNTER(gerror)
GJS_DEFINE_COUNTER(closure)
GJS_DEFINE_COUNTER(database)
GJS_DEFINE_COUNTER(dbus_exports)
@@ -54,6 +55,7 @@ GJS_DEFINE_COUNTER(interface)
static GjsMemCounter* counters[] = {
GJS_LIST_COUNTER(boxed),
+ GJS_LIST_COUNTER(gerror),
GJS_LIST_COUNTER(closure),
GJS_LIST_COUNTER(database),
GJS_LIST_COUNTER(dbus_exports),
diff --git a/gjs/mem.h b/gjs/mem.h
index 3988b4f..7efe51b 100644
--- a/gjs/mem.h
+++ b/gjs/mem.h
@@ -44,6 +44,7 @@ typedef struct {
GJS_DECLARE_COUNTER(everything)
GJS_DECLARE_COUNTER(boxed)
+GJS_DECLARE_COUNTER(gerror)
GJS_DECLARE_COUNTER(closure)
GJS_DECLARE_COUNTER(database)
GJS_DECLARE_COUNTER(dbus_exports)
diff --git a/modules/overrides/GLib.js b/modules/overrides/GLib.js
index bfbcf63..8d0eed0 100644
--- a/modules/overrides/GLib.js
+++ b/modules/overrides/GLib.js
@@ -240,6 +240,11 @@ function _init() {
GLib = this;
+ // small HACK: we add a matches() method to standard Errors so that
+ // you can do "catch(e if e.matches(Ns.FooError, Ns.FooError.SOME_CODE))"
+ // without checking instanceof
+ Error.prototype.matches = function() { return false; }
+
this.Variant.new = function (sig, value) {
let signature = Array.prototype.slice.call(sig);
diff --git a/test/js/testEverythingBasic.js b/test/js/testEverythingBasic.js
index 455c7a1..fdcd3ef 100644
--- a/test/js/testEverythingBasic.js
+++ b/test/js/testEverythingBasic.js
@@ -521,4 +521,33 @@ function testVariant() {
assertEquals(3, as.length);
}
+function testGError() {
+ assertEquals(Gio.io_error_quark(), Number(Gio.IOErrorEnum));
+
+ try {
+ let file = Gio.file_new_for_path("\\/,.^!@&$_don't exist");
+ file.read(null);
+ } catch (x) {
+ assertTrue(x instanceof Gio.IOErrorEnum);
+ assertTrue(x.matches(Gio.io_error_quark(), Gio.IOErrorEnum.NOT_FOUND));
+ assertTrue(x.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND));
+
+ assertEquals(Gio.io_error_quark(), x.domain);
+ assertEquals(Gio.IOErrorEnum.NOT_FOUND, x.code);
+ }
+
+ Everything.test_gerror_callback(function(e) {
+ assertTrue(e instanceof Gio.IOErrorEnum);
+ assertEquals(Gio.io_error_quark(), e.domain);
+ assertEquals(Gio.IOErrorEnum.NOT_SUPPORTED, e.code);
+ assertEquals('regression test error', e.message);
+ });
+ Everything.test_owned_gerror_callback(function(e) {
+ assertTrue(e instanceof Gio.IOErrorEnum);
+ assertEquals(Gio.io_error_quark(), e.domain);
+ assertEquals(Gio.IOErrorEnum.PERMISSION_DENIED, e.code);
+ assertEquals('regression test owned error', e.message);
+ });
+}
+
gjstestRun();
diff --git a/util/log.c b/util/log.c
index 3f0e06a..e52daf5 100644
--- a/util/log.c
+++ b/util/log.c
@@ -277,6 +277,9 @@ gjs_debug(GjsDebugTopic topic,
case GJS_DEBUG_BYTE_ARRAY:
prefix = "JS BYTE ARRAY";
break;
+ case GJS_DEBUG_GERROR:
+ prefix = "JS G ERR";
+ break;
}
if (!is_allowed_prefix(prefix))
diff --git a/util/log.h b/util/log.h
index f9d3c54..777fe28 100644
--- a/util/log.h
+++ b/util/log.h
@@ -60,7 +60,8 @@ typedef enum {
GJS_DEBUG_PROPS,
GJS_DEBUG_SCOPE,
GJS_DEBUG_HTTP,
- GJS_DEBUG_BYTE_ARRAY
+ GJS_DEBUG_BYTE_ARRAY,
+ GJS_DEBUG_GERROR,
} GjsDebugTopic;
/* These defines are because we have some pretty expensive and
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]