summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGiovanni Campagna <gcampagna@src.gnome.org>2012-07-05 16:05:47 +0200
committerGiovanni Campagna <gcampagna@src.gnome.org>2012-08-08 00:58:45 +0200
commit6ed5e40b4f62992c5b18ab8c3034072dd12e60c9 (patch)
tree1692a9e37965637985518a699f647dfcbb563b87
parent4d31cc9c544b1403c21a14ccb077190ae87de0d6 (diff)
downloadgjs-6ed5e40b4f62992c5b18ab8c3034072dd12e60c9.tar.gz
Rework typechecking and private data access
Previously, all type safety checks were inside priv_from_js, which returned NULL if the object was of the wrong class. Except that a lot of code was not checking for NULL, causing potential crashes. Also, we were only checking the JSClass, not the actual type of object, so for example a function accepting a boxed would take in any boxed instance, not just the right one (and then that could crash JS code). Rework this by splitting typechecking and private data access. Internal API for typechecking is provided by all object classes (object, boxed, union, param, gerror) and it takes the necessary amount of information (GType, GIBaseInfo) to ensure type safety. priv_from_js becomes unsafe, and thus ok to call in dangerous data paths such as tracing and finalization. https://bugzilla.gnome.org/show_bug.cgi?id=679688
-rw-r--r--gi/arg.c85
-rw-r--r--gi/boxed.c58
-rw-r--r--gi/boxed.h5
-rw-r--r--gi/function.c94
-rw-r--r--gi/gerror.c8
-rw-r--r--gi/gerror.h3
-rw-r--r--gi/object.c102
-rw-r--r--gi/object.h4
-rw-r--r--gi/param.c42
-rw-r--r--gi/param.h4
-rw-r--r--gi/union.c69
-rw-r--r--gi/union.h5
-rw-r--r--gi/value.c35
-rw-r--r--gjs/jsapi-util-error.c27
-rw-r--r--gjs/jsapi-util.c99
-rw-r--r--gjs/jsapi-util.h123
-rw-r--r--modules/system.c5
-rw-r--r--test/js/testEverythingBasic.js47
18 files changed, 592 insertions, 223 deletions
diff --git a/gi/arg.c b/gi/arg.c
index 2d576277..39b2c2cd 100644
--- a/gi/arg.c
+++ b/gi/arg.c
@@ -1282,11 +1282,16 @@ gjs_value_to_g_argument(JSContext *context,
if (JSVAL_IS_NULL(value)) {
arg->v_pointer = NULL;
} else if (JSVAL_IS_OBJECT(value)) {
- arg->v_pointer = gjs_gerror_from_error(context,
- JSVAL_TO_OBJECT(value));
+ if (gjs_typecheck_gerror(context, JSVAL_TO_OBJECT(value),
+ JS_TRUE)) {
+ arg->v_pointer = gjs_gerror_from_error(context,
+ JSVAL_TO_OBJECT(value));
- if (transfer != GI_TRANSFER_NOTHING)
- arg->v_pointer = g_error_copy (arg->v_pointer);
+ if (transfer != GI_TRANSFER_NOTHING)
+ arg->v_pointer = g_error_copy (arg->v_pointer);
+ } else {
+ wrong = TRUE;
+ }
} else {
wrong = TRUE;
report_type_mismatch = TRUE;
@@ -1363,14 +1368,26 @@ gjs_value_to_g_argument(JSContext *context,
/* 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));
+ if (!gjs_typecheck_gerror(context, JSVAL_TO_OBJECT(value), JS_TRUE)) {
+ arg->v_pointer = NULL;
+ wrong = TRUE;
+ } else {
+ 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 (!gjs_typecheck_boxed(context, JSVAL_TO_OBJECT(value),
+ interface_info, gtype,
+ JS_TRUE)) {
+ arg->v_pointer = NULL;
+ wrong = TRUE;
+ } else {
+ arg->v_pointer = gjs_c_struct_from_boxed(context,
+ JSVAL_TO_OBJECT(value));
+ }
}
- if (transfer != GI_TRANSFER_NOTHING) {
+ if (!wrong && transfer != GI_TRANSFER_NOTHING) {
if (g_type_is_a(gtype, G_TYPE_BOXED))
arg->v_pointer = g_boxed_copy (gtype, arg->v_pointer);
else if (g_type_is_a(gtype, G_TYPE_VARIANT))
@@ -1384,37 +1401,39 @@ gjs_value_to_g_argument(JSContext *context,
}
} else if (interface_type == GI_INFO_TYPE_UNION) {
- arg->v_pointer = gjs_c_union_from_union(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);
- else {
- gjs_throw(context,
- "Can't transfer ownership of a union type not registered as boxed");
+ if (gjs_typecheck_union(context, JSVAL_TO_OBJECT(value),
+ interface_info, gtype, JS_TRUE)) {
+ arg->v_pointer = gjs_c_union_from_union(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);
+ else {
+ gjs_throw(context,
+ "Can't transfer ownership of a union type not registered as boxed");
- arg->v_pointer = NULL;
- wrong = TRUE;
+ arg->v_pointer = NULL;
+ wrong = TRUE;
+ }
}
+ } else {
+ arg->v_pointer = NULL;
+ wrong = TRUE;
}
} else if (gtype != G_TYPE_NONE) {
-
if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
- arg->v_pointer = gjs_g_object_from_object(context,
- JSVAL_TO_OBJECT(value));
- if (arg->v_pointer != NULL) {
- if (!g_type_is_a(G_TYPE_FROM_INSTANCE(arg->v_pointer),
- gtype)) {
- gjs_throw(context,
- "Expected type '%s' but got '%s'",
- g_type_name(gtype),
- g_type_name(G_TYPE_FROM_INSTANCE(arg->v_pointer)));
- arg->v_pointer = NULL;
- wrong = TRUE;
- } else if (transfer != GI_TRANSFER_NOTHING)
+ if (gjs_typecheck_object(context, JSVAL_TO_OBJECT(value),
+ gtype, JS_TRUE)) {
+ arg->v_pointer = gjs_g_object_from_object(context,
+ JSVAL_TO_OBJECT(value));
+
+ if (transfer != GI_TRANSFER_NOTHING)
g_object_ref(G_OBJECT(arg->v_pointer));
+ } else {
+ arg->v_pointer = NULL;
+ wrong = TRUE;
}
} else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
if (g_type_is_a(gtype, G_TYPE_CLOSURE)) {
diff --git a/gi/boxed.c b/gi/boxed.c
index 62c0be0c..2b4fb181 100644
--- a/gi/boxed.c
+++ b/gi/boxed.c
@@ -1261,17 +1261,61 @@ gjs_c_struct_from_boxed(JSContext *context,
return NULL;
priv = priv_from_js(context, obj);
-
if (priv == NULL)
return NULL;
+ return priv->gboxed;
+}
+
+JSBool
+gjs_typecheck_boxed(JSContext *context,
+ JSObject *object,
+ GIStructInfo *expected_info,
+ GType expected_type,
+ JSBool throw)
+{
+ Boxed *priv;
+ JSBool result;
+
+ if (!do_base_typecheck(context, object, throw))
+ return JS_FALSE;
+
+ priv = priv_from_js(context, object);
+
if (priv->gboxed == 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;
+ if (throw) {
+ gjs_throw_custom(context, "TypeError",
+ "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 JS_FALSE;
}
- return priv->gboxed;
+ if (expected_type != G_TYPE_NONE)
+ result = g_type_is_a (priv->gtype, expected_type);
+ else if (expected_info != NULL)
+ result = g_base_info_equal((GIBaseInfo*) priv->info, (GIBaseInfo*) expected_info);
+ else
+ result = JS_TRUE;
+
+ if (!result && throw) {
+ if (expected_info != NULL) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s.%s - cannot convert to %s.%s",
+ g_base_info_get_namespace((GIBaseInfo*) priv->info),
+ g_base_info_get_name((GIBaseInfo*) priv->info),
+ g_base_info_get_namespace((GIBaseInfo*) expected_info),
+ g_base_info_get_name((GIBaseInfo*) expected_info));
+ } else {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s.%s - cannot convert to %s",
+ g_base_info_get_namespace((GIBaseInfo*) priv->info),
+ g_base_info_get_name((GIBaseInfo*) priv->info),
+ g_type_name(expected_type));
+ }
+ }
+
+ return result;
}
diff --git a/gi/boxed.h b/gi/boxed.h
index 3ac1ba03..6f324568 100644
--- a/gi/boxed.h
+++ b/gi/boxed.h
@@ -57,6 +57,11 @@ JSObject* gjs_boxed_from_c_struct (JSContext *context,
GIStructInfo *info,
void *gboxed,
GjsBoxedCreationFlags flags);
+JSBool gjs_typecheck_boxed (JSContext *context,
+ JSObject *obj,
+ GIStructInfo *expected_info,
+ GType expected_type,
+ JSBool throw);
G_END_DECLS
diff --git a/gi/function.c b/gi/function.c
index 03bd5e87..a1fe2b25 100644
--- a/gi/function.c
+++ b/gi/function.c
@@ -508,6 +508,59 @@ get_length_from_arg (GArgument *arg, GITypeTag tag)
}
static JSBool
+gjs_fill_method_instance (JSContext *context,
+ JSObject *obj,
+ Function *function,
+ GIArgument *out_arg)
+{
+ GIBaseInfo *container = g_base_info_get_container((GIBaseInfo *) function->info);
+ GIInfoType type = g_base_info_get_type(container);
+ GType gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *)container);
+
+ switch (type) {
+ case GI_INFO_TYPE_STRUCT:
+ case GI_INFO_TYPE_BOXED:
+ /* GError must be special cased */
+ if (g_type_is_a(gtype, G_TYPE_ERROR)) {
+ if (!gjs_typecheck_gerror(context, obj, JS_TRUE))
+ return JS_FALSE;
+
+ out_arg->v_pointer = gjs_gerror_from_error(context, obj);
+ } else {
+ if (!gjs_typecheck_boxed(context, obj,
+ container, gtype,
+ JS_TRUE))
+ return JS_FALSE;
+
+ out_arg->v_pointer = gjs_c_struct_from_boxed(context, obj);
+ }
+ break;
+
+ case GI_INFO_TYPE_UNION:
+ if (!gjs_typecheck_union(context, obj,
+ container, gtype, JS_TRUE))
+ return JS_FALSE;
+
+ out_arg->v_pointer = gjs_c_union_from_union(context, obj);
+ break;
+
+ case GI_INFO_TYPE_OBJECT:
+ case GI_INFO_TYPE_INTERFACE:
+ if (!gjs_typecheck_object(context, obj,
+ gtype, JS_TRUE))
+ return JS_FALSE;
+
+ out_arg->v_pointer = gjs_g_object_from_object(context, obj);
+ break;
+
+ default:
+ g_assert_not_reached();
+ }
+
+ return JS_TRUE;
+}
+
+static JSBool
gjs_invoke_c_function(JSContext *context,
Function *function,
JSObject *obj, /* "this" object */
@@ -630,44 +683,9 @@ gjs_invoke_c_function(JSContext *context,
js_arg_pos = 0; /* index into argv */
if (is_method) {
- GIBaseInfo *container = g_base_info_get_container((GIBaseInfo *) function->info);
- GIInfoType type = g_base_info_get_type(container);
-
- g_assert_cmpuint(0, <, c_argc);
-
- if (type == GI_INFO_TYPE_STRUCT || type == GI_INFO_TYPE_BOXED) {
- 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 */
- GType gtype;
-
- in_arg_cvalues[0].v_pointer = gjs_g_object_from_object(context, obj);
- if (in_arg_cvalues[0].v_pointer == NULL) {
- /* priv == NULL (user probably forgot to chain _init).
- * Anyway, in this case we've thrown an exception, so just
- * make sure we fail. */
- failed = TRUE;
- goto release;
- }
-
- gtype = g_registered_type_info_get_g_type ((GIRegisteredTypeInfo *)container);
- if (!g_type_is_a (G_TYPE_FROM_INSTANCE (in_arg_cvalues[0].v_pointer),
- gtype)) {
- gjs_throw(context,
- "Expected type '%s' but got '%s'",
- g_type_name(gtype),
- g_type_name(G_TYPE_FROM_INSTANCE(in_arg_cvalues[0].v_pointer)));
- failed = TRUE;
- goto release;
- }
- }
+ if (!gjs_fill_method_instance(context, obj,
+ function, &in_arg_cvalues[0]))
+ return JS_FALSE;
ffi_arg_pointers[0] = &in_arg_cvalues[0];
++c_arg_pos;
}
diff --git a/gi/gerror.c b/gi/gerror.c
index b5ec1ab2..5c035a19 100644
--- a/gi/gerror.c
+++ b/gi/gerror.c
@@ -650,3 +650,11 @@ gjs_gerror_from_error(JSContext *context,
return priv->gerror;
}
+
+JSBool
+gjs_typecheck_gerror (JSContext *context,
+ JSObject *obj,
+ JSBool throw)
+{
+ return do_base_typecheck(context, obj, throw);
+}
diff --git a/gi/gerror.h b/gi/gerror.h
index 3940d1ab..eddee610 100644
--- a/gi/gerror.h
+++ b/gi/gerror.h
@@ -48,6 +48,9 @@ GError* gjs_gerror_from_error (JSContext *context,
JSObject* gjs_error_from_gerror (JSContext *context,
GError *gerror,
gboolean add_stack);
+JSBool gjs_typecheck_gerror (JSContext *context,
+ JSObject *obj,
+ JSBool throw);
G_END_DECLS
diff --git a/gi/object.c b/gi/object.c
index 7586b131..e7101000 100644
--- a/gi/object.c
+++ b/gi/object.c
@@ -1049,11 +1049,7 @@ object_instance_trace(JSTracer *tracer,
ObjectInstance *priv;
GList *iter;
- /* DO NOT use priv_from_js here: that uses JS_BeginRequest,
- but this is called from the GC thread, and deadlocks
- We know we're of the right JSClass anyway.
- */
- priv = JS_GetPrivate(tracer->context, obj);
+ priv = priv_from_js(tracer->context, obj);
for (iter = priv->signals; iter; iter = iter->next) {
ConnectData *cd = iter->data;
@@ -1172,6 +1168,9 @@ real_connect_func(JSContext *context,
ConnectData *connect_data;
JSBool ret = JS_FALSE;
+ if (!do_base_typecheck(context, obj, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(context, obj);
gjs_debug_gsignal("connect obj %p priv %p argc %d", obj, priv, argc);
if (priv == NULL) {
@@ -1271,6 +1270,9 @@ disconnect_func(JSContext *context,
ObjectInstance *priv;
gulong id;
+ if (!do_base_typecheck(context, obj, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(context, obj);
gjs_debug_gsignal("disconnect obj %p priv %p argc %d", obj, priv, argc);
@@ -1321,6 +1323,9 @@ emit_func(JSContext *context,
jsval retval;
JSBool ret = JS_FALSE;
+ if (!do_base_typecheck(context, obj, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(context, obj);
gjs_debug_gsignal("emit obj %p priv %p argc %d", obj, priv, argc);
@@ -1441,6 +1446,9 @@ to_string_func(JSContext *context,
const char *name;
jsval retval;
+ if (!do_base_typecheck(context, obj, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(context, obj);
if (priv == NULL) {
@@ -1896,25 +1904,70 @@ gjs_g_object_from_object(JSContext *context,
return NULL;
priv = priv_from_js(context, obj);
+ return priv->gobj;
+}
+
+JSBool
+gjs_typecheck_object(JSContext *context,
+ JSObject *object,
+ GType expected_type,
+ JSBool throw)
+{
+ ObjectInstance *priv;
+ JSBool result;
+
+ if (!do_base_typecheck(context, object, throw))
+ return JS_FALSE;
+
+ priv = priv_from_js(context, object);
if (priv == NULL) {
- gjs_throw(context,
- "Object instance or prototype has not been properly initialized yet. "
- "Did you forget to chain-up from _init()?");
- return NULL;
+ if (throw) {
+ gjs_throw(context,
+ "Object instance or prototype has not been properly initialized yet. "
+ "Did you forget to chain-up from _init()?");
+ }
+
+ return JS_FALSE;
}
if (priv->gobj == NULL) {
- gjs_throw(context,
- "Object is %s.%s.prototype, not an object instance - cannot convert to GObject*",
- priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "",
- priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype));
- return NULL;
+ if (throw) {
+ gjs_throw(context,
+ "Object is %s.%s.prototype, not an object instance - cannot convert to GObject*",
+ priv->info ? g_base_info_get_namespace( (GIBaseInfo*) priv->info) : "",
+ priv->info ? g_base_info_get_name( (GIBaseInfo*) priv->info) : g_type_name(priv->gtype));
+ }
+
+ return JS_FALSE;
}
- return priv->gobj;
+ g_assert(priv->gtype == G_OBJECT_TYPE(priv->gobj));
+
+ if (expected_type != G_TYPE_NONE)
+ result = g_type_is_a (priv->gtype, expected_type);
+ else
+ result = JS_TRUE;
+
+ if (!result && throw) {
+ if (priv->info) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s.%s - cannot convert to %s",
+ g_base_info_get_namespace((GIBaseInfo*) priv->info),
+ g_base_info_get_name((GIBaseInfo*) priv->info),
+ g_type_name(expected_type));
+ } else {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s - cannot convert to %s",
+ g_type_name(priv->gtype),
+ g_type_name(expected_type));
+ }
+ }
+
+ return result;
}
+
static void
find_vfunc_info (JSContext *context,
GType implementor_gtype,
@@ -2013,6 +2066,9 @@ gjs_hook_up_vfunc(JSContext *cx,
"function", &function))
return JS_FALSE;
+ if (!do_base_typecheck(cx, object, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(cx, object);
gtype = priv->gtype;
info = priv->info;
@@ -2233,6 +2289,9 @@ gjs_register_type(JSContext *cx,
if (!parent)
return JS_FALSE;
+ if (!do_base_typecheck(cx, parent, JS_TRUE))
+ return JS_FALSE;
+
parent_priv = priv_from_js(cx, parent);
if (!parent_priv)
@@ -2283,6 +2342,9 @@ gjs_add_interface(JSContext *cx,
"gtype", &iface_jsobj))
return JS_FALSE;
+ if (!do_base_typecheck(cx, object, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(cx, object);
g_type_add_interface_static(priv->gtype,
@@ -2313,6 +2375,11 @@ gjs_register_property(JSContext *cx,
obj = JSVAL_TO_OBJECT(argv[0]);
pspec_js = JSVAL_TO_OBJECT(argv[1]);
+ if (!do_base_typecheck(cx, obj, JS_TRUE))
+ return JS_FALSE;
+ if (!gjs_typecheck_param(cx, pspec_js, G_TYPE_NONE, JS_TRUE))
+ return JS_FALSE;
+
priv = priv_from_js(cx, obj);
pspec = gjs_g_param_from_param(cx, pspec_js);
@@ -2350,6 +2417,11 @@ gjs_signal_new(JSContext *cx,
}
obj = JSVAL_TO_OBJECT(argv[0]);
+ if (!do_base_typecheck(cx, obj, JS_TRUE)) {
+ ret = JS_FALSE;
+ goto out;
+ }
+
priv = priv_from_js(cx, obj);
/* we only support standard accumulators for now */
diff --git a/gi/object.h b/gi/object.h
index 8a39b5d2..1957abd0 100644
--- a/gi/object.h
+++ b/gi/object.h
@@ -43,6 +43,10 @@ JSObject* gjs_object_from_g_object (JSContext *context,
GObject *gobj);
GObject* gjs_g_object_from_object (JSContext *context,
JSObject *obj);
+JSBool gjs_typecheck_object (JSContext *context,
+ JSObject *obj,
+ GType expected_type,
+ JSBool throw);
G_END_DECLS
diff --git a/gi/param.c b/gi/param.c
index ae7bed59..509bdf78 100644
--- a/gi/param.c
+++ b/gi/param.c
@@ -618,14 +618,44 @@ gjs_g_param_from_param(JSContext *context,
priv = priv_from_js(context, obj);
- if (priv == NULL)
- return NULL;
+ return priv->gparam;
+}
+
+JSBool
+gjs_typecheck_param(JSContext *context,
+ JSObject *object,
+ GType expected_type,
+ JSBool throw)
+{
+ Param *priv;
+ JSBool result;
+
+ if (!do_base_typecheck(context, object, throw))
+ return JS_FALSE;
+
+ priv = priv_from_js(context, object);
if (priv->gparam == NULL) {
- gjs_throw(context,
- "Object is a prototype, not an object instance - cannot convert to a paramspec instance");
- return NULL;
+ if (throw) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is GObject.ParamSpec.prototype, not an object instance - "
+ "cannot convert to a GObject.ParamSpec instance");
+ }
+
+ return JS_FALSE;
}
- return priv->gparam;
+ if (expected_type != G_TYPE_NONE)
+ result = g_type_is_a (G_TYPE_FROM_INSTANCE (priv->gparam), expected_type);
+ else
+ result = JS_TRUE;
+
+ if (!result && throw) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s - cannot convert to %s",
+ g_type_name(G_TYPE_FROM_INSTANCE (priv->gparam)),
+ g_type_name(expected_type));
+ }
+
+ return result;
}
diff --git a/gi/param.h b/gi/param.h
index 6a50b43f..56d2fed5 100644
--- a/gi/param.h
+++ b/gi/param.h
@@ -39,6 +39,10 @@ GParamSpec* gjs_g_param_from_param (JSContext *context,
JSObject *obj);
JSObject* gjs_param_from_g_param (JSContext *context,
GParamSpec *param);
+JSBool gjs_typecheck_param (JSContext *context,
+ JSObject *obj,
+ GType expected_type,
+ JSBool throw);
JSObject* gjs_lookup_param_prototype (JSContext *context);
diff --git a/gi/union.c b/gi/union.c
index 36e9f4b8..53ff4681 100644
--- a/gi/union.c
+++ b/gi/union.c
@@ -44,6 +44,7 @@
typedef struct {
GIUnionInfo *info;
void *gboxed; /* NULL if we are the prototype and not an instance */
+ GType gtype;
} Union;
static struct JSClass gjs_union_class;
@@ -194,7 +195,6 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(union)
Union *priv;
Union *proto_priv;
JSObject *proto;
- GType gtype;
void *gboxed;
GJS_NATIVE_CONSTRUCTOR_PRELUDE(union);
@@ -226,8 +226,7 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(union)
priv->info = proto_priv->info;
g_base_info_ref( (GIBaseInfo*) priv->info);
-
- gtype = g_registered_type_info_get_g_type( (GIRegisteredTypeInfo*) priv->info);
+ priv->gtype = proto_priv->gtype;
/* union_new happens to be implemented by calling
* gjs_invoke_c_function(), which returns a jsval.
@@ -244,11 +243,11 @@ GJS_NATIVE_CONSTRUCTOR_DECLARE(union)
* be garbage collected, we make a copy here to be
* owned by us.
*/
- priv->gboxed = g_boxed_copy(gtype, gboxed);
+ priv->gboxed = g_boxed_copy(priv->gtype, gboxed);
gjs_debug_lifecycle(GJS_DEBUG_GBOXED,
"JSObject created with union instance %p type %s",
- priv->gboxed, g_type_name(gtype));
+ priv->gboxed, g_type_name(priv->gtype));
GJS_NATIVE_CONSTRUCTOR_FINISH(union);
@@ -454,6 +453,7 @@ gjs_define_union_class(JSContext *context,
priv = g_slice_new0(Union);
priv->info = info;
g_base_info_ref( (GIBaseInfo*) priv->info);
+ priv->gtype = gtype;
JS_SetPrivate(context, prototype, priv);
gjs_debug(GJS_DEBUG_GBOXED, "Defined class %s prototype is %p class %p in object %p",
@@ -520,6 +520,7 @@ gjs_union_from_c_union(JSContext *context,
JS_SetPrivate(context, obj, priv);
priv->info = info;
g_base_info_ref( (GIBaseInfo *) priv->info);
+ priv->gtype = gtype;
priv->gboxed = g_boxed_copy(gtype, gboxed);
return obj;
@@ -536,16 +537,58 @@ gjs_c_union_from_union(JSContext *context,
priv = priv_from_js(context, obj);
- if (priv == NULL)
- return NULL;
+ return priv->gboxed;
+}
+
+JSBool
+gjs_typecheck_union(JSContext *context,
+ JSObject *object,
+ GIStructInfo *expected_info,
+ GType expected_type,
+ JSBool throw)
+{
+ Union *priv;
+ JSBool result;
+
+ if (!do_base_typecheck(context, object, throw))
+ return JS_FALSE;
+
+ priv = priv_from_js(context, object);
if (priv->gboxed == NULL) {
- gjs_throw(context,
- "Object is %s.%s.prototype, not an object instance - cannot convert to a union instance",
- g_base_info_get_namespace( (GIBaseInfo*) priv->info),
- g_base_info_get_name( (GIBaseInfo*) priv->info));
- return NULL;
+ if (throw) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is %s.%s.prototype, not an object instance - cannot convert to a union instance",
+ g_base_info_get_namespace( (GIBaseInfo*) priv->info),
+ g_base_info_get_name( (GIBaseInfo*) priv->info));
+ }
+
+ return JS_FALSE;
}
- return priv->gboxed;
+ if (expected_type != G_TYPE_NONE)
+ result = g_type_is_a (priv->gtype, expected_type);
+ else if (expected_info != NULL)
+ result = g_base_info_equal((GIBaseInfo*) priv->info, (GIBaseInfo*) expected_info);
+ else
+ result = JS_TRUE;
+
+ if (!result && throw) {
+ if (expected_info != NULL) {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s.%s - cannot convert to %s.%s",
+ g_base_info_get_namespace((GIBaseInfo*) priv->info),
+ g_base_info_get_name((GIBaseInfo*) priv->info),
+ g_base_info_get_namespace((GIBaseInfo*) expected_info),
+ g_base_info_get_name((GIBaseInfo*) expected_info));
+ } else {
+ gjs_throw_custom(context, "TypeError",
+ "Object is of type %s.%s - cannot convert to %s",
+ g_base_info_get_namespace((GIBaseInfo*) priv->info),
+ g_base_info_get_name((GIBaseInfo*) priv->info),
+ g_type_name(expected_type));
+ }
+ }
+
+ return result;
}
diff --git a/gi/union.h b/gi/union.h
index 87c4f967..08aff51b 100644
--- a/gi/union.h
+++ b/gi/union.h
@@ -48,6 +48,11 @@ void* gjs_c_union_from_union (JSContext *context,
JSObject* gjs_union_from_c_union (JSContext *context,
GIUnionInfo *info,
void *gboxed);
+JSBool gjs_typecheck_union (JSContext *context,
+ JSObject *obj,
+ GIStructInfo *expected_info,
+ GType expected_type,
+ JSBool throw);
G_END_DECLS
diff --git a/gi/value.c b/gi/value.c
index 5d8fe16e..4d8de2ff 100644
--- a/gi/value.c
+++ b/gi/value.c
@@ -334,6 +334,11 @@ gjs_value_to_g_value_internal(JSContext *context,
} else if (JSVAL_IS_OBJECT(value)) {
JSObject *obj;
obj = JSVAL_TO_OBJECT(value);
+
+ if (!gjs_typecheck_object(context, obj,
+ gtype, JS_TRUE))
+ return JS_FALSE;
+
gobj = gjs_g_object_from_object(context, obj);
} else {
gjs_throw(context,
@@ -392,9 +397,28 @@ gjs_value_to_g_value_internal(JSContext *context,
if (g_type_is_a(gtype, G_TYPE_ERROR)) {
/* special case GError */
+ if (!gjs_typecheck_gerror(context, obj, JS_TRUE))
+ return JS_FALSE;
+
gboxed = gjs_gerror_from_error(context, obj);
} else {
- gboxed = gjs_c_struct_from_boxed(context, obj);
+ /* First try a union, if that fails,
+ assume a boxed struct. Distinguishing
+ which one is expected would require checking
+ the associated GIBaseInfo, which is not necessary
+ possible, if e.g. we see the GType without
+ loading the typelib.
+ */
+ if (gjs_typecheck_union(context, obj,
+ NULL, gtype, JS_FALSE)) {
+ gboxed = gjs_c_union_from_union(context, obj);
+ } else {
+ if (!gjs_typecheck_boxed(context, obj,
+ NULL, gtype, JS_TRUE))
+ return JS_FALSE;
+
+ gboxed = gjs_c_struct_from_boxed(context, obj);
+ }
}
} else {
gjs_throw(context,
@@ -415,6 +439,11 @@ gjs_value_to_g_value_internal(JSContext *context,
/* nothing to do */
} else if (JSVAL_IS_OBJECT(value)) {
JSObject *obj = JSVAL_TO_OBJECT(value);
+
+ if (!gjs_typecheck_boxed(context, obj,
+ NULL, G_TYPE_VARIANT, JS_TRUE))
+ return JS_FALSE;
+
variant = gjs_c_struct_from_boxed(context, obj);
} else {
gjs_throw(context,
@@ -474,6 +503,10 @@ gjs_value_to_g_value_internal(JSContext *context,
} else if (JSVAL_IS_OBJECT(value)) {
JSObject *obj;
obj = JSVAL_TO_OBJECT(value);
+
+ if (!gjs_typecheck_param(context, obj, gtype, JS_TRUE))
+ return JS_FALSE;
+
gparam = gjs_g_param_from_param(context, obj);
} else {
gjs_throw(context,
diff --git a/gjs/jsapi-util-error.c b/gjs/jsapi-util-error.c
index 8227fe1d..c008b8c4 100644
--- a/gjs/jsapi-util-error.c
+++ b/gjs/jsapi-util-error.c
@@ -43,8 +43,9 @@
*/
static void
gjs_throw_valist(JSContext *context,
- const char *format,
- va_list args)
+ const char *error_class,
+ const char *format,
+ va_list args)
{
char *s;
JSBool result;
@@ -82,7 +83,7 @@ gjs_throw_valist(JSContext *context,
}
if (!gjs_object_get_property(context, JS_GetGlobalObject(context),
- "Error", &v_constructor)) {
+ error_class, &v_constructor)) {
JS_ReportError(context, "??? Missing Error constructor in global object?");
goto out;
}
@@ -125,7 +126,25 @@ gjs_throw(JSContext *context,
va_list args;
va_start(args, format);
- gjs_throw_valist(context, format, args);
+ gjs_throw_valist(context, "Error", format, args);
+ va_end(args);
+}
+
+/*
+ * Like gjs_throw, but allows to customize the error
+ * class. Mainly used for throwing TypeError instead of
+ * error.
+ */
+void
+gjs_throw_custom(JSContext *context,
+ const char *error_class,
+ const char *format,
+ ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ gjs_throw_valist(context, error_class, format, args);
va_end(args);
}
diff --git a/gjs/jsapi-util.c b/gjs/jsapi-util.c
index ca2965e9..59df4356 100644
--- a/gjs/jsapi-util.c
+++ b/gjs/jsapi-util.c
@@ -567,64 +567,48 @@ gjs_throw_constructor_error(JSContext *context)
"Constructor called as normal method. Use 'new SomeObject()' not 'SomeObject()'");
}
-void*
-gjs_get_instance_private_dynamic(JSContext *context,
- JSObject *obj,
- JSClass *static_clasp,
- jsval *argv)
+static const char*
+format_dynamic_class_name (const char *name)
{
- RuntimeData *rd;
- JSClass *obj_class;
- void *instance;
-
- if (static_clasp->name != NULL) {
- g_warning("Dynamic class should not have a name in the JSClass struct");
- return NULL;
- }
-
- JS_BeginRequest(context);
-
- obj_class = JS_GET_CLASS(context, obj);
- g_assert(obj_class != NULL);
+ if (g_str_has_prefix(name, "_private_"))
+ return name + strlen("_private_");
+ else
+ return name;
+}
- rd = get_data_from_context(context);
- g_assert(rd != NULL);
+JSBool
+gjs_typecheck_static_instance(JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ JSBool throw)
+{
+ if (!JS_InstanceOf(context, obj, static_clasp, NULL)) {
+ if (throw) {
+ JSClass *obj_class = JS_GET_CLASS(context, obj);
- /* Check that it's safe to cast to DynamicJSClass */
- if (g_hash_table_lookup(rd->dynamic_classes, obj_class) == NULL) {
- gjs_throw(context,
- "Object %p proto %p doesn't have a dynamically-registered class, it has %s",
- obj, JS_GetPrototype(context, obj), obj_class->name);
- JS_EndRequest(context);
- return NULL;
- }
+ gjs_throw_custom(context, "TypeError",
+ "Object %p is not a subclass of %s, it's a %s",
+ obj, static_clasp->name, format_dynamic_class_name (obj_class->name));
+ }
- if (static_clasp != ((DynamicJSClass*) obj_class)->static_class) {
- gjs_throw(context, "Object is not a dynamically-registered class based on expected static class pointer");
- JS_EndRequest(context);
- return NULL;
+ return JS_FALSE;
}
- instance = JS_GetInstancePrivate(context, obj, obj_class, argv);
- JS_EndRequest(context);
-
- return instance;
+ return JS_TRUE;
}
-void*
-gjs_get_instance_private_dynamic_with_typecheck(JSContext *context,
- JSObject *obj,
- JSClass *static_clasp,
- jsval *argv)
+JSBool
+gjs_typecheck_dynamic_instance(JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ JSBool throw)
{
RuntimeData *rd;
JSClass *obj_class;
- void *instance;
+ gboolean wrong = FALSE;
- if (static_clasp->name != NULL) {
- g_warning("Dynamic class should not have a name in the JSClass struct");
- return NULL;
- }
+ obj_class = JS_GET_CLASS(context, obj);
+ g_assert(obj_class != NULL);
JS_BeginRequest(context);
@@ -636,18 +620,29 @@ gjs_get_instance_private_dynamic_with_typecheck(JSContext *context,
/* Check that it's safe to cast to DynamicJSClass */
if (g_hash_table_lookup(rd->dynamic_classes, obj_class) == NULL) {
- JS_EndRequest(context);
- return NULL;
+ wrong = TRUE;
+ goto out;
}
if (static_clasp != ((DynamicJSClass*) obj_class)->static_class) {
- JS_EndRequest(context);
- return NULL;
+ wrong = TRUE;
+ goto out;
}
- instance = JS_GetInstancePrivate(context, obj, obj_class, argv);
+ out:
JS_EndRequest(context);
- return instance;
+
+ if (wrong) {
+ if (throw) {
+ gjs_throw_custom(context, "TypeError",
+ "Object %p is not a subclass of %s, it's a %s",
+ obj, static_clasp->name, format_dynamic_class_name (obj_class->name));
+ }
+
+ return JS_FALSE;
+ }
+
+ return JS_TRUE;
}
JSObject*
diff --git a/gjs/jsapi-util.h b/gjs/jsapi-util.h
index 0e186f9a..191dfd85 100644
--- a/gjs/jsapi-util.h
+++ b/gjs/jsapi-util.h
@@ -55,54 +55,67 @@ typedef struct GjsRootedArray GjsRootedArray;
*/
#define GJS_MODULE_PROP_FLAGS (JSPROP_PERMANENT | JSPROP_ENUMERATE)
-/* priv_from_js_with_typecheck checks that the object is in fact an
- * instance of the specified class before accessing its private data.
- * Keep in mind that the function can return JS_TRUE and still fill the
- * out parameter with NULL if the object is the prototype for a class
- * without the JSCLASS_CONSTRUCT_PROTOTYPE flag or if it the class simply
- * does not have any private data.
+/*
+ * Helper methods to access private data:
+ *
+ * do_base_typecheck: checks that object has the right JSClass, and possibly
+ * throw a TypeError exception if the check fails
+ * priv_from_js: accesses the object private field; as a debug measure,
+ * it also checks that the object is of a compatible
+ * JSClass, but it doesn't raise an exception (it
+ * wouldn't be of much use, if subsequent code crashes on
+ * NULL)
+ * priv_from_js_with_typecheck: a convenience function to call
+ * do_base_typecheck and priv_from_js
*/
-#define GJS_DEFINE_PRIV_FROM_JS(type, class) \
- __attribute__((unused)) static JSBool \
- priv_from_js_with_typecheck(JSContext *context, \
- JSObject *object, \
- type **out) \
- {\
- if (!out) \
- return JS_FALSE; \
- if (!JS_InstanceOf(context, object, &class, NULL)) \
- return JS_FALSE; \
- *out = JS_GetInstancePrivate(context, object, &class, NULL); \
- return JS_TRUE; \
- }\
- static type*\
- priv_from_js(JSContext *context, \
- JSObject *object) \
- {\
- return JS_GetInstancePrivate(context, object, &class, NULL); \
+#define GJS_DEFINE_PRIV_FROM_JS(type, class) \
+ __attribute__((unused)) static inline JSBool \
+ do_base_typecheck(JSContext *context, \
+ JSObject *object, \
+ JSBool throw) \
+ { \
+ return gjs_typecheck_static_instance(context, object, &class, throw); \
+ } \
+ static inline type* \
+ priv_from_js(JSContext *context, \
+ JSObject *object) \
+ { \
+ return JS_GetInstancePrivate(context, object, &class, NULL); \
+ } \
+ __attribute__((unused)) static JSBool \
+ priv_from_js_with_typecheck(JSContext *context, \
+ JSObject *object, \
+ type **out) \
+ { \
+ if (!do_base_typecheck(context, object, JS_FALSE)) \
+ return JS_FALSE; \
+ *out = priv_from_js(context, object); \
+ return JS_TRUE; \
}
-
-#define GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(type, class) \
- __attribute__((unused)) static JSBool\
- priv_from_js_with_typecheck(JSContext *context, \
- JSObject *object, \
- type **out) \
- {\
- type *result; \
- if (!out) \
- return JS_FALSE; \
- result = gjs_get_instance_private_dynamic_with_typecheck(context, object, &class, NULL); \
- if (result == NULL) \
- return JS_FALSE; \
- *out = result; \
- return JS_TRUE; \
- }\
- static type*\
- priv_from_js(JSContext *context, \
- JSObject *object) \
- {\
- return gjs_get_instance_private_dynamic(context, object, &class, NULL); \
+#define GJS_DEFINE_DYNAMIC_PRIV_FROM_JS(type, class) \
+ __attribute__((unused)) static inline JSBool \
+ do_base_typecheck(JSContext *context, \
+ JSObject *object, \
+ JSBool throw) \
+ { \
+ return gjs_typecheck_dynamic_instance(context, object, &class, throw); \
+ } \
+ static inline type* \
+ priv_from_js(JSContext *context, \
+ JSObject *object) \
+ { \
+ return JS_GetPrivate(context, object); \
+ } \
+ __attribute__((unused)) static JSBool \
+ priv_from_js_with_typecheck(JSContext *context, \
+ JSObject *object, \
+ type **out) \
+ { \
+ if (!do_base_typecheck(context, object, JS_FALSE)) \
+ return JS_FALSE; \
+ *out = priv_from_js(context, object); \
+ return JS_TRUE; \
}
/**
@@ -227,14 +240,14 @@ JSObject * gjs_init_class_dynamic (JSContext *context,
JSFunctionSpec *static_fs);
void gjs_throw_constructor_error (JSContext *context);
-void* gjs_get_instance_private_dynamic (JSContext *context,
- JSObject *obj,
- JSClass *static_clasp,
- jsval *argv);
-void* gjs_get_instance_private_dynamic_with_typecheck (JSContext *context,
- JSObject *obj,
- JSClass *static_clasp,
- jsval *argv);
+JSBool gjs_typecheck_static_instance (JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ JSBool _throw);
+JSBool gjs_typecheck_dynamic_instance (JSContext *context,
+ JSObject *obj,
+ JSClass *static_clasp,
+ JSBool _throw);
JSObject* gjs_construct_object_dynamic (JSContext *context,
JSObject *proto,
@@ -249,6 +262,10 @@ JSObject* gjs_define_string_array (JSContext *context,
void gjs_throw (JSContext *context,
const char *format,
...) G_GNUC_PRINTF (2, 3);
+void gjs_throw_custom (JSContext *context,
+ const char *error_class,
+ const char *format,
+ ...) G_GNUC_PRINTF (3, 4);
void gjs_throw_literal (JSContext *context,
const char *string);
void gjs_throw_g_error (JSContext *context,
diff --git a/modules/system.c b/modules/system.c
index 60b9cce9..7089d8bd 100644
--- a/modules/system.c
+++ b/modules/system.c
@@ -69,8 +69,11 @@ gjs_refcount(JSContext *context,
if (!gjs_parse_args(context, "refcount", "o", argc, argv, "object", &target_obj))
return JS_FALSE;
- obj = gjs_g_object_from_object(context, target_obj);
+ if (!gjs_typecheck_object(context, target_obj,
+ G_TYPE_OBJECT, JS_TRUE))
+ return JS_FALSE;
+ obj = gjs_g_object_from_object(context, target_obj);
if (obj == NULL)
return JS_FALSE;
diff --git a/test/js/testEverythingBasic.js b/test/js/testEverythingBasic.js
index fdcd3ef5..99c35b94 100644
--- a/test/js/testEverythingBasic.js
+++ b/test/js/testEverythingBasic.js
@@ -550,4 +550,51 @@ function testGError() {
});
}
+function testWrongClassGObject() {
+ /* Function calls */
+ // Everything.func_obj_null_in expects a Everything.TestObj
+ assertRaises(function() {
+ Everything.func_obj_null_in(new Gio.SimpleAction);
+ });
+ assertRaises(function() {
+ Everything.func_obj_null_in(new GLib.KeyFile);
+ });
+ assertRaises(function() {
+ Everything.func_obj_null_in(Gio.File.new_for_path('/'));
+ });
+ Everything.func_obj_null_in(new Everything.TestSubObj);
+
+ /* Method calls */
+ assertRaises(function() {
+ Everything.TestObj.prototype.instance_method.call(new Gio.SimpleAction);
+ });
+ assertRaises(function() {
+ Everything.TestObj.prototype.instance_method.call(new GLib.KeyFile);
+ });
+ Everything.TestObj.prototype.instance_method.call(new Everything.TestSubObj);
+}
+
+function testWrongClassGBoxed() {
+ let simpleBoxed = new Everything.TestSimpleBoxedA;
+ // simpleBoxed.equals expects a Everything.TestSimpleBoxedA
+ assertRaises(function() {
+ simpleBoxed.equals(new Gio.SimpleAction);
+ })
+ assertRaises(function() {
+ simpleBoxed.equals(new Everything.TestObj);
+ })
+ assertRaises(function() {
+ simpleBoxed.equals(new GLib.KeyFile);
+ })
+ assertTrue(simpleBoxed.equals(simpleBoxed));
+
+ assertRaises(function() {
+ Everything.TestSimpleBoxedA.prototype.copy.call(new Gio.SimpleAction);
+ });
+ assertRaises(function() {
+ Everything.TestSimpleBoxedA.prototype.copy.call(new GLib.KeyFile);
+ })
+ Everything.TestSimpleBoxedA.prototype.copy.call(simpleBoxed);
+}
+
gjstestRun();