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(¶m_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 = ¶m_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 = ¶m_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, >ype))
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