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(&param_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 = &param_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 = &param_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, &gtype))
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