Commit 2ab1b3f0 authored by Giovanni Campagna's avatar Giovanni Campagna
Browse files

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
parent 9ae8271a
......@@ -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 = \
......
......@@ -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;
......
......@@ -102,6 +102,41 @@ gjs_define_enum_value(JSContext *context,
return JS_TRUE;
}
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,
......@@ -109,11 +144,9 @@ gjs_define_enumeration(JSContext *context,
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",
......
......@@ -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,
......
......@@ -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;
......
/* -*- 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)) {