[gjs] Introduce special marshalling for GErrors



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]