Branch data Line data Source code
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 <limits.h> // for INT_MAX
8 : : #include <stdint.h>
9 : :
10 : : #include <sstream>
11 : : #include <string>
12 : :
13 : : #include <girepository/girepository.h>
14 : : #include <glib-object.h>
15 : : #include <glib.h>
16 : :
17 : : #include <js/Array.h>
18 : : #include <js/BigInt.h>
19 : : #include <js/CharacterEncoding.h>
20 : : #include <js/Conversions.h>
21 : : #include <js/Exception.h>
22 : : #include <js/GCVector.h> // for RootedVector
23 : : #include <js/HeapAPI.h> // for RuntimeHeapIsCollecting
24 : : #include <js/Realm.h>
25 : : #include <js/RootingAPI.h>
26 : : #include <js/TypeDecls.h>
27 : : #include <js/Utility.h> // for UniqueChars
28 : : #include <js/Value.h>
29 : : #include <js/ValueArray.h>
30 : : #include <js/experimental/TypedData.h>
31 : : #include <jsapi.h> // for InformalValueTypeName, JS_Get...
32 : : #include <mozilla/Maybe.h>
33 : :
34 : : #include "gi/arg-inl.h"
35 : : #include "gi/arg.h"
36 : : #include "gi/boxed.h"
37 : : #include "gi/closure.h"
38 : : #include "gi/foreign.h"
39 : : #include "gi/fundamental.h"
40 : : #include "gi/gerror.h"
41 : : #include "gi/gtype.h"
42 : : #include "gi/info.h"
43 : : #include "gi/js-value-inl.h"
44 : : #include "gi/object.h"
45 : : #include "gi/param.h"
46 : : #include "gi/struct.h"
47 : : #include "gi/union.h"
48 : : #include "gi/value.h"
49 : : #include "gi/wrapperutils.h"
50 : : #include "gjs/auto.h"
51 : : #include "gjs/byteArray.h"
52 : : #include "gjs/context-private.h"
53 : : #include "gjs/jsapi-util.h"
54 : : #include "gjs/macros.h"
55 : : #include "gjs/objectbox.h"
56 : : #include "util/log.h"
57 : :
58 : : using mozilla::Maybe, mozilla::Nothing, mozilla::Some;
59 : :
60 : : GJS_JSAPI_RETURN_CONVENTION
61 : : static bool gjs_value_from_g_value_internal(
62 : : JSContext*, JS::MutableHandleValue, const GValue*, bool = false,
63 : : bool = false, Maybe<std::pair<const GI::ArgInfo, const GI::TypeInfo>> = {});
64 : :
65 : : GJS_JSAPI_RETURN_CONVENTION
66 : 71 : static bool gjs_arg_set_from_gvalue(JSContext* cx, GIArgument* arg,
67 : : const GValue* value) {
68 [ - - - - : 71 : switch (G_VALUE_TYPE(value)) {
- - - - -
- - - - -
+ ]
69 : 0 : case G_TYPE_CHAR:
70 : 0 : gjs_arg_set(arg, g_value_get_schar(value));
71 : 0 : return true;
72 : 0 : case G_TYPE_UCHAR:
73 : 0 : gjs_arg_set(arg, g_value_get_uchar(value));
74 : 0 : return true;
75 : 0 : case G_TYPE_BOOLEAN:
76 : 0 : gjs_arg_set(arg, g_value_get_boolean(value));
77 : 0 : return true;
78 : 0 : case G_TYPE_INT:
79 : 0 : gjs_arg_set(arg, g_value_get_int(value));
80 : 0 : return true;
81 : 0 : case G_TYPE_UINT:
82 : 0 : gjs_arg_set(arg, g_value_get_uint(value));
83 : 0 : return true;
84 : 0 : case G_TYPE_LONG:
85 : 0 : gjs_arg_set<Gjs::Tag::Long>(arg, g_value_get_long(value));
86 : 0 : return true;
87 : 0 : case G_TYPE_ULONG:
88 : 0 : gjs_arg_set<Gjs::Tag::UnsignedLong>(arg, g_value_get_ulong(value));
89 : 0 : return true;
90 : 0 : case G_TYPE_INT64:
91 : 0 : gjs_arg_set(arg, int64_t{g_value_get_int64(value)});
92 : 0 : return true;
93 : 0 : case G_TYPE_UINT64:
94 : 0 : gjs_arg_set(arg, uint64_t{g_value_get_uint64(value)});
95 : 0 : return true;
96 : 0 : case G_TYPE_FLOAT:
97 : 0 : gjs_arg_set(arg, g_value_get_float(value));
98 : 0 : return true;
99 : 0 : case G_TYPE_DOUBLE:
100 : 0 : gjs_arg_set(arg, g_value_get_double(value));
101 : 0 : return true;
102 : 0 : case G_TYPE_STRING:
103 : 0 : gjs_arg_set(arg, g_value_get_string(value));
104 : 0 : return true;
105 : 0 : case G_TYPE_POINTER:
106 : 0 : gjs_arg_set(arg, g_value_get_pointer(value));
107 : 0 : return true;
108 : 0 : case G_TYPE_VARIANT:
109 : 0 : gjs_arg_set(arg, g_value_get_variant(value));
110 : 0 : return true;
111 : 71 : default: {
112 [ + - ]: 71 : if (g_value_fits_pointer(value)) {
113 : 71 : gjs_arg_set(arg, g_value_peek_pointer(value));
114 : 71 : return true;
115 : : }
116 : :
117 : 0 : GType gtype = G_VALUE_TYPE(value);
118 : :
119 [ # # # # : 0 : if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
# # ]
120 : 0 : gjs_arg_set<Gjs::Tag::UnsignedEnum>(arg,
121 : : g_value_get_flags(value));
122 : 0 : return true;
123 : : }
124 : :
125 [ # # # # : 0 : if (g_type_is_a(gtype, G_TYPE_ENUM)) {
# # ]
126 : 0 : gjs_arg_set<Gjs::Tag::Enum>(arg, g_value_get_enum(value));
127 : 0 : return true;
128 : : }
129 : :
130 [ # # # # : 0 : if (g_type_is_a(gtype, G_TYPE_GTYPE)) {
# # ]
131 : 0 : gjs_arg_set<Gjs::Tag::GType>(arg, g_value_get_gtype(value));
132 : 0 : return true;
133 : : }
134 : :
135 [ # # # # : 0 : if (g_type_is_a(gtype, G_TYPE_PARAM)) {
# # ]
136 : 0 : gjs_arg_set(arg, g_value_get_param(value));
137 : 0 : return true;
138 : : }
139 : : }
140 : : }
141 : :
142 : 0 : gjs_throw(cx, "No known GIArgument conversion for %s",
143 : : G_VALUE_TYPE_NAME(value));
144 : 0 : return false;
145 : : }
146 : :
147 : : GJS_JSAPI_RETURN_CONVENTION
148 : 71 : static bool maybe_release_signal_value(JSContext* cx,
149 : : const GI::ArgInfo arg_info,
150 : : const GI::TypeInfo type_info,
151 : : const GValue* gvalue,
152 : : GITransfer transfer) {
153 [ - + ]: 71 : if (transfer == GI_TRANSFER_NOTHING)
154 : 0 : return true;
155 : :
156 : : GIArgument arg;
157 [ - + ]: 71 : if (!gjs_arg_set_from_gvalue(cx, &arg, gvalue))
158 : 0 : return false;
159 : :
160 [ - + ]: 71 : if (!gjs_gi_argument_release(cx, transfer, type_info, &arg,
161 : : GjsArgumentFlags::ARG_OUT)) {
162 : 0 : gjs_throw(cx, "Cannot release argument %s value, we're gonna leak!",
163 : : arg_info.name());
164 : 0 : return false;
165 : : }
166 : :
167 : 71 : return true;
168 : : }
169 : :
170 : : /*
171 : : * Gets signal introspection info about closure, or Nothing if not found.
172 : : * Currently only works for signals on introspected GObjects, not signals on
173 : : * GJS-defined GObjects nor standalone closures. The return value must be
174 : : * unreffed.
175 : : */
176 : : [[nodiscard]]
177 : 10109 : static Maybe<GI::AutoSignalInfo> get_signal_info_if_available(
178 : : const GI::Repository& repo, GSignalQuery* signal_query) {
179 [ + + ]: 10109 : if (!signal_query->itype)
180 : 9720 : return {};
181 : :
182 : : Maybe<GI::AutoRegisteredTypeInfo> info{
183 : 389 : repo.find_by_gtype(signal_query->itype)};
184 [ + + ]: 389 : if (!info)
185 : 67 : return {};
186 : :
187 [ + + ]: 322 : if (auto object_info = info->as<GI::InfoTag::OBJECT>())
188 : 320 : return object_info->signal(signal_query->signal_name);
189 [ + - ]: 2 : else if (auto interface_info = info->as<GI::InfoTag::INTERFACE>())
190 [ - + - + ]: 324 : return interface_info->signal(signal_query->signal_name);
191 : :
192 : 0 : return {};
193 : 389 : }
194 : :
195 : : /*
196 : : * Fill in value_p with a JS array, converted from a C array stored as a pointer
197 : : * in array_value, with its length stored in array_length_value.
198 : : */
199 : : GJS_JSAPI_RETURN_CONVENTION
200 : 1 : static bool gjs_value_from_array_and_length_values(
201 : : JSContext* cx, JS::MutableHandleValue value_p,
202 : : const GI::TypeInfo array_type_info, const GValue* array_value,
203 : : Maybe<std::pair<const GI::ArgInfo, const GI::TypeInfo>> array_length_info,
204 : : const GValue* array_length_value, bool no_copy,
205 : : bool is_introspected_signal) {
206 : 1 : JS::RootedValue array_length{cx};
207 : :
208 : 1 : g_assert(G_VALUE_HOLDS_POINTER(array_value));
209 : 1 : g_assert(G_VALUE_HOLDS_INT(array_length_value));
210 : :
211 [ - + ]: 1 : if (!gjs_value_from_g_value_internal(cx, &array_length, array_length_value,
212 : : no_copy, is_introspected_signal,
213 : : array_length_info))
214 : 0 : return false;
215 : :
216 : : GIArgument array_arg;
217 : 1 : gjs_arg_set(&array_arg, Gjs::gvalue_get<void*>(array_value));
218 : :
219 [ - + ]: 3 : return gjs_value_from_explicit_array(
220 : 1 : cx, value_p, array_type_info, &array_arg, array_length.toInt32(),
221 : 1 : no_copy ? GI_TRANSFER_NOTHING : GI_TRANSFER_EVERYTHING);
222 : 1 : }
223 : :
224 : : // FIXME(3v1n0): Move into closure.cpp one day...
225 : 10110 : void Gjs::Closure::marshal(GValue* return_value, unsigned n_param_values,
226 : : const GValue* param_values, void* invocation_hint,
227 : : void* marshal_data) {
228 : : unsigned i;
229 : 10110 : GSignalQuery signal_query = { 0, };
230 : :
231 : : gjs_debug_marshal(GJS_DEBUG_GCLOSURE, "Marshal closure %p", this);
232 : :
233 [ - + ]: 10110 : if (!is_valid()) {
234 : : // We were destroyed; become a no-op
235 : 9 : return;
236 : : }
237 : :
238 : 10110 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(m_cx);
239 [ + + - + : 10110 : if (G_UNLIKELY(!gjs->is_owner_thread()) || JS::RuntimeHeapIsCollecting()) {
+ + ]
240 : 1 : auto* hint = static_cast<GSignalInvocationHint*>(invocation_hint);
241 : 1 : std::ostringstream message;
242 : :
243 [ + - ]: 1 : if (!gjs->is_owner_thread()) {
244 : : message << "Attempting to call back into JSAPI on a different "
245 : : "thread. This is most likely caused by an API not "
246 : : "intended to be used in JS. Because it would crash the "
247 : 1 : "application, it has been blocked.";
248 : : } else {
249 : : message
250 : : << "Attempting to call back into JSAPI during the sweeping "
251 : : "phase of GC. This is most likely caused by not destroying "
252 : : "a Clutter actor or Gtk+ widget with ::destroy signals "
253 : : "connected, but can also be caused by using the destroy(), "
254 : : "dispose(), or remove() vfuncs. Because it would crash the "
255 : : "application, it has been blocked and the JS callback not "
256 : 0 : "invoked.";
257 : 0 : message << "\n" << gjs_dumpstack_string();
258 : : }
259 [ + - ]: 1 : if (hint) {
260 : 1 : g_signal_query(hint->signal_id, &signal_query);
261 : :
262 : 1 : void* instance = g_value_peek_pointer(¶m_values[0]);
263 : 1 : message << "\nThe offending signal was " << signal_query.signal_name
264 : 1 : << " on " << g_type_name(G_TYPE_FROM_INSTANCE(instance))
265 : 1 : << " " << instance << ".";
266 : : }
267 : 1 : g_critical("%s", message.str().c_str());
268 : 1 : return;
269 : 1 : }
270 : :
271 : 10109 : JSAutoRealm ar{m_cx, callable()};
272 : :
273 [ + + ]: 10109 : if (marshal_data) {
274 : : // we are used for a signal handler
275 : 389 : unsigned signal_id = GPOINTER_TO_UINT(marshal_data);
276 : :
277 : 389 : g_signal_query(signal_id, &signal_query);
278 : :
279 [ - + ]: 389 : if (!signal_query.signal_id) {
280 : 0 : gjs_debug(GJS_DEBUG_GCLOSURE,
281 : : "Signal handler being called on invalid signal");
282 : 0 : return;
283 : : }
284 : :
285 [ - + ]: 389 : if (signal_query.n_params + 1 != n_param_values) {
286 : 0 : gjs_debug(
287 : : GJS_DEBUG_GCLOSURE,
288 : : "Signal handler being called with wrong number of parameters");
289 : 0 : return;
290 : : }
291 : : }
292 : :
293 : : /* Check if any parameters, such as array lengths, need to be eliminated
294 : : * before we invoke the closure.
295 : : */
296 : : struct ArgumentDetails {
297 : : int array_len_index_for = -1;
298 : : // FIXME don't use StackInfo here?
299 : : Maybe<std::pair<GI::StackArgInfo, GI::StackTypeInfo>> info;
300 : : bool skip = false;
301 : :
302 : : // Convenience methods, unsafe if there is no introspection info
303 : 1061 : GI::StackArgInfo& arg_info() { return info->first; }
304 : 930 : GI::StackTypeInfo& type_info() { return info->second; }
305 : : };
306 : 10109 : std::vector<ArgumentDetails> args_details(n_param_values);
307 : 10109 : bool needs_cleanup = false;
308 : :
309 : 10109 : GI::Repository repo;
310 : : Maybe<GI::AutoSignalInfo> signal_info{
311 : 10109 : get_signal_info_if_available(repo, &signal_query)};
312 [ + + ]: 10109 : if (signal_info) {
313 : : // Start at argument 1, skip the instance parameter
314 [ + + ]: 751 : for (i = 1; i < n_param_values; ++i) {
315 : 429 : ArgumentDetails& arg_details = args_details[i];
316 : 429 : arg_details.info.emplace();
317 : 429 : signal_info->load_arg(i - 1, &arg_details.arg_info());
318 : 429 : arg_details.info->first.load_type(&arg_details.type_info());
319 : :
320 : : Maybe<unsigned> array_len_pos =
321 : 429 : arg_details.type_info().array_length_index();
322 [ + + ]: 429 : if (array_len_pos) {
323 : 1 : args_details[*array_len_pos + 1].skip = true;
324 : 1 : arg_details.array_len_index_for = *array_len_pos + 1;
325 : : }
326 : :
327 [ + + + + : 429 : if (!needs_cleanup && arg_details.arg_info().ownership_transfer() !=
+ + ]
328 : : GI_TRANSFER_NOTHING)
329 : 71 : needs_cleanup = true;
330 : : }
331 : : }
332 : :
333 : 10109 : JS::RootedValueVector argv{m_cx};
334 : : // May end up being less
335 [ - + ]: 10109 : if (!argv.reserve(n_param_values))
336 : 0 : g_error("Unable to reserve space");
337 : 10109 : JS::RootedValue argv_to_append{m_cx};
338 : 10109 : bool is_introspected_signal = !!signal_info;
339 [ + + ]: 11046 : for (i = 0; i < n_param_values; ++i) {
340 : 937 : const GValue* gval = ¶m_values[i];
341 : 937 : ArgumentDetails& arg_details = args_details[i];
342 : : bool no_copy;
343 : : bool res;
344 : :
345 [ + + ]: 937 : if (arg_details.skip)
346 : 1 : continue;
347 : :
348 : 936 : no_copy = false;
349 : :
350 [ + + + + ]: 936 : if (i >= 1 && signal_query.signal_id) {
351 : 477 : no_copy = (signal_query.param_types[i - 1] &
352 : : G_SIGNAL_TYPE_STATIC_SCOPE) != 0;
353 : : }
354 : :
355 [ + + ]: 936 : if (arg_details.array_len_index_for != -1) {
356 : 1 : const GValue* array_len_gval =
357 : 1 : ¶m_values[arg_details.array_len_index_for];
358 : : ArgumentDetails& array_len_details =
359 : 1 : args_details[arg_details.array_len_index_for];
360 : 1 : res = gjs_value_from_array_and_length_values(
361 : : // FIXME: what if no type_info
362 : 1 : m_cx, &argv_to_append, arg_details.type_info(), gval,
363 : 2 : array_len_details.info, array_len_gval, no_copy,
364 : : is_introspected_signal);
365 : : } else {
366 : 935 : res = gjs_value_from_g_value_internal(
367 : 935 : m_cx, &argv_to_append, gval, no_copy, is_introspected_signal,
368 : 1870 : arg_details.info);
369 : : }
370 : :
371 [ - + ]: 936 : if (!res) {
372 : 0 : gjs_debug(GJS_DEBUG_GCLOSURE,
373 : : "Unable to convert arg %d in order to invoke closure",
374 : : i);
375 : 0 : gjs_log_exception(m_cx);
376 : 0 : return;
377 : : }
378 : :
379 : 936 : argv.infallibleAppend(argv_to_append);
380 : : }
381 : :
382 : 10109 : JS::RootedValue rval{m_cx};
383 : :
384 [ + + ]: 10109 : if (!invoke(nullptr, argv, &rval)) {
385 [ - + ]: 1 : if (JS_IsExceptionPending(m_cx)) {
386 : 0 : gjs_log_exception_uncaught(m_cx);
387 : : } else {
388 : : // "Uncatchable" exception thrown, we have to exit. This
389 : : // matches the closure exit handling in function.cpp
390 : : uint8_t code;
391 [ + - ]: 1 : if (gjs->should_exit(&code))
392 : 1 : gjs->exit_immediately(code);
393 : :
394 : : // Some other uncatchable exception, e.g. out of memory
395 : 0 : g_error("Call to %s terminated with uncatchable exception",
396 : : gjs_debug_callable(callable()).c_str());
397 : : }
398 : : }
399 : :
400 [ + + ]: 10108 : if (needs_cleanup) {
401 [ + + ]: 335 : for (i = 0; i < n_param_values; ++i) {
402 : 264 : ArgumentDetails& arg_details = args_details[i];
403 [ + + ]: 264 : if (!arg_details.info)
404 : 71 : continue;
405 : :
406 : 193 : GITransfer transfer = arg_details.arg_info().ownership_transfer();
407 [ + + ]: 193 : if (transfer == GI_TRANSFER_NOTHING)
408 : 122 : continue;
409 : :
410 : 71 : if (!maybe_release_signal_value(m_cx, arg_details.arg_info(),
411 [ - + ]: 142 : arg_details.type_info(),
412 : 71 : ¶m_values[i], transfer)) {
413 : 0 : gjs_log_exception(m_cx);
414 : 0 : return;
415 : : }
416 : : }
417 : : }
418 : :
419 : : // null return_value means the closure wasn't expected to return a value.
420 : : // Discard the JS function's return value in that case.
421 [ + + ]: 10108 : if (return_value != nullptr) {
422 [ + + ]: 9690 : if (rval.isUndefined()) {
423 : : // Either an exception was thrown and logged, or the JS function
424 : : // returned undefined. Leave the GValue uninitialized.
425 : : // FIXME: not sure what happens on the other side with an
426 : : // uninitialized GValue!
427 : 4 : return;
428 : : }
429 : :
430 [ + + ]: 9686 : if (!gjs_value_to_g_value(m_cx, rval, return_value)) {
431 : 4 : gjs_debug(GJS_DEBUG_GCLOSURE,
432 : : "Unable to convert return value when invoking closure");
433 : 4 : gjs_log_exception(m_cx);
434 : 4 : return;
435 : : }
436 : : }
437 [ + + + + : 10156 : }
+ + + + +
+ + + +
+ ]
438 : :
439 : : GJS_JSAPI_RETURN_CONVENTION
440 : 102 : static bool gjs_value_guess_g_type(JSContext* cx, JS::Value value,
441 : : GType* gtype_out) {
442 : 102 : g_assert(gtype_out && "Invalid return location");
443 : :
444 [ + + ]: 102 : if (value.isNull()) {
445 : 1 : *gtype_out = G_TYPE_POINTER;
446 : 1 : return true;
447 : : }
448 [ + + ]: 101 : if (value.isString()) {
449 : 10 : *gtype_out = G_TYPE_STRING;
450 : 10 : return true;
451 : : }
452 [ + + ]: 91 : if (value.isInt32()) {
453 : 27 : *gtype_out = G_TYPE_INT;
454 : 27 : return true;
455 : : }
456 [ + + ]: 64 : if (value.isDouble()) {
457 : 7 : *gtype_out = G_TYPE_DOUBLE;
458 : 7 : return true;
459 : : }
460 [ + + ]: 57 : if (value.isBoolean()) {
461 : 3 : *gtype_out = G_TYPE_BOOLEAN;
462 : 3 : return true;
463 : : }
464 [ + + ]: 54 : if (value.isBigInt()) {
465 : : // Assume that if the value is negative or within the int64_t limit,
466 : : // then we're handling a signed integer, otherwise unsigned.
467 : : int64_t ignored;
468 [ + + + + : 8 : if (JS::BigIntIsNegative(value.toBigInt()) ||
+ + ]
469 : 3 : JS::BigIntFits(value.toBigInt(), &ignored))
470 : 3 : *gtype_out = G_TYPE_INT64;
471 : : else
472 : 2 : *gtype_out = G_TYPE_UINT64;
473 : 5 : return true;
474 : : }
475 [ + - ]: 49 : if (value.isObject()) {
476 : 49 : JS::RootedObject obj{cx, &value.toObject()};
477 : 49 : return gjs_gtype_get_actual_gtype(cx, obj, gtype_out);
478 : 49 : }
479 : :
480 : 0 : *gtype_out = G_TYPE_INVALID;
481 : 0 : return true;
482 : : }
483 : :
484 : 20 : static bool throw_expect_type(JSContext* cx, JS::HandleValue value,
485 : : const char* expected_type, GType gtype = 0,
486 : : bool out_of_range = false) {
487 : 20 : JS::UniqueChars val_str;
488 [ - + - - ]: 20 : out_of_range = (out_of_range && value.isNumeric());
489 : :
490 [ - + ]: 20 : if (out_of_range) {
491 : 0 : JS::RootedString str(cx, JS::ToString(cx, value));
492 [ # # ]: 0 : if (str)
493 : 0 : val_str = JS_EncodeStringToUTF8(cx, str);
494 : 0 : }
495 : :
496 [ - + - + : 38 : gjs_throw(cx, "Wrong type %s; %s%s%s expected%s%s",
+ + + + ]
497 : : JS::InformalValueTypeName(value), expected_type, gtype ? " " : "",
498 : 18 : gtype ? g_type_name(gtype) : "",
499 : : out_of_range ? ". But it's out of range: " : "",
500 : 0 : out_of_range ? val_str.get() : "");
501 : 40 : return false; // for convenience
502 : 20 : }
503 : :
504 : : GJS_JSAPI_RETURN_CONVENTION
505 : 10734 : static bool gjs_value_to_g_value_internal(JSContext* cx, JS::HandleValue value,
506 : : GValue* gvalue, bool no_copy) {
507 : : GType gtype;
508 : 10734 : bool out_of_range = false;
509 : :
510 : 10734 : gtype = G_VALUE_TYPE(gvalue);
511 : :
512 [ + + ]: 10734 : if (value.isObject()) {
513 : 286 : JS::RootedObject obj{cx, &value.toObject()};
514 : : GType boxed_gtype;
515 : :
516 [ - + ]: 286 : if (!gjs_gtype_get_actual_gtype(cx, obj, &boxed_gtype))
517 : 0 : return false;
518 : :
519 : : // Don't unbox GValue if the GValue's gtype is GObject.Value
520 [ + + - + : 286 : if (g_type_is_a(boxed_gtype, G_TYPE_VALUE) && gtype != G_TYPE_VALUE) {
+ + + + ]
521 [ - + ]: 4 : if (no_copy) {
522 : 0 : gjs_throw(
523 : : cx, "Cannot convert GObject.Value object without copying.");
524 : 0 : return false;
525 : : }
526 : :
527 : 4 : GValue* source = StructBase::to_c_ptr<GValue>(cx, obj);
528 : : // Only initialize the value if it doesn't have a type
529 : : // and our source GValue has been initialized
530 : 4 : GType source_gtype = G_VALUE_TYPE(source);
531 [ + - ]: 4 : if (gtype == 0) {
532 [ + + ]: 4 : if (source_gtype == 0) {
533 : 1 : gjs_throw(cx,
534 : : "GObject.Value is not initialized with a type");
535 : 1 : return false;
536 : : }
537 : 3 : g_value_init(gvalue, source_gtype);
538 : : }
539 : :
540 : 3 : GType dest_gtype = G_VALUE_TYPE(gvalue);
541 [ - + ]: 3 : if (!g_value_type_compatible(source_gtype, dest_gtype)) {
542 : 0 : gjs_throw(cx, "GObject.Value expected GType %s, found %s",
543 : : g_type_name(dest_gtype), g_type_name(source_gtype));
544 : 0 : return false;
545 : : }
546 : :
547 : 3 : g_value_copy(source, gvalue);
548 : 3 : return true;
549 : : }
550 [ + + ]: 286 : }
551 : :
552 [ + + ]: 10730 : if (gtype == 0) {
553 [ - + ]: 84 : if (!gjs_value_guess_g_type(cx, value, >ype))
554 : 0 : return false;
555 : :
556 [ - + ]: 84 : if (gtype == G_TYPE_INVALID) {
557 : 0 : gjs_throw(cx, "Could not guess unspecified GValue type");
558 : 0 : return false;
559 : : }
560 : :
561 : : gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
562 : : "Guessed GValue type %s from JS Value",
563 : : g_type_name(gtype));
564 : :
565 : 84 : g_value_init(gvalue, gtype);
566 : : }
567 : :
568 : : gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
569 : : "Converting JS::Value to gtype %s",
570 : : g_type_name(gtype));
571 : :
572 : :
573 [ + + ]: 10730 : if (gtype == G_TYPE_STRING) {
574 : : /* Don't use ValueToString since we don't want to just toString()
575 : : * everything automatically
576 : : */
577 [ - + ]: 120 : if (value.isNull()) {
578 : 0 : Gjs::gvalue_set<char*>(gvalue, nullptr);
579 [ + + ]: 120 : } else if (value.isString()) {
580 : 118 : JS::RootedString str{cx, value.toString()};
581 : 118 : JS::UniqueChars utf8_string{JS_EncodeStringToUTF8(cx, str)};
582 [ - + ]: 118 : if (!utf8_string)
583 : 0 : return false;
584 : :
585 : 118 : Gjs::gvalue_set(gvalue, utf8_string.get());
586 [ + - + - ]: 118 : } else {
587 : 2 : return throw_expect_type(cx, value, "string");
588 : : }
589 [ + + ]: 10610 : } else if (gtype == G_TYPE_CHAR) {
590 : : int32_t i;
591 : 18 : if (Gjs::js_value_to_c_checked<signed char>(cx, value, &i,
592 [ + - + - ]: 36 : &out_of_range) &&
593 [ + - ]: 18 : !out_of_range) {
594 : 18 : Gjs::gvalue_set<signed char>(gvalue, i);
595 : : } else {
596 : 0 : return throw_expect_type(cx, value, "char", 0, out_of_range);
597 : : }
598 [ + + ]: 10592 : } else if (gtype == G_TYPE_UCHAR) {
599 : : uint32_t i;
600 : 18 : if (Gjs::js_value_to_c_checked<unsigned char>(cx, value, &i,
601 [ + - + - ]: 36 : &out_of_range) &&
602 [ + - ]: 18 : !out_of_range) {
603 : 18 : Gjs::gvalue_set<unsigned char>(gvalue, i);
604 : : } else {
605 : 0 : return throw_expect_type(cx, value, "unsigned char", 0,
606 : 0 : out_of_range);
607 : : }
608 [ + + ]: 10574 : } else if (gtype == G_TYPE_INT) {
609 : : int32_t i;
610 [ + - ]: 188 : if (Gjs::js_value_to_c<int32_t>(cx, value, &i)) {
611 : 188 : Gjs::gvalue_set(gvalue, i);
612 : : } else {
613 : 0 : return throw_expect_type(cx, value, "integer");
614 : : }
615 [ + + ]: 10386 : } else if (gtype == G_TYPE_INT64) {
616 : : int64_t i;
617 [ + - + - ]: 50 : if (Gjs::js_value_to_c_checked<int64_t>(cx, value, &i, &out_of_range) &&
618 [ + - ]: 25 : !out_of_range) {
619 : 25 : Gjs::gvalue_set(gvalue, i);
620 : : } else {
621 : 0 : return throw_expect_type(cx, value, "64-bit integer", 0,
622 : 0 : out_of_range);
623 : : }
624 [ + + ]: 10361 : } else if (gtype == G_TYPE_DOUBLE) {
625 : : double d;
626 [ + - ]: 95 : if (Gjs::js_value_to_c<double>(cx, value, &d)) {
627 : 95 : Gjs::gvalue_set(gvalue, d);
628 : : } else {
629 : 0 : return throw_expect_type(cx, value, "double");
630 : : }
631 [ + + ]: 10266 : } else if (gtype == G_TYPE_FLOAT) {
632 : : double d;
633 [ + - + - ]: 176 : if (Gjs::js_value_to_c_checked<float>(cx, value, &d, &out_of_range) &&
634 [ + - ]: 88 : !out_of_range) {
635 : 88 : Gjs::gvalue_set<float>(gvalue, d);
636 : : } else {
637 : 0 : return throw_expect_type(cx, value, "float", 0, out_of_range);
638 : : }
639 [ + + ]: 10178 : } else if (gtype == G_TYPE_UINT) {
640 : : uint32_t i;
641 [ + - ]: 19 : if (Gjs::js_value_to_c<uint32_t>(cx, value, &i)) {
642 : 19 : Gjs::gvalue_set(gvalue, i);
643 : : } else {
644 : 0 : return throw_expect_type(cx, value, "unsigned integer");
645 : : }
646 [ + + ]: 10159 : } else if (gtype == G_TYPE_UINT64) {
647 : : uint64_t i;
648 : 21 : if (Gjs::js_value_to_c_checked<uint64_t>(cx, value, &i,
649 [ + - + - ]: 42 : &out_of_range) &&
650 [ + - ]: 21 : !out_of_range) {
651 : 21 : Gjs::gvalue_set(gvalue, i);
652 : : } else {
653 : 0 : return throw_expect_type(cx, value, "unsigned 64-bit integer", 0,
654 : 0 : out_of_range);
655 : : }
656 [ + + ]: 10138 : } else if (gtype == G_TYPE_BOOLEAN) {
657 : : // JS::ToBoolean() can't fail
658 : 9684 : Gjs::gvalue_set(gvalue, JS::ToBoolean(value));
659 [ + + + + : 841 : } else if (g_type_is_a(gtype, G_TYPE_OBJECT) ||
+ + ]
660 [ + - - + ]: 387 : g_type_is_a(gtype, G_TYPE_INTERFACE)) {
661 : 67 : GObject* gobj = nullptr;
662 [ + + ]: 67 : if (value.isNull()) {
663 : : // nothing to do
664 [ + - ]: 64 : } else if (value.isObject()) {
665 : 64 : JS::RootedObject obj{cx, &value.toObject()};
666 [ + - ]: 128 : if (!ObjectBase::typecheck(cx, obj, gtype) ||
667 [ - + - + ]: 128 : !ObjectBase::to_c_ptr(cx, obj, &gobj))
668 : 0 : return false;
669 [ - + ]: 64 : if (!gobj)
670 : 0 : return true; // treat disposed object as if value.isNull()
671 [ + - ]: 64 : } else {
672 : 0 : return throw_expect_type(cx, value, "object", gtype);
673 : : }
674 : :
675 : 67 : Gjs::gvalue_set(gvalue, gobj);
676 [ + + ]: 387 : } else if (gtype == G_TYPE_STRV) {
677 [ - + ]: 9 : if (value.isNull())
678 : 0 : return true;
679 : :
680 : : bool is_array;
681 [ - + ]: 9 : if (!JS::IsArrayObject(cx, value, &is_array))
682 : 0 : return false;
683 [ - + ]: 9 : if (!is_array)
684 : 0 : return throw_expect_type(cx, value, "strv");
685 : :
686 : 9 : JS::RootedObject array_obj{cx, &value.toObject()};
687 : : uint32_t length;
688 [ - + ]: 9 : if (!JS::GetArrayLength(cx, array_obj, &length))
689 : 0 : return throw_expect_type(cx, value, "strv");
690 : :
691 : : void* result;
692 [ - + ]: 9 : if (!gjs_array_to_strv(cx, value, length, &result))
693 : 0 : return false;
694 : :
695 : 9 : g_value_take_boxed(gvalue, static_cast<char**>(result));
696 [ + - + - : 387 : } else if (g_type_is_a(gtype, G_TYPE_BOXED)) {
+ + + + ]
697 [ + + ]: 177 : if (value.isNull())
698 : 25 : return true;
699 : :
700 : 152 : void* gboxed = nullptr;
701 : : // special case GValue
702 [ + + ]: 152 : if (gtype == G_TYPE_VALUE) {
703 : : // explicitly handle values that are already GValues to avoid
704 : : // infinite recursion
705 [ + + ]: 26 : if (value.isObject()) {
706 : 18 : JS::RootedObject obj{cx, &value.toObject()};
707 : : GType guessed_gtype;
708 : :
709 [ - + ]: 18 : if (!gjs_value_guess_g_type(cx, value, &guessed_gtype))
710 : 0 : return false;
711 : :
712 [ + - ]: 18 : if (guessed_gtype == G_TYPE_VALUE) {
713 : 18 : gboxed = StructBase::to_c_ptr<GValue>(cx, obj);
714 : 18 : g_value_set_boxed(gvalue, gboxed);
715 : 18 : return true;
716 : : }
717 [ - + ]: 18 : }
718 : :
719 : 8 : Gjs::AutoGValue nested_gvalue;
720 [ - + ]: 8 : if (!gjs_value_to_g_value(cx, value, &nested_gvalue))
721 : 0 : return false;
722 : :
723 : 8 : g_value_set_boxed(gvalue, &nested_gvalue);
724 : 8 : return true;
725 : 8 : }
726 : :
727 [ + + ]: 126 : if (value.isObject()) {
728 : 108 : JS::RootedObject obj{cx, &value.toObject()};
729 : :
730 [ + + ]: 108 : if (gtype == ObjectBox::gtype()) {
731 : 35 : g_value_set_boxed(gvalue, ObjectBox::boxed(cx, obj).get());
732 : 35 : return true;
733 [ + + ]: 73 : } else if (gtype == G_TYPE_ERROR) {
734 : : // special case GError
735 : 3 : gboxed = ErrorBase::to_c_ptr(cx, obj);
736 [ - + ]: 3 : if (!gboxed)
737 : 0 : return false;
738 [ + + ]: 70 : } else if (gtype == G_TYPE_BYTE_ARRAY) {
739 : : // special case GByteArray
740 [ + - ]: 15 : if (JS_IsUint8Array(obj)) {
741 : 15 : g_value_take_boxed(gvalue,
742 : 15 : gjs_byte_array_get_byte_array(obj));
743 : 15 : return true;
744 : : }
745 [ - + ]: 55 : } else if (gtype == G_TYPE_ARRAY) {
746 : 0 : gjs_throw(cx, "Converting %s to GArray is not supported",
747 : : JS::InformalValueTypeName(value));
748 : 0 : return false;
749 [ - + ]: 55 : } else if (gtype == G_TYPE_PTR_ARRAY) {
750 : 0 : gjs_throw(cx, "Converting %s to GArray is not supported",
751 : : JS::InformalValueTypeName(value));
752 : 0 : return false;
753 [ - + ]: 55 : } else if (gtype == G_TYPE_HASH_TABLE) {
754 : 0 : gjs_throw(cx, "Converting %s to GHashTable is not supported",
755 : : JS::InformalValueTypeName(value));
756 : 0 : return false;
757 : : } else {
758 : 55 : GI::Repository repo;
759 : : Maybe<GI::AutoRegisteredTypeInfo> registered{
760 : 55 : repo.find_by_gtype(gtype)};
761 : :
762 : : // We don't necessarily have the typelib loaded when we first
763 : : // see the structure...
764 [ + - ]: 55 : if (registered) {
765 : 55 : if (auto struct_info =
766 : 55 : registered->as<GI::InfoTag::STRUCT>();
767 [ + + + + : 55 : struct_info && struct_info->is_foreign()) {
+ + ]
768 : : GIArgument arg;
769 : :
770 : 2 : if (!gjs_struct_foreign_convert_to_gi_argument(
771 [ - + ]: 4 : cx, value, struct_info.value(), nullptr,
772 : : GJS_ARGUMENT_ARGUMENT, GI_TRANSFER_NOTHING,
773 : : GjsArgumentFlags::MAY_BE_NULL, &arg))
774 : 0 : return false;
775 : :
776 : 2 : gboxed = gjs_arg_get<void*>(&arg);
777 [ + - ]: 55 : }
778 : : }
779 : :
780 : : // First try a union. If that fails, assume a boxed struct.
781 : : // Distinguishing which one is expected would require checking
782 : : // the associated GIBaseInfo, which is not necessarily possible,
783 : : // if e.g. we see the GType without loading the typelib.
784 [ + + ]: 55 : if (!gboxed) {
785 [ + + ]: 53 : if (UnionBase::typecheck(cx, obj, gtype,
786 : : GjsTypecheckNoThrow{})) {
787 : 1 : gboxed = UnionBase::to_c_ptr(cx, obj);
788 : : } else {
789 [ - + ]: 52 : if (!StructBase::typecheck(cx, obj, gtype))
790 : 0 : return false;
791 : :
792 : 52 : gboxed = StructBase::to_c_ptr(cx, obj);
793 : : }
794 [ - + ]: 53 : if (!gboxed)
795 : 0 : return false;
796 : : }
797 [ + - + - ]: 55 : }
798 [ + + ]: 108 : } else {
799 : 18 : return throw_expect_type(cx, value, "boxed type", gtype);
800 : : }
801 : :
802 [ + + ]: 58 : if (no_copy)
803 : 2 : g_value_set_static_boxed(gvalue, gboxed);
804 : : else
805 : 56 : g_value_set_boxed(gvalue, gboxed);
806 [ + + ]: 201 : } else if (gtype == G_TYPE_VARIANT) {
807 : 56 : GVariant* variant = nullptr;
808 : :
809 [ + + ]: 56 : if (value.isNull()) {
810 : : // nothing to do
811 [ + - ]: 53 : } else if (value.isObject()) {
812 : 53 : JS::RootedObject obj{cx, &value.toObject()};
813 : :
814 [ - + ]: 53 : if (!StructBase::typecheck(cx, obj, G_TYPE_VARIANT))
815 : 0 : return false;
816 : :
817 : 53 : variant = StructBase::to_c_ptr<GVariant>(cx, obj);
818 [ - + ]: 53 : if (!variant)
819 : 0 : return false;
820 [ + - ]: 53 : } else {
821 : 0 : return throw_expect_type(cx, value, "boxed type", gtype);
822 : : }
823 : :
824 : 56 : Gjs::gvalue_set(gvalue, variant);
825 [ + - + + : 145 : } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ + ]
826 : : int64_t value_int64;
827 : :
828 [ + - ]: 59 : if (Gjs::js_value_to_c<int64_t>(cx, value, &value_int64)) {
829 : 59 : Gjs::AutoTypeClass<GEnumClass> enum_class{gtype};
830 : :
831 : : // See arg.c:_gjs_enum_to_int()
832 : : GEnumValue* v =
833 : 59 : g_enum_get_value(enum_class, static_cast<int>(value_int64));
834 [ - + ]: 59 : if (v == nullptr) {
835 : 0 : gjs_throw(cx, "%d is not a valid value for enumeration %s",
836 : : value.toInt32(), g_type_name(gtype));
837 : 0 : return false;
838 : : }
839 : :
840 : 59 : g_value_set_enum(gvalue, v->value);
841 [ + - ]: 59 : } else {
842 : 0 : return throw_expect_type(cx, value, "enum", gtype);
843 : : }
844 [ + - + + : 86 : } else if (g_type_is_a(gtype, G_TYPE_FLAGS)) {
+ + ]
845 : : int64_t value_int64;
846 : :
847 [ + - ]: 14 : if (Gjs::js_value_to_c<int64_t>(cx, value, &value_int64)) {
848 [ - + ]: 14 : if (!_gjs_flags_value_is_valid(cx, gtype, value_int64))
849 : 0 : return false;
850 : :
851 : : // See arg.c:_gjs_enum_to_int()
852 : 14 : g_value_set_flags(gvalue, static_cast<int>(value_int64));
853 : : } else {
854 : 0 : return throw_expect_type(cx, value, "flags", gtype);
855 : : }
856 [ + + - + : 72 : } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
+ + ]
857 : 2 : GParamSpec* gparam = nullptr;
858 [ + - ]: 2 : if (value.isNull()) {
859 : : // nothing to do
860 [ + - ]: 2 : } else if (value.isObject()) {
861 : 2 : JS::RootedObject obj{cx, &value.toObject()};
862 : :
863 [ - + ]: 2 : if (!gjs_typecheck_param(cx, obj, gtype, true))
864 : 0 : return false;
865 : :
866 : 2 : gparam = gjs_g_param_from_param(cx, obj);
867 [ + - ]: 2 : } else {
868 : 0 : return throw_expect_type(cx, value, "param type", gtype);
869 : : }
870 : :
871 : 2 : g_value_set_param(gvalue, gparam);
872 [ + + ]: 70 : } else if (gtype == G_TYPE_GTYPE) {
873 : : GType type;
874 : :
875 [ - + ]: 11 : if (!value.isObject())
876 : 0 : return throw_expect_type(cx, value, "GType object");
877 : :
878 : 11 : JS::RootedObject obj{cx, &value.toObject()};
879 [ - + ]: 11 : if (!gjs_gtype_get_actual_gtype(cx, obj, &type))
880 : 0 : return false;
881 : 11 : Gjs::gvalue_set<Gjs::Tag::GType>(gvalue, type);
882 [ + - + + : 70 : } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
- + + + ]
883 [ - + ]: 12 : if (value.isNull()) {
884 : : // Nothing to do
885 : : } else {
886 : 0 : gjs_throw(cx, "Cannot convert non-null JS value to G_POINTER");
887 : 0 : return false;
888 : : }
889 [ + + + - : 83 : } else if (value.isNumber() &&
+ + ]
890 : 36 : g_value_type_transformable(G_TYPE_INT, gtype)) {
891 : : /* Only do this crazy gvalue transform stuff after we've exhausted
892 : : * everything else. Adding this for e.g. ClutterUnit.
893 : : */
894 : : int32_t i;
895 [ + - ]: 36 : if (Gjs::js_value_to_c<int32_t>(cx, value, &i)) {
896 : 36 : GValue int_value = { 0, };
897 : 36 : g_value_init(&int_value, G_TYPE_INT);
898 : 36 : Gjs::gvalue_set(&int_value, i);
899 : 36 : g_value_transform(&int_value, gvalue);
900 : : } else {
901 : 0 : return throw_expect_type(cx, value, "integer");
902 : : }
903 [ + - ]: 11 : } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
904 : : // The gtype is none of the above, it should be derived from a custom
905 : : // fundamental type.
906 [ - + ]: 11 : if (!value.isObject())
907 : 2 : return throw_expect_type(cx, value, "object", gtype);
908 : :
909 : 11 : JS::RootedObject fundamental_object{cx, &value.toObject()};
910 [ + + ]: 11 : if (!FundamentalBase::to_gvalue(cx, fundamental_object, gvalue))
911 : 2 : return false;
912 [ + + ]: 11 : } else {
913 : 0 : gjs_debug(GJS_DEBUG_GCLOSURE,
914 : : "JS::Value is number %d gtype fundamental %d transformable "
915 : : "to int %d from int %d",
916 : 0 : value.isNumber(), G_TYPE_IS_FUNDAMENTAL(gtype),
917 : : g_value_type_transformable(gtype, G_TYPE_INT),
918 : : g_value_type_transformable(G_TYPE_INT, gtype));
919 : :
920 : 0 : gjs_throw(cx, "Don't know how to convert JavaScript object to GType %s",
921 : : g_type_name(gtype));
922 : 0 : return false;
923 : : }
924 : :
925 : 10607 : return true;
926 : : }
927 : :
928 : 10728 : bool gjs_value_to_g_value(JSContext* cx, JS::HandleValue value,
929 : : GValue* gvalue) {
930 : 10728 : return gjs_value_to_g_value_internal(cx, value, gvalue, false);
931 : : }
932 : :
933 : 6 : bool gjs_value_to_g_value_no_copy(JSContext* cx, JS::HandleValue value,
934 : : GValue* gvalue) {
935 : 6 : return gjs_value_to_g_value_internal(cx, value, gvalue, true);
936 : : }
937 : :
938 : : [[nodiscard]]
939 : 33 : static JS::Value convert_int_to_enum(const GI::Repository& repo, GType gtype,
940 : : int64_t v) {
941 : : double v_double;
942 : :
943 [ + + + - ]: 33 : if (v > 0 && v < INT_MAX) {
944 : : // Optimize the unambiguous case
945 : 12 : v_double = v;
946 : : } else {
947 : : // Need to distinguish between negative integers and unsigned integers
948 : : Maybe<GI::AutoEnumInfo> info{
949 : 21 : repo.find_by_gtype<GI::InfoTag::ENUM>(gtype)};
950 : :
951 : : // Native enums don't have type info, assume they are signed to avoid
952 : : // crashing when they are exposed to JS.
953 [ + + ]: 21 : if (!info) {
954 : 13 : v_double = static_cast<int64_t>(v);
955 : : } else {
956 : 8 : v_double = info->enum_from_int(v);
957 : : }
958 : 21 : }
959 : :
960 : 33 : return JS::NumberValue(v_double);
961 : : }
962 : :
963 : : GJS_JSAPI_RETURN_CONVENTION
964 : 1824 : static bool gjs_value_from_g_value_internal(
965 : : JSContext* cx, JS::MutableHandleValue value_p, const GValue* gvalue,
966 : : bool no_copy, bool is_introspected_signal,
967 : : Maybe<std::pair<const GI::ArgInfo, const GI::TypeInfo>>
968 : : introspection_info) {
969 : : GType gtype;
970 : :
971 : 1824 : gtype = G_VALUE_TYPE(gvalue);
972 : :
973 : : gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
974 : : "Converting gtype %s to JS::Value",
975 : : g_type_name(gtype));
976 : :
977 [ + + + + : 3240 : if (gtype != G_TYPE_STRV && g_value_fits_pointer(gvalue) &&
+ + + + ]
978 : 1416 : g_value_peek_pointer(gvalue) == nullptr) {
979 : : // In theory here we should throw if !g_arg_info_may_be_null(arg_info)
980 : : // however most signals don't explicitly mark themselves as nullable,
981 : : // so better to avoid this.
982 : : gjs_debug_marshal(GJS_DEBUG_GCLOSURE,
983 : : "Converting NULL %s to JS::NullValue()",
984 : : g_type_name(gtype));
985 : 137 : value_p.setNull();
986 : 137 : return true;
987 : : }
988 : :
989 [ + + + + : 1687 : switch (gtype) {
+ + + + +
+ + + + ]
990 : 27 : case G_TYPE_CHAR:
991 : 27 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<signed char>(gvalue),
992 : 27 : value_p);
993 : 16 : case G_TYPE_UCHAR:
994 : 16 : return Gjs::c_value_to_js(
995 : 32 : cx, Gjs::gvalue_get<unsigned char>(gvalue), value_p);
996 : 133 : case G_TYPE_INT:
997 : 133 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<int>(gvalue),
998 : 133 : value_p);
999 : 15 : case G_TYPE_UINT:
1000 : 15 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<unsigned>(gvalue),
1001 : 15 : value_p);
1002 : 12 : case G_TYPE_LONG:
1003 : 12 : return Gjs::c_value_to_js<Gjs::Tag::Long>(
1004 : 12 : cx, Gjs::gvalue_get<Gjs::Tag::Long>(gvalue), value_p);
1005 : 14 : case G_TYPE_ULONG:
1006 : 14 : return Gjs::c_value_to_js<Gjs::Tag::UnsignedLong>(
1007 : 14 : cx, Gjs::gvalue_get<Gjs::Tag::UnsignedLong>(gvalue), value_p);
1008 : 26 : case G_TYPE_INT64:
1009 : 26 : return Gjs::c_value_to_js_checked(
1010 : 26 : cx, Gjs::gvalue_get<int64_t>(gvalue), value_p);
1011 : 17 : case G_TYPE_UINT64:
1012 : 17 : return Gjs::c_value_to_js_checked(
1013 : 17 : cx, Gjs::gvalue_get<uint64_t>(gvalue), value_p);
1014 : 35 : case G_TYPE_DOUBLE:
1015 : 35 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<double>(gvalue),
1016 : 35 : value_p);
1017 : 16 : case G_TYPE_FLOAT:
1018 : 16 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<float>(gvalue),
1019 : 16 : value_p);
1020 : 42 : case G_TYPE_BOOLEAN:
1021 : 42 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<bool>(gvalue),
1022 : 42 : value_p);
1023 : 253 : case G_TYPE_STRING:
1024 : 253 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<char*>(gvalue),
1025 : 253 : value_p);
1026 : : }
1027 : :
1028 [ + + + + : 1594 : if (g_type_is_a(gtype, G_TYPE_OBJECT) ||
+ - + + ]
1029 [ - + ]: 513 : g_type_is_a(gtype, G_TYPE_INTERFACE)) {
1030 : 568 : return ObjectInstance::set_value_from_gobject(
1031 : 568 : cx, Gjs::gvalue_get<GObject*>(gvalue), value_p);
1032 [ + + ]: 513 : } else if (gtype == G_TYPE_STRV) {
1033 [ - + ]: 13 : if (!gjs_array_from_strv(cx, value_p,
1034 : : Gjs::gvalue_get<const char**>(gvalue))) {
1035 : 0 : gjs_throw(cx, "Failed to convert strv to array");
1036 : 0 : return false;
1037 : : }
1038 [ + - + + ]: 500 : } else if (gtype == G_TYPE_ARRAY || gtype == G_TYPE_BYTE_ARRAY ||
1039 [ + + ]: 485 : gtype == G_TYPE_PTR_ARRAY) {
1040 [ + + ]: 21 : if (gtype == G_TYPE_BYTE_ARRAY) {
1041 : : auto* byte_array =
1042 : 15 : static_cast<GByteArray*>(g_value_get_boxed(gvalue));
1043 : 15 : JSObject* array = gjs_byte_array_from_byte_array(cx, byte_array);
1044 [ - + ]: 15 : if (!array) {
1045 : 0 : gjs_throw(cx, "Couldn't convert GByteArray to a Uint8Array");
1046 : 15 : return false;
1047 : : }
1048 : 15 : value_p.setObject(*array);
1049 : 15 : return true;
1050 : : }
1051 : :
1052 [ + - - + : 6 : if (!is_introspected_signal || !introspection_info) {
- + ]
1053 : 0 : gjs_throw(cx, "Can't convert untyped array to JS value");
1054 : 0 : return false;
1055 : : }
1056 : 6 : const GI::ArgInfo arg_info = introspection_info->first;
1057 : 6 : const GI::TypeInfo type_info = introspection_info->second;
1058 : :
1059 [ - + ]: 6 : if (!gjs_array_from_g_value_array(cx, value_p, type_info.element_type(),
1060 : : arg_info.ownership_transfer(),
1061 : : gvalue)) {
1062 : 0 : gjs_throw(cx, "Failed to convert array");
1063 : 0 : return false;
1064 : : }
1065 [ + + ]: 485 : } else if (gtype == G_TYPE_HASH_TABLE) {
1066 [ - + ]: 3 : if (!introspection_info) {
1067 : 0 : gjs_throw(cx,
1068 : : "Failed to get GValue from Hash Table without signal "
1069 : : "information");
1070 : 0 : return false;
1071 : : }
1072 : 3 : const GI::ArgInfo arg_info = introspection_info->first;
1073 : 3 : const GI::TypeInfo type_info = introspection_info->second;
1074 : :
1075 : 3 : GI::AutoTypeInfo key_info{type_info.key_type()};
1076 : 3 : GI::AutoTypeInfo value_info{type_info.value_type()};
1077 : 3 : GITypeTag key_tag = key_info.tag();
1078 : 3 : GITypeTag val_tag = value_info.tag();
1079 : :
1080 : 3 : auto* ghash = Gjs::gvalue_get<GHashTable*>(gvalue);
1081 [ - + - - : 3 : if (GI_TYPE_TAG_IS_BASIC(key_tag) && GI_TYPE_TAG_IS_BASIC(val_tag)) {
- + - - ]
1082 [ - + ]: 3 : if (!gjs_value_from_basic_ghash(cx, value_p, key_tag, val_tag,
1083 : : ghash))
1084 : 0 : return false;
1085 [ # # ]: 0 : } else if (!gjs_object_from_g_hash(cx, value_p, key_info, value_info,
1086 : : arg_info.ownership_transfer(),
1087 : : ghash)) {
1088 : 0 : gjs_throw(cx, "Failed to convert Hash Table");
1089 : 0 : return false;
1090 : : }
1091 [ + - + - : 479 : } else if (g_type_is_a(gtype, G_TYPE_BOXED) || gtype == G_TYPE_VARIANT) {
+ - + + +
+ + + ]
1092 [ + + - + : 233 : if (g_type_is_a(gtype, ObjectBox::gtype())) {
+ + ]
1093 : 30 : JSObject* obj = ObjectBox::object_for_c_ptr(
1094 : : cx, Gjs::gvalue_get<ObjectBox*>(gvalue));
1095 [ - + ]: 30 : if (!obj)
1096 : 41 : return false;
1097 : 30 : value_p.setObject(*obj);
1098 : 30 : return true;
1099 : : }
1100 : :
1101 : : // special case GError
1102 [ + + ]: 203 : if (gtype == G_TYPE_ERROR) {
1103 : 2 : JSObject* obj = ErrorInstance::object_for_c_ptr(
1104 : : cx, Gjs::gvalue_get<GError*>(gvalue));
1105 [ - + ]: 2 : if (!obj)
1106 : 0 : return false;
1107 : 2 : value_p.setObject(*obj);
1108 : 2 : return true;
1109 : : }
1110 : :
1111 : : // special case GValue
1112 [ + + ]: 201 : if (gtype == G_TYPE_VALUE) {
1113 : 8 : return gjs_value_from_g_value(cx, value_p,
1114 : 16 : Gjs::gvalue_get<GValue*>(gvalue));
1115 : : }
1116 : :
1117 : : /* The only way to differentiate unions and structs is from their g-i
1118 : : * info as both are GBoxed */
1119 : 193 : GI::Repository repo;
1120 : 193 : Maybe<GI::AutoRegisteredTypeInfo> info{repo.find_by_gtype(gtype)};
1121 [ - + ]: 193 : if (!info) {
1122 : 0 : gjs_throw(cx, "No introspection information found for %s",
1123 : : g_type_name(gtype));
1124 : 0 : return false;
1125 : : }
1126 : :
1127 : : JSObject* obj;
1128 : 193 : void* gboxed = Gjs::gvalue_get<void*>(gvalue);
1129 [ + - ]: 193 : if (auto struct_info = info->as<GI::InfoTag::STRUCT>()) {
1130 [ + + ]: 193 : if (struct_info->is_foreign()) {
1131 : : GIArgument arg;
1132 : 1 : gjs_arg_set(&arg, gboxed);
1133 : 1 : return gjs_struct_foreign_convert_from_gi_argument(
1134 : 2 : cx, value_p, struct_info.value(), &arg);
1135 : : }
1136 : :
1137 [ + + ]: 192 : if (no_copy) {
1138 : 1 : obj = StructInstance::new_for_c_struct(cx, struct_info.value(),
1139 : : gboxed, Boxed::NoCopy{});
1140 : : } else {
1141 : 191 : obj = StructInstance::new_for_c_struct(cx, struct_info.value(),
1142 : : gboxed);
1143 : : }
1144 [ # # ]: 0 : } else if (auto union_info = info->as<GI::InfoTag::UNION>()) {
1145 : : obj =
1146 : 0 : UnionInstance::new_for_c_union(cx, union_info.value(), gboxed);
1147 : : } else {
1148 : 0 : gjs_throw(cx, "Unexpected introspection type %s for %s",
1149 : 0 : info->type_string(), g_type_name(gtype));
1150 : 0 : return false;
1151 [ - - + + ]: 193 : }
1152 [ - + ]: 192 : if (!obj)
1153 : 0 : return false;
1154 : :
1155 : 192 : value_p.setObject(*obj);
1156 [ + + + + : 437 : } else if (g_type_is_a(gtype, G_TYPE_ENUM)) {
+ + + + +
+ ]
1157 : 33 : GI::Repository repo;
1158 : 33 : value_p.set(convert_int_to_enum(
1159 : : repo, gtype, Gjs::gvalue_get<Gjs::Tag::Long>(gvalue)));
1160 [ + + - + : 243 : } else if (g_type_is_a(gtype, G_TYPE_PARAM)) {
+ + ]
1161 : 183 : GParamSpec* gparam = Gjs::gvalue_get<GParamSpec*>(gvalue);
1162 : : JSObject *obj;
1163 : :
1164 : 183 : obj = gjs_param_from_g_param(cx, gparam);
1165 [ - + ]: 183 : if (!obj)
1166 : 0 : return false;
1167 : 183 : value_p.setObject(*obj);
1168 [ - + - - : 27 : } else if (is_introspected_signal && g_type_is_a(gtype, G_TYPE_POINTER)) {
- - - + ]
1169 [ # # ]: 0 : if (!introspection_info) {
1170 : 0 : gjs_throw(cx, "Unknown signal.");
1171 : 0 : return false;
1172 : : }
1173 : 0 : const GI::TypeInfo type_info = introspection_info->second;
1174 : :
1175 : 0 : g_assert(!type_info.array_length_index() &&
1176 : : "Check gjs_value_from_array_and_length_values() before "
1177 : : "calling gjs_value_from_g_value_internal()");
1178 : :
1179 : : GIArgument arg;
1180 : 0 : gjs_arg_set(&arg, Gjs::gvalue_get<void*>(gvalue));
1181 : :
1182 : 0 : return gjs_value_from_gi_argument(cx, value_p, type_info, &arg, true);
1183 [ + + ]: 27 : } else if (gtype == G_TYPE_GTYPE) {
1184 : 5 : GType gvalue_gtype = Gjs::gvalue_get<Gjs::Tag::GType>(gvalue);
1185 : :
1186 [ - + ]: 5 : if (gvalue_gtype == 0) {
1187 : 0 : value_p.setNull();
1188 : 0 : return true;
1189 : : }
1190 : :
1191 : : JS::RootedObject obj{cx,
1192 : 5 : gjs_gtype_create_gtype_wrapper(cx, gvalue_gtype)};
1193 [ - + ]: 5 : if (!obj)
1194 : 0 : return false;
1195 : :
1196 : 5 : value_p.setObject(*obj);
1197 [ + - + - : 27 : } else if (g_type_is_a(gtype, G_TYPE_POINTER)) {
- + - + ]
1198 [ # # ]: 0 : if (Gjs::gvalue_get<void*>(gvalue) != nullptr) {
1199 : 0 : gjs_throw(cx, "Can't convert non-null pointer to JS value");
1200 : 0 : return false;
1201 : : }
1202 [ - + ]: 22 : } else if (g_value_type_transformable(gtype, G_TYPE_DOUBLE)) {
1203 : 0 : GValue double_value = { 0, };
1204 : 0 : g_value_init(&double_value, G_TYPE_DOUBLE);
1205 : 0 : g_value_transform(gvalue, &double_value);
1206 : 0 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<double>(&double_value),
1207 : 0 : value_p);
1208 [ + + ]: 22 : } else if (g_value_type_transformable(gtype, G_TYPE_INT)) {
1209 : 9 : GValue int_value = { 0, };
1210 : 9 : g_value_init(&int_value, G_TYPE_INT);
1211 : 9 : g_value_transform(gvalue, &int_value);
1212 : 9 : return Gjs::c_value_to_js(cx, Gjs::gvalue_get<int>(&int_value),
1213 : 9 : value_p);
1214 [ + - ]: 13 : } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
1215 : : // The gtype is none of the above, it should be a custom fundamental
1216 : : // type.
1217 : 13 : JS::RootedObject obj{cx};
1218 [ - + ]: 13 : if (!FundamentalInstance::object_for_gvalue(cx, gvalue, gtype, &obj))
1219 : 0 : return false;
1220 : :
1221 : 13 : value_p.setObject(*obj);
1222 [ + - ]: 13 : } else {
1223 : 0 : gjs_throw(cx, "Don't know how to convert GType %s to JavaScript object",
1224 : : g_type_name(gtype));
1225 : 0 : return false;
1226 : : }
1227 : :
1228 : 448 : return true;
1229 : : }
1230 : :
1231 : 888 : bool gjs_value_from_g_value(JSContext* cx, JS::MutableHandleValue value_p,
1232 : : const GValue* gvalue) {
1233 : 888 : return gjs_value_from_g_value_internal(cx, value_p, gvalue, false);
1234 : : }
|