1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /*
3 * Copyright (c) 2008 litl, LLC
4 *
5 * Permission is hereby granted, free of charge, to any person obtaining a copy
6 * of this software and associated documentation files (the "Software"), to
7 * deal in the Software without restriction, including without limitation the
8 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9 * sell copies of the Software, and to permit persons to whom the Software is
10 * furnished to do so, subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be included in
13 * all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21 * IN THE SOFTWARE.
22 */
23
24 #include <config.h>
25
26 #include <limits.h> // for SCHAR_MAX, SCHAR_MIN, UCHAR_MAX
27 #include <stdint.h>
28 #include <string.h> // for memset
29
30 #include <girepository.h>
31 #include <glib-object.h>
32 #include <glib.h>
33
34 #include <js/CharacterEncoding.h>
35 #include <js/Conversions.h>
36 #include <js/GCVector.h> // for RootedVector
37 #include <js/RootingAPI.h>
38 #include <js/TypeDecls.h>
39 #include <js/Utility.h> // for UniqueChars
40 #include <js/Value.h>
41 #include <js/ValueArray.h>
42 #include <jsapi.h> // for InformalValueTypeName, JS_ClearPendingException
43 #include <mozilla/Unused.h>
44
45 #include "gi/arg-inl.h"
46 #include "gi/arg.h"
47 #include "gi/boxed.h"
48 #include "gi/closure.h"
49 #include "gi/foreign.h"
50 #include "gi/fundamental.h"
51 #include "gi/gerror.h"
52 #include "gi/gtype.h"
53 #include "gi/object.h"
54 #include "gi/param.h"
55 #include "gi/union.h"
56 #include "gi/value.h"
57 #include "gi/wrapperutils.h"
58 #include "cjs/atoms.h"
59 #include "cjs/context-private.h"
60 #include "cjs/context.h"
61 #include "cjs/jsapi-util.h"
62 #include "util/log.h"
63
64 GJS_JSAPI_RETURN_CONVENTION
65 static bool gjs_value_from_g_value_internal(JSContext *context,
66 JS::MutableHandleValue value_p,
67 const GValue *gvalue,
68 bool no_copy,
69 GSignalQuery *signal_query,
70 int arg_n);
71
72 /*
73 * Gets signal introspection info about closure, or NULL if not found. Currently
74 * only works for signals on introspected GObjects, not signals on GJS-defined
75 * GObjects nor standalone closures. The return value must be unreffed.
76 */
get_signal_info_if_available(GSignalQuery * signal_query)77 [[nodiscard]] static GISignalInfo* get_signal_info_if_available(
78 GSignalQuery* signal_query) {
79 GIBaseInfo *obj;
80 GIInfoType info_type;
81 GISignalInfo *signal_info = NULL;
82
83 if (!signal_query->itype)
84 return NULL;
85
86 obj = g_irepository_find_by_gtype(NULL, signal_query->itype);
87 if (!obj)
88 return NULL;
89
90 info_type = g_base_info_get_type (obj);
91 if (info_type == GI_INFO_TYPE_OBJECT)
92 signal_info = g_object_info_find_signal((GIObjectInfo*)obj,
93 signal_query->signal_name);
94 else if (info_type == GI_INFO_TYPE_INTERFACE)
95 signal_info = g_interface_info_find_signal((GIInterfaceInfo*)obj,
96 signal_query->signal_name);
97 g_base_info_unref((GIBaseInfo*)obj);
98
99 return signal_info;
100 }
101
102 /*
103 * Fill in value_p with a JS array, converted from a C array stored as a pointer
104 * in array_value, with its length stored in array_length_value.
105 */
106 GJS_JSAPI_RETURN_CONVENTION
107 static bool
gjs_value_from_array_and_length_values(JSContext * context,JS::MutableHandleValue value_p,GITypeInfo * array_type_info,const GValue * array_value,const GValue * array_length_value,bool no_copy,GSignalQuery * signal_query,int array_length_arg_n)108 gjs_value_from_array_and_length_values(JSContext *context,
109 JS::MutableHandleValue value_p,
110 GITypeInfo *array_type_info,
111 const GValue *array_value,
112 const GValue *array_length_value,
113 bool no_copy,
114 GSignalQuery *signal_query,
115 int array_length_arg_n)
116 {
117 JS::RootedValue array_length(context);
118 GArgument array_arg;
119
120 g_assert(G_VALUE_HOLDS_POINTER(array_value));
121 g_assert(G_VALUE_HOLDS_INT(array_length_value));
122
123 if (!gjs_value_from_g_value_internal(context, &array_length,
124 array_length_value, no_copy,
125 signal_query, array_length_arg_n))
126 return false;
127
128 gjs_arg_set(&array_arg, g_value_get_pointer(array_value));
129
130 return gjs_value_from_explicit_array(context, value_p, array_type_info,
131 &array_arg, array_length.toInt32());
132 }
133
134 static void
closure_marshal(GClosure * closure,GValue * return_value,guint n_param_values,const GValue * param_values,gpointer invocation_hint,gpointer marshal_data)135 closure_marshal(GClosure *closure,
136 GValue *return_value,
137 guint n_param_values,
138 const GValue *param_values,
139 gpointer invocation_hint,
140 gpointer marshal_data)
141 {
142 JSContext *context;
143 unsigned i;
144 GSignalQuery signal_query = { 0, };
145 GISignalInfo *signal_info;
146 bool *skip;
147 int *array_len_indices_for;
148 GITypeInfo **type_info_for;
149
150 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
151 "Marshal closure %p",
152 closure);
153
154 if (!gjs_closure_is_valid(closure)) {
155 /* We were destroyed; become a no-op */
156 return;
157 }
158
159 context = gjs_closure_get_context(closure);
160 GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
161 if (G_UNLIKELY(gjs->sweeping())) {
162 GSignalInvocationHint *hint = (GSignalInvocationHint*) invocation_hint;
163
164 g_critical("Attempting to call back into JSAPI during the sweeping phase of GC. "
165 "This is most likely caused by not destroying a Clutter actor or Gtk+ "
166 "widget with ::destroy signals connected, but can also be caused by "
167 "using the destroy(), dispose(), or remove() vfuncs. "
168 "Because it would crash the application, it has been "
169 "blocked and the JS callback not invoked.");
170 if (hint) {
171 gpointer instance;
172 g_signal_query(hint->signal_id, &signal_query);
173
174 instance = g_value_peek_pointer(¶m_values[0]);
175 g_critical("The offending signal was %s on %s %p.", signal_query.signal_name,
176 g_type_name(G_TYPE_FROM_INSTANCE(instance)), instance);
177 }
178 gjs_dumpstack();
179 return;
180 }
181
182 JSFunction* func = gjs_closure_get_callable(closure);
183 JSAutoRealm ar(context, JS_GetFunctionObject(func));
184
185 if (marshal_data) {
186 /* we are used for a signal handler */
187 guint signal_id;
188
189 signal_id = GPOINTER_TO_UINT(marshal_data);
190
191 g_signal_query(signal_id, &signal_query);
192
193 if (!signal_query.signal_id) {
194 gjs_debug(GJS_DEBUG_GCLOSURE,
195 "Signal handler being called on invalid signal");
196 return;
197 }
198
199 if (signal_query.n_params + 1 != n_param_values) {
200 gjs_debug(GJS_DEBUG_GCLOSURE,
201 "Signal handler being called with wrong number of parameters");
202 return;
203 }
204 }
205
206 /* Check if any parameters, such as array lengths, need to be eliminated
207 * before we invoke the closure.
208 */
209 skip = g_newa(bool, n_param_values);
210 memset(skip, 0, sizeof (bool) * n_param_values);
211 array_len_indices_for = g_newa(int, n_param_values);
212 for(i = 0; i < n_param_values; i++)
213 array_len_indices_for[i] = -1;
214 type_info_for = g_newa(GITypeInfo *, n_param_values);
215 memset(type_info_for, 0, sizeof (gpointer) * n_param_values);
216
217 signal_info = get_signal_info_if_available(&signal_query);
218 if (signal_info) {
219 /* Start at argument 1, skip the instance parameter */
220 for (i = 1; i < n_param_values; ++i) {
221 GIArgInfo *arg_info;
222 int array_len_pos;
223
224 arg_info = g_callable_info_get_arg(signal_info, i - 1);
225 type_info_for[i] = g_arg_info_get_type(arg_info);
226
227 array_len_pos = g_type_info_get_array_length(type_info_for[i]);
228 if (array_len_pos != -1) {
229 skip[array_len_pos + 1] = true;
230 array_len_indices_for[i] = array_len_pos + 1;
231 }
232
233 g_base_info_unref((GIBaseInfo *)arg_info);
234 }
235
236 g_base_info_unref((GIBaseInfo *)signal_info);
237 }
238
239 JS::RootedValueVector argv(context);
240 /* May end up being less */
241 if (!argv.reserve(n_param_values))
242 g_error("Unable to reserve space");
243 JS::RootedValue argv_to_append(context);
244 for (i = 0; i < n_param_values; ++i) {
245 const GValue *gval = ¶m_values[i];
246 bool no_copy;
247 int array_len_index;
248 bool res;
249
250 if (skip[i])
251 continue;
252
253 no_copy = false;
254
255 if (i >= 1 && signal_query.signal_id) {
256 no_copy = (signal_query.param_types[i - 1] & G_SIGNAL_TYPE_STATIC_SCOPE) != 0;
257 }
258
259 array_len_index = array_len_indices_for[i];
260 if (array_len_index != -1) {
261 const GValue *array_len_gval = ¶m_values[array_len_index];
262 res = gjs_value_from_array_and_length_values(context,
263 &argv_to_append,
264 type_info_for[i],
265 gval, array_len_gval,
266 no_copy, &signal_query,
267 array_len_index);
268 } else {
269 res = gjs_value_from_g_value_internal(context,
270 &argv_to_append,
271 gval, no_copy, &signal_query,
272 i);
273 }
274
275 if (!res) {
276 gjs_debug(GJS_DEBUG_GCLOSURE,
277 "Unable to convert arg %d in order to invoke closure",
278 i);
279 gjs_log_exception(context);
280 return;
281 }
282
283 argv.infallibleAppend(argv_to_append);
284 }
285
286 for (i = 1; i < n_param_values; i++)
287 if (type_info_for[i])
288 g_base_info_unref((GIBaseInfo *)type_info_for[i]);
289
290 JS::RootedValue rval(context);
291 mozilla::Unused << gjs_closure_invoke(closure, nullptr, argv, &rval, false);
292 // Any exception now pending, is handled when returning control to JS
293
294 if (return_value != NULL) {
295 if (rval.isUndefined()) {
296 /* something went wrong invoking, error should be set already */
297 return;
298 }
299
300 if (!gjs_value_to_g_value(context, rval, return_value)) {
301 gjs_debug(GJS_DEBUG_GCLOSURE,
302 "Unable to convert return value when invoking closure");
303 gjs_log_exception(context);
304 return;
305 }
306 }
307 }
308
gjs_closure_new_for_signal(JSContext * context,JSFunction * callable,const char * description,guint signal_id)309 GClosure* gjs_closure_new_for_signal(JSContext* context, JSFunction* callable,
310 const char* description, guint signal_id) {
311 GClosure *closure;
312
313 closure = gjs_closure_new(context, callable, description, false);
314
315 g_closure_set_meta_marshal(closure, GUINT_TO_POINTER(signal_id), closure_marshal);
316
317 return closure;
318 }
319
gjs_closure_new_marshaled(JSContext * context,JSFunction * callable,const char * description)320 GClosure* gjs_closure_new_marshaled(JSContext* context, JSFunction* callable,
321 const char* description) {
322 GClosure *closure;
323
324 closure = gjs_closure_new(context, callable, description, true);
325
326 g_closure_set_marshal(closure, closure_marshal);
327
328 return closure;
329 }
330
331 GJS_JSAPI_RETURN_CONVENTION
gjs_value_guess_g_type(JSContext * context,JS::Value value,GType * gtype_out)332 static bool gjs_value_guess_g_type(JSContext* context, JS::Value value,
333 GType* gtype_out) {
334 g_assert(gtype_out && "Invalid return location");
335
336 if (value.isNull()) {
337 *gtype_out = G_TYPE_POINTER;
338 return true;
339 }
340 if (value.isString()) {
341 *gtype_out = G_TYPE_STRING;
342 return true;
343 }
344 if (value.isInt32()) {
345 *gtype_out = G_TYPE_INT;
346 return true;
347 }
348 if (value.isDouble()) {
349 *gtype_out = G_TYPE_DOUBLE;
350 return true;
351 }
352 if (value.isBoolean()) {
353 *gtype_out = G_TYPE_BOOLEAN;
354 return true;
355 }
356 if (value.isObject()) {
357 JS::RootedObject obj(context, &value.toObject());
358 return gjs_gtype_get_actual_gtype(context, obj, gtype_out);
359 }
360
361 *gtype_out = G_TYPE_INVALID;
362 return true;
363 }
364
365 static bool
throw_expect_type(JSContext * cx,JS::HandleValue value,const char * expected_type,GType gtype=0)366 throw_expect_type(JSContext *cx,
367 JS::HandleValue value,
368 const char *expected_type,
369 GType gtype = 0)
370 {
371 gjs_throw(cx, "Wrong type %s; %s%s%s expected",
372 JS::InformalValueTypeName(value), expected_type,
373 gtype ? " " : "",
374 gtype ? g_type_name(gtype) : "");
375 return false; /* for convenience */
376 }
377
378 GJS_JSAPI_RETURN_CONVENTION
379 static bool
gjs_value_to_g_value_internal(JSContext * context,JS::HandleValue value,GValue * gvalue,bool no_copy)380 gjs_value_to_g_value_internal(JSContext *context,
381 JS::HandleValue value,
382 GValue *gvalue,
383 bool no_copy)
384 {
385 GType gtype;
386
387 gtype = G_VALUE_TYPE(gvalue);
388
389 if (gtype == 0) {
390 if (!gjs_value_guess_g_type(context, value, >ype))
391 return false;
392
393 if (gtype == G_TYPE_INVALID) {
394 gjs_throw(context, "Could not guess unspecified GValue type");
395 return false;
396 }
397
398 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
399 "Guessed GValue type %s from JS Value",
400 g_type_name(gtype));
401
402 g_value_init(gvalue, gtype);
403 }
404
405 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
406 "Converting JS::Value to gtype %s",
407 g_type_name(gtype));
408
409
410 if (gtype == G_TYPE_STRING) {
411 /* Don't use ValueToString since we don't want to just toString()
412 * everything automatically
413 */
414 if (value.isNull()) {
415 g_value_set_string(gvalue, NULL);
416 } else if (value.isString()) {
417 JS::RootedString str(context, value.toString());
418 JS::UniqueChars utf8_string(JS_EncodeStringToUTF8(context, str));
419 if (!utf8_string)
420 return false;
421
422 g_value_set_string(gvalue, utf8_string.get());
423 } else {
424 return throw_expect_type(context, value, "string");
425 }
426 } else if (gtype == G_TYPE_CHAR) {
427 gint32 i;
428 if (JS::ToInt32(context, value, &i) && i >= SCHAR_MIN && i <= SCHAR_MAX) {
429 g_value_set_schar(gvalue, (signed char)i);
430 } else {
431 return throw_expect_type(context, value, "char");
432 }
433 } else if (gtype == G_TYPE_UCHAR) {
434 guint16 i;
435 if (JS::ToUint16(context, value, &i) && i <= UCHAR_MAX) {
436 g_value_set_uchar(gvalue, (unsigned char)i);
437 } else {
438 return throw_expect_type(context, value, "unsigned char");
439 }
440 } else if (gtype == G_TYPE_INT) {
441 gint32 i;
442 if (JS::ToInt32(context, value, &i)) {
443 g_value_set_int(gvalue, i);
444 } else {
445 return throw_expect_type(context, value, "integer");
446 }
447 } else if (gtype == G_TYPE_DOUBLE) {
448 gdouble d;
449 if (JS::ToNumber(context, value, &d)) {
450 g_value_set_double(gvalue, d);
451 } else {
452 return throw_expect_type(context, value, "double");
453 }
454 } else if (gtype == G_TYPE_FLOAT) {
455 gdouble d;
456 if (JS::ToNumber(context, value, &d)) {
457 g_value_set_float(gvalue, d);
458 } else {
459 return throw_expect_type(context, value, "float");
460 }
461 } else if (gtype == G_TYPE_UINT) {
462 guint32 i;
463 if (JS::ToUint32(context, value, &i)) {
464 g_value_set_uint(gvalue, i);
465 } else {
466 return throw_expect_type(context, value, "unsigned integer");
467 }
468 } else if (gtype == G_TYPE_BOOLEAN) {
469 /* JS::ToBoolean() can't fail */
470 g_value_set_boolean(gvalue, JS::ToBoolean(value));
471 } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
472 GObject *gobj;
473
474 gobj = NULL;
475 if (value.isNull()) {
476 /* nothing to do */
477 } else if (value.isObject()) {
478 JS::RootedObject obj(context, &value.toObject());
479 if (!ObjectBase::typecheck(context, obj, nullptr, gtype) ||
480 !ObjectBase::to_c_ptr(context, obj, &gobj))
481 return false;
482 if (!gobj)
483 return true; // treat disposed object as if value.isNull()
484 } else {
485 return throw_expect_type(context, value, "object", gtype);
486 }
487
488 g_value_set_object(gvalue, gobj);
489 } else if (gtype == G_TYPE_STRV) {
490 if (value.isNull()) {
491 /* do nothing */
492 } else if (value.isObject()) {
493 bool found_length;
494
495 const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
496 JS::RootedObject array_obj(context, &value.toObject());
497 if (JS_HasPropertyById(context, array_obj, atoms.length(),
498 &found_length) &&
499 found_length) {
500 guint32 length;
501
502 if (!gjs_object_require_converted_property(
503 context, array_obj, nullptr, atoms.length(), &length)) {
504 JS_ClearPendingException(context);
505 return throw_expect_type(context, value, "strv");
506 } else {
507 void *result;
508 char **strv;
509
510 if (!gjs_array_to_strv (context,
511 value,
512 length, &result))
513 return false;
514 /* cast to strv in a separate step to avoid type-punning */
515 strv = (char**) result;
516 g_value_take_boxed (gvalue, strv);
517 }
518 } else {
519 return throw_expect_type(context, value, "strv");
520 }
521 } else {
522 return throw_expect_type(context, value, "strv");
523 }
524 } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
525 void *gboxed;
526
527 gboxed = NULL;
528 if (value.isNull())
529 return true;
530
531 /* special case GValue */
532 if (g_type_is_a(gtype, G_TYPE_VALUE)) {
533 GValue nested_gvalue = G_VALUE_INIT;
534
535 /* explicitly handle values that are already GValues
536 to avoid infinite recursion */
537 if (value.isObject()) {
538 JS::RootedObject obj(context, &value.toObject());
539 GType guessed_gtype;
540
541 if (!gjs_value_guess_g_type(context, value, &guessed_gtype))
542 return false;
543
544 if (guessed_gtype == G_TYPE_VALUE) {
545 gboxed = BoxedBase::to_c_ptr<GValue>(context, obj);
546 g_value_set_boxed(gvalue, gboxed);
547 return true;
548 }
549 }
550
551 if (!gjs_value_to_g_value(context, value, &nested_gvalue))
552 return false;
553
554 g_value_set_boxed(gvalue, &nested_gvalue);
555 g_value_unset(&nested_gvalue);
556 return true;
557 }
558
559 if (value.isObject()) {
560 JS::RootedObject obj(context, &value.toObject());
561
562 if (g_type_is_a(gtype, G_TYPE_ERROR)) {
563 /* special case GError */
564 gboxed = ErrorBase::to_c_ptr(context, obj);
565 if (!gboxed)
566 return false;
567 } else {
568 GIBaseInfo *registered = g_irepository_find_by_gtype (NULL, gtype);
569
570 /* We don't necessarily have the typelib loaded when
571 we first see the structure... */
572 if (registered) {
573 GIInfoType info_type = g_base_info_get_type (registered);
574
575 if (info_type == GI_INFO_TYPE_STRUCT &&
576 g_struct_info_is_foreign ((GIStructInfo*)registered)) {
577 GArgument arg;
578
579 if (!gjs_struct_foreign_convert_to_g_argument (context, value,
580 registered,
581 NULL,
582 GJS_ARGUMENT_ARGUMENT,
583 GI_TRANSFER_NOTHING,
584 true, &arg))
585 return false;
586
587 gboxed = gjs_arg_get<void*>(&arg);
588 }
589 }
590
591 /* First try a union, if that fails,
592 assume a boxed struct. Distinguishing
593 which one is expected would require checking
594 the associated GIBaseInfo, which is not necessary
595 possible, if e.g. we see the GType without
596 loading the typelib.
597 */
598 if (!gboxed) {
599 if (UnionBase::typecheck(context, obj, nullptr, gtype,
600 GjsTypecheckNoThrow())) {
601 gboxed = UnionBase::to_c_ptr(context, obj);
602 } else {
603 if (!BoxedBase::typecheck(context, obj, nullptr, gtype))
604 return false;
605
606 gboxed = BoxedBase::to_c_ptr(context, obj);
607 }
608 if (!gboxed)
609 return false;
610 }
611 }
612 } else {
613 return throw_expect_type(context, value, "boxed type", gtype);
614 }
615
616 if (no_copy)
617 g_value_set_static_boxed(gvalue, gboxed);
618 else
619 g_value_set_boxed(gvalue, gboxed);
620 } else if (g_type_is_a(gtype, G_TYPE_VARIANT)) {
621 GVariant *variant = NULL;
622
623 if (value.isNull()) {
624 /* nothing to do */
625 } else if (value.isObject()) {
626 JS::RootedObject obj(context, &value.toObject());
627
628 if (!BoxedBase::typecheck(context, obj, nullptr, G_TYPE_VARIANT))
629 return false;
630
631 variant = BoxedBase::to_c_ptr<GVariant>(context, obj);
632 if (!variant)
633 return false;
634 } else {
635 return throw_expect_type(context, value, "boxed type", gtype);
636 }
637
638 g_value_set_variant (gvalue, variant);
639 } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
640 int64_t value_int64;
641
642 if (JS::ToInt64(context, value, &value_int64)) {
643 GEnumValue *v;
644 GjsAutoTypeClass<GEnumClass> enum_class(gtype);
645
646 /* See arg.c:_gjs_enum_to_int() */
647 v = g_enum_get_value(enum_class, (int)value_int64);
648 if (v == NULL) {
649 gjs_throw(context,
650 "%d is not a valid value for enumeration %s",
651 value.toInt32(), g_type_name(gtype));
652 return false;
653 }
654
655 g_value_set_enum(gvalue, v->value);
656 } else {
657 return throw_expect_type(context, value, "enum", gtype);
658 }
659 } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
660 int64_t value_int64;
661
662 if (JS::ToInt64(context, value, &value_int64)) {
663 if (!_gjs_flags_value_is_valid(context, gtype, value_int64))
664 return false;
665
666 /* See arg.c:_gjs_enum_to_int() */
667 g_value_set_flags(gvalue, (int)value_int64);
668 } else {
669 return throw_expect_type(context, value, "flags", gtype);
670 }
671 } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
672 void *gparam;
673
674 gparam = NULL;
675 if (value.isNull()) {
676 /* nothing to do */
677 } else if (value.isObject()) {
678 JS::RootedObject obj(context, &value.toObject());
679
680 if (!gjs_typecheck_param(context, obj, gtype, true))
681 return false;
682
683 gparam = gjs_g_param_from_param(context, obj);
684 } else {
685 return throw_expect_type(context, value, "param type", gtype);
686 }
687
688 g_value_set_param(gvalue, (GParamSpec*) gparam);
689 } else if (g_type_is_a(gtype, G_TYPE_GTYPE)) {
690 GType type;
691
692 if (!value.isObject())
693 return throw_expect_type(context, value, "GType object");
694
695 JS::RootedObject obj(context, &value.toObject());
696 if (!gjs_gtype_get_actual_gtype(context, obj, &type))
697 return false;
698 g_value_set_gtype(gvalue, type);
699 } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
700 if (value.isNull()) {
701 /* Nothing to do */
702 } else {
703 gjs_throw(context,
704 "Cannot convert non-null JS value to G_POINTER");
705 return false;
706 }
707 } else if (value.isNumber() &&
708 g_value_type_transformable(G_TYPE_INT, gtype)) {
709 /* Only do this crazy gvalue transform stuff after we've
710 * exhausted everything else. Adding this for
711 * e.g. ClutterUnit.
712 */
713 gint32 i;
714 if (JS::ToInt32(context, value, &i)) {
715 GValue int_value = { 0, };
716 g_value_init(&int_value, G_TYPE_INT);
717 g_value_set_int(&int_value, i);
718 g_value_transform(&int_value, gvalue);
719 } else {
720 return throw_expect_type(context, value, "integer");
721 }
722 } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
723 // The gtype is none of the above, it should be derived from a custom
724 // fundamental type.
725 if (!value.isObject())
726 return throw_expect_type(context, value, "object", gtype);
727
728 JS::RootedObject fundamental_object(context, &value.toObject());
729 if (!FundamentalBase::to_gvalue(context, fundamental_object, gvalue))
730 return false;
731 } else {
732 gjs_debug(GJS_DEBUG_GCLOSURE, "JS::Value is number %d gtype fundamental %d transformable to int %d from int %d",
733 value.isNumber(),
734 G_TYPE_IS_FUNDAMENTAL(gtype),
735 g_value_type_transformable(gtype, G_TYPE_INT),
736 g_value_type_transformable(G_TYPE_INT, gtype));
737
738 gjs_throw(context,
739 "Don't know how to convert JavaScript object to GType %s",
740 g_type_name(gtype));
741 return false;
742 }
743
744 return true;
745 }
746
747 bool
gjs_value_to_g_value(JSContext * context,JS::HandleValue value,GValue * gvalue)748 gjs_value_to_g_value(JSContext *context,
749 JS::HandleValue value,
750 GValue *gvalue)
751 {
752 return gjs_value_to_g_value_internal(context, value, gvalue, false);
753 }
754
755 bool
gjs_value_to_g_value_no_copy(JSContext * context,JS::HandleValue value,GValue * gvalue)756 gjs_value_to_g_value_no_copy(JSContext *context,
757 JS::HandleValue value,
758 GValue *gvalue)
759 {
760 return gjs_value_to_g_value_internal(context, value, gvalue, true);
761 }
762
convert_int_to_enum(GType gtype,int v)763 [[nodiscard]] static JS::Value convert_int_to_enum(GType gtype, int v) {
764 double v_double;
765
766 if (v > 0 && v < G_MAXINT) {
767 /* Optimize the unambiguous case */
768 v_double = v;
769 } else {
770 /* Need to distinguish between negative integers and unsigned integers */
771 GjsAutoEnumInfo info = g_irepository_find_by_gtype(nullptr, gtype);
772 g_assert (info);
773
774 v_double = _gjs_enum_from_int(info, v);
775 }
776
777 return JS::NumberValue(v_double);
778 }
779
780 GJS_JSAPI_RETURN_CONVENTION
781 static bool
gjs_value_from_g_value_internal(JSContext * context,JS::MutableHandleValue value_p,const GValue * gvalue,bool no_copy,GSignalQuery * signal_query,int arg_n)782 gjs_value_from_g_value_internal(JSContext *context,
783 JS::MutableHandleValue value_p,
784 const GValue *gvalue,
785 bool no_copy,
786 GSignalQuery *signal_query,
787 int arg_n)
788 {
789 GType gtype;
790
791 gtype = G_VALUE_TYPE(gvalue);
792
793 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
794 "Converting gtype %s to JS::Value",
795 g_type_name(gtype));
796
797 if (gtype == G_TYPE_STRING) {
798 const char *v;
799 v = g_value_get_string(gvalue);
800 if (v == NULL) {
801 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
802 "Converting NULL string to JS::NullValue()");
803 value_p.setNull();
804 } else {
805 if (!gjs_string_from_utf8(context, v, value_p))
806 return false;
807 }
808 } else if (gtype == G_TYPE_CHAR) {
809 char v;
810 v = g_value_get_schar(gvalue);
811 value_p.setInt32(v);
812 } else if (gtype == G_TYPE_UCHAR) {
813 unsigned char v;
814 v = g_value_get_uchar(gvalue);
815 value_p.setInt32(v);
816 } else if (gtype == G_TYPE_INT) {
817 int v;
818 v = g_value_get_int(gvalue);
819 value_p.set(JS::NumberValue(v));
820 } else if (gtype == G_TYPE_UINT) {
821 guint v;
822 v = g_value_get_uint(gvalue);
823 value_p.setNumber(v);
824 } else if (gtype == G_TYPE_DOUBLE) {
825 double d;
826 d = g_value_get_double(gvalue);
827 value_p.setNumber(d);
828 } else if (gtype == G_TYPE_FLOAT) {
829 double d;
830 d = g_value_get_float(gvalue);
831 value_p.setNumber(d);
832 } else if (gtype == G_TYPE_BOOLEAN) {
833 bool v;
834 v = g_value_get_boolean(gvalue);
835 value_p.setBoolean(!!v);
836 } else if (g_type_is_a(gtype, G_TYPE_OBJECT) || g_type_is_a(gtype, G_TYPE_INTERFACE)) {
837 GObject *gobj;
838
839 gobj = (GObject*) g_value_get_object(gvalue);
840
841 if (gobj) {
842 JSObject* obj = ObjectInstance::wrapper_from_gobject(context, gobj);
843 if (!obj)
844 return false;
845 value_p.setObject(*obj);
846 } else {
847 value_p.setNull();
848 }
849 } else if (gtype == G_TYPE_STRV) {
850 if (!gjs_array_from_strv (context,
851 value_p,
852 (const char**) g_value_get_boxed (gvalue))) {
853 gjs_throw(context, "Failed to convert strv to array");
854 return false;
855 }
856 } else if (g_type_is_a(gtype, G_TYPE_HASH_TABLE) ||
857 g_type_is_a(gtype, G_TYPE_ARRAY) ||
858 g_type_is_a(gtype, G_TYPE_BYTE_ARRAY) ||
859 g_type_is_a(gtype, G_TYPE_PTR_ARRAY)) {
860 gjs_throw(context,
861 "Unable to introspect element-type of container in GValue");
862 return false;
863 } else if (g_type_is_a(gtype, G_TYPE_BOXED) ||
864 g_type_is_a(gtype, G_TYPE_VARIANT)) {
865 void *gboxed;
866 JSObject *obj;
867
868 if (g_type_is_a(gtype, G_TYPE_BOXED))
869 gboxed = g_value_get_boxed(gvalue);
870 else
871 gboxed = g_value_get_variant(gvalue);
872
873 if (!gboxed) {
874 gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
875 "Converting null boxed pointer to JS::Value");
876 value_p.setNull();
877 return true;
878 }
879
880 /* special case GError */
881 if (g_type_is_a(gtype, G_TYPE_ERROR)) {
882 obj = ErrorInstance::object_for_c_ptr(context,
883 static_cast<GError*>(gboxed));
884 if (!obj)
885 return false;
886 value_p.setObject(*obj);
887 return true;
888 }
889
890 /* special case GValue */
891 if (g_type_is_a(gtype, G_TYPE_VALUE)) {
892 return gjs_value_from_g_value(context, value_p,
893 static_cast<GValue *>(gboxed));
894 }
895
896 /* The only way to differentiate unions and structs is from
897 * their g-i info as both GBoxed */
898 GjsAutoBaseInfo info = g_irepository_find_by_gtype(nullptr, gtype);
899 if (!info) {
900 gjs_throw(context,
901 "No introspection information found for %s",
902 g_type_name(gtype));
903 return false;
904 }
905
906 if (info.type() == GI_INFO_TYPE_STRUCT &&
907 g_struct_info_is_foreign(info)) {
908 GIArgument arg;
909 gjs_arg_set(&arg, gboxed);
910 return gjs_struct_foreign_convert_from_g_argument(context, value_p,
911 info, &arg);
912 }
913
914 GIInfoType type = info.type();
915 if (type == GI_INFO_TYPE_BOXED || type == GI_INFO_TYPE_STRUCT) {
916 if (no_copy)
917 obj = BoxedInstance::new_for_c_struct(context, info, gboxed,
918 BoxedInstance::NoCopy());
919 else
920 obj = BoxedInstance::new_for_c_struct(context, info, gboxed);
921 } else if (type == GI_INFO_TYPE_UNION) {
922 obj = gjs_union_from_c_union(context, info, gboxed);
923 } else {
924 gjs_throw(context, "Unexpected introspection type %d for %s",
925 info.type(), g_type_name(gtype));
926 return false;
927 }
928
929 value_p.setObjectOrNull(obj);
930 } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
931 value_p.set(convert_int_to_enum(gtype, g_value_get_enum(gvalue)));
932 } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
933 GParamSpec *gparam;
934 JSObject *obj;
935
936 gparam = g_value_get_param(gvalue);
937
938 obj = gjs_param_from_g_param(context, gparam);
939 value_p.setObjectOrNull(obj);
940 } else if (signal_query && g_type_is_a(gtype, G_TYPE_POINTER)) {
941 bool res;
942 GArgument arg;
943 GIArgInfo *arg_info;
944 GISignalInfo *signal_info;
945 GITypeInfo type_info;
946
947 signal_info = get_signal_info_if_available(signal_query);
948 if (!signal_info) {
949 gjs_throw(context, "Unknown signal.");
950 return false;
951 }
952
953 arg_info = g_callable_info_get_arg(signal_info, arg_n - 1);
954 g_arg_info_load_type(arg_info, &type_info);
955
956 g_assert(((void) "Check gjs_value_from_array_and_length_values() before"
957 " calling gjs_value_from_g_value_internal()",
958 g_type_info_get_array_length(&type_info) == -1));
959
960 gjs_arg_set(&arg, g_value_get_pointer(gvalue));
961
962 res = gjs_value_from_g_argument(context, value_p, &type_info, &arg, true);
963
964 g_base_info_unref((GIBaseInfo*)arg_info);
965 g_base_info_unref((GIBaseInfo*)signal_info);
966 return res;
967 } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
968 gpointer pointer;
969
970 pointer = g_value_get_pointer(gvalue);
971
972 if (pointer == NULL) {
973 value_p.setNull();
974 } else {
975 gjs_throw(context,
976 "Can't convert non-null pointer to JS value");
977 return false;
978 }
979 } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) {
980 GValue double_value = { 0, };
981 double v;
982 g_value_init(&double_value, G_TYPE_DOUBLE);
983 g_value_transform(gvalue, &double_value);
984 v = g_value_get_double(&double_value);
985 value_p.setNumber(v);
986 } else if (g_value_type_transformable(gtype, G_TYPE_INT)) {
987 GValue int_value = { 0, };
988 int v;
989 g_value_init(&int_value, G_TYPE_INT);
990 g_value_transform(gvalue, &int_value);
991 v = g_value_get_int(&int_value);
992 value_p.set(JS::NumberValue(v));
993 } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
994 /* The gtype is none of the above, it should be a custom
995 fundamental type. */
996 JSObject* obj =
997 FundamentalInstance::object_for_gvalue(context, gvalue, gtype);
998 if (obj == NULL)
999 return false;
1000 else
1001 value_p.setObject(*obj);
1002 } else {
1003 gjs_throw(context,
1004 "Don't know how to convert GType %s to JavaScript object",
1005 g_type_name(gtype));
1006 return false;
1007 }
1008
1009 return true;
1010 }
1011
1012 bool
gjs_value_from_g_value(JSContext * context,JS::MutableHandleValue value_p,const GValue * gvalue)1013 gjs_value_from_g_value(JSContext *context,
1014 JS::MutableHandleValue value_p,
1015 const GValue *gvalue)
1016 {
1017 return gjs_value_from_g_value_internal(context, value_p, gvalue, false, NULL, 0);
1018 }
1019