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