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 <stddef.h> // for NULL, size_t
8 : : #include <stdint.h>
9 : :
10 : : #include <limits>
11 : : #include <memory> // for unique_ptr
12 : : #include <sstream>
13 : : #include <string>
14 : : #include <vector>
15 : :
16 : : #include <ffi.h>
17 : : #include <girepository.h>
18 : : #include <girffi.h>
19 : : #include <glib-object.h>
20 : : #include <glib.h>
21 : :
22 : : #include <js/Array.h>
23 : : #include <js/CallAndConstruct.h> // for IsCallable
24 : : #include <js/CallArgs.h>
25 : : #include <js/Class.h>
26 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
27 : : #include <js/Exception.h>
28 : : #include <js/HeapAPI.h> // for RuntimeHeapIsCollecting
29 : : #include <js/PropertyAndElement.h>
30 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT
31 : : #include <js/PropertySpec.h>
32 : : #include <js/Realm.h> // for GetRealmFunctionPrototype
33 : : #include <js/RootingAPI.h>
34 : : #include <js/TypeDecls.h>
35 : : #include <js/Value.h>
36 : : #include <js/ValueArray.h>
37 : : #include <js/Warnings.h>
38 : : #include <jsapi.h> // for HandleValueArray
39 : : #include <jspubtd.h> // for JSProtoKey
40 : :
41 : : #include "gi/arg-cache.h"
42 : : #include "gi/arg-inl.h"
43 : : #include "gi/arg.h"
44 : : #include "gi/closure.h"
45 : : #include "gi/cwrapper.h"
46 : : #include "gi/function.h"
47 : : #include "gi/gerror.h"
48 : : #include "gi/object.h"
49 : : #include "gi/utils-inl.h"
50 : : #include "gjs/context-private.h"
51 : : #include "gjs/global.h"
52 : : #include "gjs/jsapi-util.h"
53 : : #include "gjs/macros.h"
54 : : #include "gjs/mem-private.h"
55 : : #include "gjs/profiler-private.h"
56 : : #include "util/log.h"
57 : :
58 : : /* We use guint8 for arguments; functions can't
59 : : * have more than this.
60 : : */
61 : : #define GJS_ARG_INDEX_INVALID G_MAXUINT8
62 : :
63 : : namespace Gjs {
64 : :
65 : : class Function : public CWrapper<Function> {
66 : : friend CWrapperPointerOps<Function>;
67 : : friend CWrapper<Function>;
68 : :
69 : : static constexpr auto PROTOTYPE_SLOT = GjsGlobalSlot::PROTOTYPE_function;
70 : : static constexpr GjsDebugTopic DEBUG_TOPIC = GJS_DEBUG_GFUNCTION;
71 : :
72 : : GjsAutoCallableInfo m_info;
73 : :
74 : : ArgsCache m_arguments;
75 : :
76 : : uint8_t m_js_in_argc;
77 : : uint8_t m_js_out_argc;
78 : : GIFunctionInvoker m_invoker;
79 : :
80 : 10058 : explicit Function(GICallableInfo* info)
81 : 10058 : : m_info(info, GjsAutoTakeOwnership()),
82 : 10058 : m_js_in_argc(0),
83 : 10058 : m_js_out_argc(0),
84 : 10058 : m_invoker({}) {
85 : 10058 : GJS_INC_COUNTER(function);
86 : 10058 : }
87 : : ~Function();
88 : :
89 : : GJS_JSAPI_RETURN_CONVENTION
90 : : bool init(JSContext* cx, GType gtype = G_TYPE_NONE);
91 : :
92 : : /**
93 : : * Like CWrapperPointerOps::for_js_typecheck(), but additionally checks that
94 : : * the pointer is not null, which is the case for prototype objects.
95 : : */
96 : : GJS_JSAPI_RETURN_CONVENTION
97 : 13 : static bool for_js_instance(JSContext* cx, JS::HandleObject obj,
98 : : Function** out, JS::CallArgs* args) {
99 : : Function* priv;
100 [ - + ]: 13 : if (!Function::for_js_typecheck(cx, obj, &priv, args))
101 : 0 : return false;
102 [ - + ]: 13 : if (!priv) {
103 : : // This is the prototype
104 : 0 : gjs_throw(cx, "Impossible on prototype; only on instances");
105 : 0 : return false;
106 : : }
107 : 13 : *out = priv;
108 : 13 : return true;
109 : : }
110 : :
111 : : GJS_JSAPI_RETURN_CONVENTION
112 : : static bool call(JSContext* cx, unsigned argc, JS::Value* vp);
113 : :
114 : : static void finalize_impl(JS::GCContext*, Function* priv);
115 : :
116 : : GJS_JSAPI_RETURN_CONVENTION
117 : : static bool get_length(JSContext* cx, unsigned argc, JS::Value* vp);
118 : :
119 : : GJS_JSAPI_RETURN_CONVENTION
120 : : static bool to_string(JSContext* cx, unsigned argc, JS::Value* vp);
121 : :
122 : : GJS_JSAPI_RETURN_CONVENTION
123 : : bool to_string_impl(JSContext* cx, JS::MutableHandleValue rval);
124 : :
125 : : GJS_JSAPI_RETURN_CONVENTION
126 : : bool finish_invoke(JSContext* cx, const JS::CallArgs& args,
127 : : GjsFunctionCallState* state,
128 : : GIArgument* r_value = nullptr);
129 : :
130 : : GJS_JSAPI_RETURN_CONVENTION
131 : 56 : static JSObject* inherit_builtin_function(JSContext* cx, JSProtoKey) {
132 : : JS::RootedObject builtin_function_proto(
133 : 56 : cx, JS::GetRealmFunctionPrototype(cx));
134 : 56 : return JS_NewObjectWithGivenProto(cx, nullptr, builtin_function_proto);
135 : 56 : }
136 : :
137 : : static const JSClassOps class_ops;
138 : : static const JSPropertySpec proto_props[];
139 : : static const JSFunctionSpec proto_funcs[];
140 : :
141 : : static constexpr js::ClassSpec class_spec = {
142 : : nullptr, // createConstructor
143 : : &Function::inherit_builtin_function,
144 : : nullptr, // constructorFunctions
145 : : nullptr, // constructorProperties
146 : : Function::proto_funcs,
147 : : Function::proto_props,
148 : : nullptr, // finishInit
149 : : js::ClassSpec::DontDefineConstructor};
150 : :
151 : : static constexpr JSClass klass = {
152 : : "GIRepositoryFunction",
153 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_BACKGROUND_FINALIZE,
154 : : &Function::class_ops, &Function::class_spec};
155 : :
156 : : public:
157 : : GJS_JSAPI_RETURN_CONVENTION
158 : : static JSObject* create(JSContext* cx, GType gtype, GICallableInfo* info);
159 : :
160 : : [[nodiscard]] std::string format_name();
161 : :
162 : : GJS_JSAPI_RETURN_CONVENTION
163 : : bool invoke(JSContext* cx, const JS::CallArgs& args,
164 : : JS::HandleObject this_obj = nullptr,
165 : : GIArgument* r_value = nullptr);
166 : :
167 : : GJS_JSAPI_RETURN_CONVENTION
168 : 0 : static bool invoke_constructor_uncached(JSContext* cx, GIFunctionInfo* info,
169 : : JS::HandleObject obj,
170 : : const JS::CallArgs& args,
171 : : GIArgument* rvalue) {
172 : 0 : Function function(info);
173 [ # # ]: 0 : if (!function.init(cx))
174 : 0 : return false;
175 : 0 : return function.invoke(cx, args, obj, rvalue);
176 : 0 : }
177 : : };
178 : :
179 : : } // namespace Gjs
180 : :
181 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
182 : 532 : static inline void set_ffi_arg(void* result, GIArgument* value) {
183 : : if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
184 : 439 : *static_cast<ffi_sarg*>(result) = gjs_arg_get<T, TAG>(value);
185 : : } else if constexpr (std::is_floating_point_v<T> || std::is_unsigned_v<T>) {
186 : 0 : *static_cast<ffi_arg*>(result) = gjs_arg_get<T, TAG>(value);
187 : : } else if constexpr (std::is_pointer_v<T>) {
188 : 93 : *static_cast<ffi_arg*>(result) =
189 : 93 : gjs_pointer_to_int<ffi_arg>(gjs_arg_get<T, TAG>(value));
190 : : }
191 : 532 : }
192 : :
193 : 532 : static void set_return_ffi_arg_from_gi_argument(GITypeInfo* ret_type,
194 : : void* result,
195 : : GIArgument* return_value) {
196 : : // Be consistent with gjs_value_to_gi_argument()
197 [ - - - - : 532 : switch (g_type_info_get_tag(ret_type)) {
- + - + -
+ + - - -
- + - ]
198 : 0 : case GI_TYPE_TAG_VOID:
199 : : g_assert_not_reached();
200 : 0 : case GI_TYPE_TAG_INT8:
201 : 0 : set_ffi_arg<int8_t>(result, return_value);
202 : 0 : break;
203 : 0 : case GI_TYPE_TAG_UINT8:
204 : 0 : set_ffi_arg<uint8_t>(result, return_value);
205 : 0 : break;
206 : 0 : case GI_TYPE_TAG_INT16:
207 : 0 : set_ffi_arg<int16_t>(result, return_value);
208 : 0 : break;
209 : 0 : case GI_TYPE_TAG_UINT16:
210 : 0 : set_ffi_arg<uint16_t>(result, return_value);
211 : 0 : break;
212 : 82 : case GI_TYPE_TAG_INT32:
213 : 82 : set_ffi_arg<int32_t>(result, return_value);
214 : 82 : break;
215 : 0 : case GI_TYPE_TAG_UINT32:
216 : 0 : set_ffi_arg<uint32_t>(result, return_value);
217 : 0 : break;
218 : 169 : case GI_TYPE_TAG_BOOLEAN:
219 : 169 : set_ffi_arg<gboolean, GI_TYPE_TAG_BOOLEAN>(result, return_value);
220 : 169 : break;
221 : 0 : case GI_TYPE_TAG_UNICHAR:
222 : 0 : set_ffi_arg<char32_t>(result, return_value);
223 : 0 : break;
224 : 18 : case GI_TYPE_TAG_INT64:
225 : 18 : set_ffi_arg<int64_t>(result, return_value);
226 : 18 : break;
227 : 259 : case GI_TYPE_TAG_INTERFACE:
228 : : {
229 : 259 : GjsAutoBaseInfo interface_info(g_type_info_get_interface(ret_type));
230 : 259 : GIInfoType interface_type = interface_info.type();
231 : :
232 [ + + + + ]: 259 : if (interface_type == GI_INFO_TYPE_ENUM ||
233 : : interface_type == GI_INFO_TYPE_FLAGS)
234 : 170 : set_ffi_arg<int, GI_TYPE_TAG_INTERFACE>(result, return_value);
235 : : else
236 : 89 : set_ffi_arg<void*>(result, return_value);
237 : 259 : }
238 : 259 : break;
239 : 0 : case GI_TYPE_TAG_UINT64:
240 : : // Other primitive types need to squeeze into 64-bit ffi_arg too
241 : 0 : set_ffi_arg<uint64_t>(result, return_value);
242 : 0 : break;
243 : 0 : case GI_TYPE_TAG_FLOAT:
244 : 0 : set_ffi_arg<float>(result, return_value);
245 : 0 : break;
246 : 0 : case GI_TYPE_TAG_DOUBLE:
247 : 0 : set_ffi_arg<double>(result, return_value);
248 : 0 : break;
249 : 0 : case GI_TYPE_TAG_GTYPE:
250 : 0 : set_ffi_arg<GType, GI_TYPE_TAG_GTYPE>(result, return_value);
251 : 0 : break;
252 : 4 : case GI_TYPE_TAG_UTF8:
253 : : case GI_TYPE_TAG_FILENAME:
254 : 4 : set_ffi_arg<char*>(result, return_value);
255 : 4 : break;
256 : 0 : case GI_TYPE_TAG_ARRAY:
257 : : case GI_TYPE_TAG_GLIST:
258 : : case GI_TYPE_TAG_GSLIST:
259 : : case GI_TYPE_TAG_GHASH:
260 : : case GI_TYPE_TAG_ERROR:
261 : : default:
262 : 0 : set_ffi_arg<void*>(result, return_value);
263 : 0 : break;
264 : : }
265 : 532 : }
266 : :
267 : 4 : void GjsCallbackTrampoline::warn_about_illegal_js_callback(const char* when,
268 : : const char* reason,
269 : : bool dump_stack) {
270 : 4 : std::ostringstream message;
271 : :
272 : : message << "Attempting to run a JS callback " << when << ". "
273 : : << "This is most likely caused by " << reason << ". "
274 : 4 : << "Because it would crash the application, it has been blocked.";
275 [ + - ]: 4 : if (m_info) {
276 : : message << "\nThe offending callback was " << m_info.name() << "()"
277 [ + - ]: 4 : << (m_is_vfunc ? ", a vfunc." : ".");
278 : : }
279 [ + - ]: 4 : if (dump_stack) {
280 : 4 : message << "\n" << gjs_dumpstack_string();
281 : : }
282 : 4 : g_critical("%s", message.str().c_str());
283 : 4 : }
284 : :
285 : : /* This is our main entry point for ffi_closure callbacks.
286 : : * ffi_prep_closure is doing pure magic and replaces the original
287 : : * function call with this one which gives us the ffi arguments,
288 : : * a place to store the return value and our use data.
289 : : * In other words, everything we need to call the JS function and
290 : : * getting the return value back.
291 : : */
292 : 464 : void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) {
293 : : GITypeInfo ret_type;
294 : :
295 : : // Fill in the result with some hopefully neutral value
296 : 464 : g_callable_info_load_return_type(m_info, &ret_type);
297 [ + + ]: 464 : if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) {
298 : 277 : GIArgument argument = {};
299 : 277 : gjs_gi_argument_init_default(&ret_type, &argument);
300 : 277 : set_return_ffi_arg_from_gi_argument(&ret_type, result, &argument);
301 : : }
302 : :
303 [ - + ]: 464 : if (G_UNLIKELY(!is_valid())) {
304 : 0 : warn_about_illegal_js_callback(
305 : : "during shutdown",
306 : : "destroying a Clutter actor or GTK widget with ::destroy signal "
307 : : "connected, or using the destroy(), dispose(), or remove() vfuncs",
308 : : true);
309 : 12 : return;
310 : : }
311 : :
312 : 464 : JSContext* context = this->context();
313 : 464 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
314 [ + + ]: 464 : if (JS::RuntimeHeapIsCollecting()) {
315 : 4 : warn_about_illegal_js_callback(
316 : : "during garbage collection",
317 : : "destroying a Clutter actor or GTK widget with ::destroy signal "
318 : : "connected, or using the destroy(), dispose(), or remove() vfuncs",
319 : : true);
320 : 4 : return;
321 : : }
322 : :
323 [ - + ]: 460 : if (G_UNLIKELY(!gjs->is_owner_thread())) {
324 : 0 : warn_about_illegal_js_callback("on a different thread",
325 : : "an API not intended to be used in JS",
326 : : false);
327 : 0 : return;
328 : : }
329 : :
330 : 460 : JSAutoRealm ar(context, callable());
331 : :
332 : 460 : int n_args = g_callable_info_get_n_args(m_info);
333 : 460 : g_assert(n_args >= 0);
334 : :
335 : : struct AutoCallbackData {
336 : 460 : AutoCallbackData(GjsCallbackTrampoline* trampoline,
337 : : GjsContextPrivate* gjs)
338 : 460 : : trampoline(trampoline), gjs(gjs) {}
339 : 458 : ~AutoCallbackData() {
340 [ + + ]: 458 : if (trampoline->m_scope == GI_SCOPE_TYPE_ASYNC) {
341 : : // We don't release the trampoline here as we've an extra ref
342 : : // that has been set in gjs_marshal_callback_in()
343 : : gjs_debug_closure("Saving async closure for gc cleanup %p",
344 : : trampoline);
345 : 112 : gjs->async_closure_enqueue_for_gc(trampoline);
346 : : }
347 : 458 : gjs->schedule_gc_if_needed();
348 : 458 : }
349 : :
350 : : GjsCallbackTrampoline* trampoline;
351 : : GjsContextPrivate* gjs;
352 : : };
353 : :
354 : 460 : AutoCallbackData callback_data(this, gjs);
355 : 460 : JS::RootedObject this_object(context);
356 : 460 : int c_args_offset = 0;
357 : 460 : GObject* gobj = nullptr;
358 [ + + ]: 460 : if (m_is_vfunc) {
359 : 106 : gobj = G_OBJECT(gjs_arg_get<GObject*>(args[0]));
360 [ + - ]: 106 : if (gobj) {
361 : 106 : this_object = ObjectInstance::wrapper_from_gobject(context, gobj);
362 [ - + ]: 106 : if (!this_object) {
363 [ # # ]: 0 : if (g_object_get_qdata(gobj, ObjectBase::disposed_quark())) {
364 : 0 : warn_about_illegal_js_callback(
365 : : "on disposed object",
366 : : "using the destroy(), dispose(), or remove() vfuncs",
367 : : false);
368 : : }
369 : 0 : gjs_log_exception(context);
370 : 0 : return;
371 : : }
372 : : }
373 : :
374 : : /* "this" is not included in the GI signature, but is in the C (and
375 : : * FFI) signature */
376 : 106 : c_args_offset = 1;
377 : : }
378 : :
379 : 460 : JS::RootedValue rval(context);
380 : :
381 [ + + ]: 460 : if (!callback_closure_inner(context, this_object, gobj, &rval, args,
382 : : &ret_type, n_args, c_args_offset, result)) {
383 [ + + ]: 25 : if (!JS_IsExceptionPending(context)) {
384 : : // "Uncatchable" exception thrown, we have to exit. We may be in a
385 : : // main loop, or maybe not, but there's no way to tell, so we have
386 : : // to exit here instead of propagating the exception back to the
387 : : // original calling JS code.
388 : : uint8_t code;
389 [ + - ]: 1 : if (gjs->should_exit(&code))
390 : 1 : gjs->exit_immediately(code);
391 : :
392 : : // Some other uncatchable exception, e.g. out of memory
393 : 0 : g_error("Call to %s (%s.%s) terminated with uncatchable exception",
394 : : gjs_debug_callable(callable()).c_str(), m_info.ns(),
395 : : m_info.name());
396 : : }
397 : :
398 : : // If the callback has a GError** argument, then make a GError from the
399 : : // value that was thrown. Otherwise, log it as "uncaught" (critical
400 : : // instead of warning)
401 : :
402 [ + + ]: 24 : if (!g_callable_info_can_throw_gerror(m_info)) {
403 : 8 : gjs_log_exception_uncaught(context);
404 : 8 : return;
405 : : }
406 : :
407 : : // The GError** pointer is the last argument, and is not included in
408 : : // the n_args
409 : 16 : GIArgument* error_argument = args[n_args + c_args_offset];
410 : 16 : auto* gerror = gjs_arg_get<GError**>(error_argument);
411 : 16 : GError* local_error = gjs_gerror_make_from_thrown_value(context);
412 : 16 : g_propagate_error(gerror, local_error);
413 : : }
414 [ + + + + : 482 : }
+ + + + ]
415 : :
416 : 34 : inline GIArgument* get_argument_for_arg_info(GIArgInfo* arg_info,
417 : : GIArgument** args, int index) {
418 [ + + ]: 34 : if (!g_arg_info_is_caller_allocates(arg_info))
419 : 31 : return *reinterpret_cast<GIArgument**>(args[index]);
420 : : else
421 : 3 : return args[index];
422 : : }
423 : :
424 : 460 : bool GjsCallbackTrampoline::callback_closure_inner(
425 : : JSContext* context, JS::HandleObject this_object, GObject* gobject,
426 : : JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type,
427 : : int n_args, int c_args_offset, void* result) {
428 : 460 : int n_outargs = 0;
429 : 460 : JS::RootedValueVector jsargs(context);
430 : :
431 [ - + ]: 460 : if (!jsargs.reserve(n_args))
432 : 0 : g_error("Unable to reserve space for vector");
433 : :
434 : 460 : GITypeTag ret_tag = g_type_info_get_tag(ret_type);
435 : 460 : bool ret_type_is_void = ret_tag == GI_TYPE_TAG_VOID;
436 : 460 : bool in_args_to_cleanup = false;
437 : :
438 [ + + ]: 1756 : for (int i = 0, n_jsargs = 0; i < n_args; i++) {
439 : : GIArgInfo arg_info;
440 : : GITypeInfo type_info;
441 : : GjsParamType param_type;
442 : :
443 : 1296 : g_callable_info_load_arg(m_info, i, &arg_info);
444 : 1296 : g_arg_info_load_type(&arg_info, &type_info);
445 : :
446 : : /* Skip void * arguments */
447 [ + + ]: 1296 : if (g_type_info_get_tag(&type_info) == GI_TYPE_TAG_VOID)
448 : 386 : continue;
449 : :
450 [ + + ]: 945 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT) {
451 : 33 : n_outargs++;
452 : 33 : continue;
453 : : }
454 : :
455 [ + + ]: 912 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT)
456 : 6 : n_outargs++;
457 : :
458 [ + + ]: 912 : if (g_arg_info_get_ownership_transfer(&arg_info) != GI_TRANSFER_NOTHING)
459 : 6 : in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER;
460 : :
461 : 912 : param_type = m_param_types[i];
462 : :
463 [ + + + - ]: 912 : switch (param_type) {
464 : 2 : case PARAM_SKIPPED:
465 : 2 : continue;
466 : 2 : case PARAM_ARRAY: {
467 : 2 : gint array_length_pos = g_type_info_get_array_length(&type_info);
468 : : GIArgInfo array_length_arg;
469 : : GITypeInfo arg_type_info;
470 : :
471 : 2 : g_callable_info_load_arg(m_info, array_length_pos,
472 : : &array_length_arg);
473 : 2 : g_arg_info_load_type(&array_length_arg, &arg_type_info);
474 : 2 : size_t length = gjs_gi_argument_get_array_length(
475 : : g_type_info_get_tag(&arg_type_info),
476 : 2 : args[array_length_pos + c_args_offset]);
477 : :
478 [ - + ]: 2 : if (!jsargs.growBy(1))
479 : 0 : g_error("Unable to grow vector");
480 : :
481 [ - + ]: 2 : if (!gjs_value_from_explicit_array(
482 : 2 : context, jsargs[n_jsargs++], &type_info,
483 : : g_arg_info_get_ownership_transfer(&arg_info),
484 : 2 : args[i + c_args_offset], length))
485 : 0 : return false;
486 : 2 : break;
487 : : }
488 : 908 : case PARAM_NORMAL: {
489 [ - + ]: 908 : if (!jsargs.growBy(1))
490 : 0 : g_error("Unable to grow vector");
491 : :
492 : 908 : GIArgument* arg = args[i + c_args_offset];
493 [ + + + - : 914 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT &&
+ + ]
494 : 6 : !g_arg_info_is_caller_allocates(&arg_info))
495 : 6 : arg = *reinterpret_cast<GIArgument**>(arg);
496 : :
497 [ - + ]: 908 : if (!gjs_value_from_gi_argument(context, jsargs[n_jsargs++],
498 : : &type_info, arg, false))
499 : 0 : return false;
500 : 908 : break;
501 : : }
502 : 0 : case PARAM_CALLBACK:
503 : : /* Callbacks that accept another callback as a parameter are not
504 : : * supported, see gjs_callback_trampoline_new() */
505 : : case PARAM_UNKNOWN:
506 : : // PARAM_UNKNOWN is currently not ever set on a callback's args.
507 : : default:
508 : : g_assert_not_reached();
509 : 2 : }
510 : : }
511 : :
512 [ + + ]: 460 : if (!invoke(this_object, jsargs, rval))
513 : 17 : return false;
514 : :
515 [ + + + + ]: 442 : if (n_outargs == 0 && ret_type_is_void) {
516 : : /* void return value, no out args, nothing to do */
517 [ + + ]: 278 : } else if (n_outargs == 0) {
518 : : GIArgument argument;
519 : : GITransfer transfer;
520 : :
521 : 247 : transfer = g_callable_info_get_caller_owns(m_info);
522 : : /* non-void return value, no out args. Should
523 : : * be a single return value. */
524 [ + + ]: 247 : if (!gjs_value_to_gi_argument(context, rval, ret_type, "callback",
525 : : GJS_ARGUMENT_RETURN_VALUE, transfer,
526 : : GjsArgumentFlags::MAY_BE_NULL, &argument))
527 : 2 : return false;
528 : :
529 : 245 : set_return_ffi_arg_from_gi_argument(ret_type, result, &argument);
530 [ + + + + ]: 31 : } else if (n_outargs == 1 && ret_type_is_void) {
531 : : /* void return value, one out args. Should
532 : : * be a single return value. */
533 [ + - ]: 17 : for (int i = 0; i < n_args; i++) {
534 : : GIArgInfo arg_info;
535 : 17 : g_callable_info_load_arg(m_info, i, &arg_info);
536 [ + + ]: 17 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
537 : 2 : continue;
538 : :
539 [ + + ]: 15 : if (!gjs_value_to_callback_out_arg(
540 : : context, rval, &arg_info,
541 : : get_argument_for_arg_info(&arg_info, args,
542 : : i + c_args_offset)))
543 : 3 : return false;
544 : :
545 : 12 : break;
546 : : }
547 : 12 : } else {
548 : 16 : bool is_array = rval.isObject();
549 [ - + ]: 16 : if (!JS::IsArrayObject(context, rval, &is_array))
550 : 3 : return false;
551 : :
552 [ + + ]: 16 : if (!is_array) {
553 : 6 : gjs_throw(context,
554 : : "Call to %s (%s.%s) returned unexpected value, expecting "
555 : : "an Array",
556 : 6 : gjs_debug_callable(callable()).c_str(), m_info.ns(),
557 : : m_info.name());
558 : 3 : return false;
559 : : }
560 : :
561 : 13 : JS::RootedValue elem(context);
562 : 13 : JS::RootedObject out_array(context, rval.toObjectOrNull());
563 : 13 : gsize elem_idx = 0;
564 : : /* more than one of a return value or an out argument.
565 : : * Should be an array of output values. */
566 : :
567 [ + + ]: 13 : if (!ret_type_is_void) {
568 : : GIArgument argument;
569 : 10 : GITransfer transfer = g_callable_info_get_caller_owns(m_info);
570 : :
571 [ - + ]: 10 : if (!JS_GetElement(context, out_array, elem_idx, &elem))
572 : 0 : return false;
573 : :
574 [ - + ]: 10 : if (!gjs_value_to_gi_argument(context, elem, ret_type, "callback",
575 : : GJS_ARGUMENT_RETURN_VALUE, transfer,
576 : : GjsArgumentFlags::MAY_BE_NULL,
577 : : &argument))
578 : 0 : return false;
579 : :
580 [ + - + + ]: 10 : if ((ret_tag == GI_TYPE_TAG_FILENAME ||
581 [ + - ]: 2 : ret_tag == GI_TYPE_TAG_UTF8) &&
582 : : transfer == GI_TRANSFER_NOTHING) {
583 : : // We duplicated the string so not to leak we need to both
584 : : // ensure that the string is bound to the object lifetime or
585 : : // created once
586 [ + - ]: 2 : if (gobject) {
587 : 2 : ObjectInstance::associate_string(
588 : : gobject, gjs_arg_get<char*>(&argument));
589 : : } else {
590 : 0 : GjsAutoChar str = gjs_arg_steal<char*>(&argument);
591 : 0 : gjs_arg_set<const char*>(&argument, g_intern_string(str));
592 : 0 : }
593 : : }
594 : :
595 : 10 : set_return_ffi_arg_from_gi_argument(ret_type, result, &argument);
596 : :
597 : 10 : elem_idx++;
598 : : }
599 : :
600 [ + + ]: 40 : for (int i = 0; i < n_args; i++) {
601 : : GIArgInfo arg_info;
602 : 27 : g_callable_info_load_arg(m_info, i, &arg_info);
603 [ + + ]: 27 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
604 : 8 : continue;
605 : :
606 [ - + ]: 19 : if (!JS_GetElement(context, out_array, elem_idx, &elem))
607 : 0 : return false;
608 : :
609 [ - + ]: 19 : if (!gjs_value_to_callback_out_arg(
610 : : context, elem, &arg_info,
611 : : get_argument_for_arg_info(&arg_info, args,
612 : : i + c_args_offset)))
613 : 0 : return false;
614 : :
615 : 19 : elem_idx++;
616 : : }
617 [ + - + - ]: 13 : }
618 : :
619 [ + + ]: 434 : if (!in_args_to_cleanup)
620 : 430 : return true;
621 : :
622 [ + + ]: 10 : for (int i = 0; i < n_args; i++) {
623 : : GIArgInfo arg_info;
624 : 6 : g_callable_info_load_arg(m_info, i, &arg_info);
625 : 6 : GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info);
626 : :
627 [ - + ]: 6 : if (transfer == GI_TRANSFER_NOTHING)
628 : 6 : continue;
629 : :
630 [ + - ]: 6 : if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN)
631 : 6 : continue;
632 : :
633 : 0 : GIArgument* arg = args[i + c_args_offset];
634 [ # # ]: 0 : if (m_scope == GI_SCOPE_TYPE_CALL) {
635 : : GITypeInfo type_info;
636 : 0 : g_arg_info_load_type(&arg_info, &type_info);
637 : :
638 [ # # ]: 0 : if (!gjs_gi_argument_release(context, transfer, &type_info, arg))
639 : 0 : return false;
640 : :
641 : 0 : continue;
642 : 0 : }
643 : :
644 : : struct InvalidateData {
645 : : GIArgInfo arg_info;
646 : : GIArgument arg;
647 : : };
648 : :
649 : 0 : auto* data = new InvalidateData({arg_info, *arg});
650 : 0 : g_closure_add_invalidate_notifier(
651 : 0 : this, data, [](void* invalidate_data, GClosure* c) {
652 : 0 : auto* self = static_cast<GjsCallbackTrampoline*>(c);
653 : : std::unique_ptr<InvalidateData> data(
654 : 0 : static_cast<InvalidateData*>(invalidate_data));
655 : : GITransfer transfer =
656 : 0 : g_arg_info_get_ownership_transfer(&data->arg_info);
657 : :
658 : : GITypeInfo type_info;
659 : 0 : g_arg_info_load_type(&data->arg_info, &type_info);
660 [ # # ]: 0 : if (!gjs_gi_argument_release(self->context(), transfer,
661 : 0 : &type_info, &data->arg)) {
662 : 0 : gjs_throw(self->context(),
663 : : "Impossible to release closure argument '%s'",
664 : 0 : g_base_info_get_name(&data->arg_info));
665 : : }
666 : 0 : });
667 : : }
668 : :
669 : 4 : return true;
670 : 459 : }
671 : :
672 : 273 : GjsCallbackTrampoline* GjsCallbackTrampoline::create(
673 : : JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info,
674 : : GIScopeType scope, bool has_scope_object, bool is_vfunc) {
675 : 273 : g_assert(JS::IsCallable(callable) &&
676 : : "tried to create a callback trampoline for a non-callable object");
677 : :
678 : : auto* trampoline = new GjsCallbackTrampoline(
679 : 273 : cx, callable, callable_info, scope, has_scope_object, is_vfunc);
680 : :
681 [ + + ]: 273 : if (!trampoline->initialize()) {
682 : 1 : g_closure_unref(trampoline);
683 : 1 : return nullptr;
684 : : }
685 : :
686 : 272 : return trampoline;
687 : : }
688 : :
689 : : decltype(GjsCallbackTrampoline::s_forever_closure_list)
690 : : GjsCallbackTrampoline::s_forever_closure_list;
691 : :
692 : 273 : GjsCallbackTrampoline::GjsCallbackTrampoline(
693 : : JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info,
694 : 273 : GIScopeType scope, bool has_scope_object, bool is_vfunc)
695 : : // The rooting rule is:
696 : : // - notify callbacks in GObject methods are traced from the scope object
697 : : // - async and call callbacks, and other notify callbacks, are rooted
698 : : // - vfuncs are traced from the GObject prototype
699 : : : Closure(cx, callable,
700 [ + + + + ]: 273 : scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object,
701 : : g_base_info_get_name(callable_info)),
702 : 273 : m_info(callable_info, GjsAutoTakeOwnership()),
703 : 273 : m_param_types(std::make_unique<GjsParamType[]>(
704 : 273 : g_callable_info_get_n_args(callable_info))),
705 : 273 : m_scope(scope),
706 : 819 : m_is_vfunc(is_vfunc) {
707 : 273 : add_finalize_notifier<GjsCallbackTrampoline>();
708 : 273 : }
709 : :
710 : 257 : GjsCallbackTrampoline::~GjsCallbackTrampoline() {
711 [ + - + + : 257 : if (m_info && m_closure) {
+ + ]
712 : : #if GI_CHECK_VERSION(1, 71, 0)
713 : 256 : g_callable_info_destroy_closure(m_info, m_closure);
714 : : #else
715 : : g_callable_info_free_closure(m_info, m_closure);
716 : : #endif
717 : : }
718 : 257 : }
719 : :
720 : 1 : void GjsCallbackTrampoline::mark_forever() {
721 : 1 : s_forever_closure_list.emplace_back(this, GjsAutoTakeOwnership{});
722 : 1 : }
723 : :
724 : 233 : void GjsCallbackTrampoline::prepare_shutdown() {
725 : 233 : s_forever_closure_list.clear();
726 : 233 : }
727 : :
728 : 272 : ffi_closure* GjsCallbackTrampoline::create_closure() {
729 : 464 : auto callback = [](ffi_cif*, void* result, void** ffi_args, void* data) {
730 : 464 : auto** args = reinterpret_cast<GIArgument**>(ffi_args);
731 : 464 : g_assert(data && "Trampoline data is not set");
732 : : Gjs::Closure::Ptr trampoline(static_cast<GjsCallbackTrampoline*>(data),
733 : 464 : GjsAutoTakeOwnership());
734 : :
735 : 464 : trampoline.as<GjsCallbackTrampoline>()->callback_closure(args, result);
736 : 462 : };
737 : :
738 : : #if GI_CHECK_VERSION(1, 71, 0)
739 : 272 : return g_callable_info_create_closure(m_info, &m_cif, callback, this);
740 : : #else
741 : : return g_callable_info_prepare_closure(m_info, &m_cif, callback, this);
742 : : #endif
743 : : }
744 : :
745 : 273 : bool GjsCallbackTrampoline::initialize() {
746 : 273 : g_assert(is_valid());
747 : 273 : g_assert(!m_closure);
748 : :
749 : : /* Analyze param types and directions, similarly to
750 : : * init_cached_function_data */
751 : 273 : int n_param_types = g_callable_info_get_n_args(m_info);
752 [ + + ]: 861 : for (int i = 0; i < n_param_types; i++) {
753 : : GIDirection direction;
754 : : GIArgInfo arg_info;
755 : : GITypeInfo type_info;
756 : : GITypeTag type_tag;
757 : :
758 [ + + ]: 589 : if (m_param_types[i] == PARAM_SKIPPED)
759 : 43 : continue;
760 : :
761 : 588 : g_callable_info_load_arg(m_info, i, &arg_info);
762 : 588 : g_arg_info_load_type(&arg_info, &type_info);
763 : :
764 : 588 : direction = g_arg_info_get_direction(&arg_info);
765 : 588 : type_tag = g_type_info_get_tag(&type_info);
766 : :
767 [ + + ]: 588 : if (direction != GI_DIRECTION_IN) {
768 : : /* INOUT and OUT arguments are handled differently. */
769 : 42 : continue;
770 : : }
771 : :
772 [ + + ]: 546 : if (type_tag == GI_TYPE_TAG_INTERFACE) {
773 : : GjsAutoBaseInfo interface_info =
774 : 308 : g_type_info_get_interface(&type_info);
775 : 308 : GIInfoType interface_type = interface_info.type();
776 [ + + ]: 308 : if (interface_type == GI_INFO_TYPE_CALLBACK) {
777 : 2 : gjs_throw(context(),
778 : : "%s %s accepts another callback as a parameter. This "
779 : : "is not supported",
780 [ + - ]: 1 : m_is_vfunc ? "VFunc" : "Callback", m_info.name());
781 : 1 : return false;
782 : : }
783 [ + + + + ]: 546 : } else if (type_tag == GI_TYPE_TAG_ARRAY) {
784 [ + - ]: 1 : if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
785 : 1 : int array_length_pos = g_type_info_get_array_length(&type_info);
786 : :
787 [ - + ]: 1 : if (array_length_pos < 0)
788 : 0 : continue;
789 : :
790 [ + - ]: 1 : if (array_length_pos < n_param_types) {
791 : : GIArgInfo length_arg_info;
792 : :
793 : 1 : g_callable_info_load_arg(m_info, array_length_pos,
794 : : &length_arg_info);
795 [ - + ]: 1 : if (g_arg_info_get_direction(&length_arg_info) != direction) {
796 : 0 : gjs_throw(context(),
797 : : "%s %s has an array with different-direction "
798 : : "length argument. This is not supported",
799 [ # # ]: 0 : m_is_vfunc ? "VFunc" : "Callback",
800 : : m_info.name());
801 : 0 : return false;
802 : : }
803 : :
804 : 1 : m_param_types[array_length_pos] = PARAM_SKIPPED;
805 : 1 : m_param_types[i] = PARAM_ARRAY;
806 : : }
807 : : }
808 : : }
809 : : }
810 : :
811 : 272 : m_closure = create_closure();
812 : 272 : return true;
813 : : }
814 : :
815 : : // Intended for error messages
816 : 50739 : std::string Gjs::Function::format_name() {
817 : 50739 : bool is_method = g_callable_info_is_method(m_info);
818 [ + + ]: 50739 : std::string retval = is_method ? "method" : "function";
819 : 50739 : retval += ' ';
820 : 50739 : retval += m_info.ns();
821 : 50739 : retval += '.';
822 [ + + ]: 50739 : if (is_method) {
823 : 28273 : retval += g_base_info_get_name(g_base_info_get_container(m_info));
824 : 28273 : retval += '.';
825 : : }
826 : 50739 : retval += m_info.name();
827 : 50739 : return retval;
828 : : }
829 : :
830 : : namespace Gjs {
831 : :
832 : 50725 : static void* get_return_ffi_pointer_from_gi_argument(
833 : : GITypeTag tag, GITypeInfo* return_type, GIFFIReturnValue* return_value) {
834 [ + + + + : 50725 : if (return_type && g_type_info_is_pointer(return_type))
+ + ]
835 : 14248 : return &gjs_arg_member<void*>(return_value);
836 [ + + + + : 36477 : switch (tag) {
+ + + + -
+ + + + +
+ ]
837 : 21784 : case GI_TYPE_TAG_VOID:
838 : 21784 : return nullptr;
839 : 2 : case GI_TYPE_TAG_INT8:
840 : 2 : return &gjs_arg_member<int8_t>(return_value);
841 : 4 : case GI_TYPE_TAG_INT16:
842 : 4 : return &gjs_arg_member<int16_t>(return_value);
843 : 79 : case GI_TYPE_TAG_INT32:
844 : 79 : return &gjs_arg_member<int32_t>(return_value);
845 : 13 : case GI_TYPE_TAG_UINT8:
846 : 13 : return &gjs_arg_member<uint8_t>(return_value);
847 : 2 : case GI_TYPE_TAG_UINT16:
848 : 2 : return &gjs_arg_member<uint16_t>(return_value);
849 : 8813 : case GI_TYPE_TAG_UINT32:
850 : 8813 : return &gjs_arg_member<uint32_t>(return_value);
851 : 1026 : case GI_TYPE_TAG_BOOLEAN:
852 : 1026 : return &gjs_arg_member<gboolean, GI_TYPE_TAG_BOOLEAN>(return_value);
853 : 0 : case GI_TYPE_TAG_UNICHAR:
854 : 0 : return &gjs_arg_member<uint32_t>(return_value);
855 : 30 : case GI_TYPE_TAG_INT64:
856 : 30 : return &gjs_arg_member<int64_t>(return_value);
857 : 623 : case GI_TYPE_TAG_UINT64:
858 : 623 : return &gjs_arg_member<uint64_t>(return_value);
859 : 1 : case GI_TYPE_TAG_FLOAT:
860 : 1 : return &gjs_arg_member<float>(return_value);
861 : 7 : case GI_TYPE_TAG_DOUBLE:
862 : 7 : return &gjs_arg_member<double>(return_value);
863 : 2224 : case GI_TYPE_TAG_INTERFACE: {
864 [ - + ]: 2224 : if (!return_type)
865 : 0 : return nullptr;
866 : :
867 : 2224 : GjsAutoBaseInfo info = g_type_info_get_interface(return_type);
868 : :
869 [ + - ]: 2224 : switch (info.type()) {
870 : 2224 : case GI_INFO_TYPE_ENUM:
871 : : case GI_INFO_TYPE_FLAGS:
872 : 2224 : return &gjs_arg_member<int, GI_TYPE_TAG_INTERFACE>(
873 : 2224 : return_value);
874 : 0 : default:
875 : 0 : return &gjs_arg_member<void*>(return_value);
876 : : }
877 : : break;
878 : 2224 : }
879 : 1869 : default:
880 : 1869 : return &gjs_arg_member<void*>(return_value);
881 : : }
882 : : }
883 : :
884 : : // This function can be called in two different ways. You can either use it to
885 : : // create JavaScript objects by calling it without @r_value, or you can decide
886 : : // to keep the return values in GIArgument format by providing a @r_value
887 : : // argument.
888 : 50738 : bool Function::invoke(JSContext* context, const JS::CallArgs& args,
889 : : JS::HandleObject this_obj /* = nullptr */,
890 : : GIArgument* r_value /* = nullptr */) {
891 : 50738 : g_assert((args.isConstructing() || !this_obj) &&
892 : : "If not a constructor, then pass the 'this' object via CallArgs");
893 : :
894 : : void* return_value_p; // will point inside the return GIArgument union
895 : : GIFFIReturnValue return_value;
896 : :
897 : 50738 : unsigned ffi_argc = m_invoker.cif.nargs;
898 : 50738 : GjsFunctionCallState state(context, m_info);
899 : :
900 [ - + ]: 50738 : if (state.gi_argc > Argument::MAX_ARGS) {
901 : 0 : gjs_throw(context, "Function %s has too many arguments",
902 : 0 : format_name().c_str());
903 : 0 : return false;
904 : : }
905 : :
906 : : // ffi_argc is the number of arguments that the underlying C function takes.
907 : : // state.gi_argc is the number of arguments the GICallableInfo describes
908 : : // (which does not include "this" or GError**). m_js_in_argc is the number
909 : : // of arguments we expect the JS function to take (which does not include
910 : : // PARAM_SKIPPED args).
911 : : // args.length() is the number of arguments that were actually passed.
912 [ + + ]: 50738 : if (args.length() > m_js_in_argc) {
913 : 2 : if (!JS::WarnUTF8(context,
914 : : "Too many arguments to %s: expected %u, got %u",
915 [ - + ]: 2 : format_name().c_str(), m_js_in_argc, args.length()))
916 : 0 : return false;
917 [ + + ]: 50737 : } else if (args.length() < m_js_in_argc) {
918 : 2 : args.reportMoreArgsNeeded(context, format_name().c_str(), m_js_in_argc,
919 : : args.length());
920 : 2 : return false;
921 : : }
922 : :
923 : : // These arrays hold argument pointers.
924 : : // - state.in_cvalue(): C values which are passed on input (in or inout)
925 : : // - state.out_cvalue(): C values which are returned as arguments (out or
926 : : // inout)
927 : : // - state.inout_original_cvalue(): For the special case of (inout) args,
928 : : // we need to keep track of the original values we passed into the
929 : : // function, in case we need to free it.
930 : : // - ffi_arg_pointers: For passing data to FFI, we need to create another
931 : : // layer of indirection; this array is a pointer to an element in
932 : : // state.in_cvalue() or state.out_cvalue().
933 : : // - return_value: The actual return value of the C function, i.e. not an
934 : : // (out) param
935 : : //
936 : : // The 3 GIArgument arrays are indexed by the GI argument index.
937 : : // ffi_arg_pointers, on the other hand, represents the actual C arguments,
938 : : // in the way ffi expects them.
939 : :
940 : 50736 : auto ffi_arg_pointers = std::make_unique<void*[]>(ffi_argc);
941 : :
942 : 50736 : int gi_arg_pos = 0; // index into GIArgument array
943 : 50736 : unsigned ffi_arg_pos = 0; // index into ffi_arg_pointers
944 : 50736 : unsigned js_arg_pos = 0; // index into args
945 : :
946 : 50736 : JS::RootedObject obj(context, this_obj);
947 [ + - - + : 50736 : if (!args.isConstructing() && !args.computeThis(context, &obj))
- + ]
948 : 0 : return false;
949 : :
950 : 50736 : std::string dynamicString("(unknown)");
951 : :
952 [ + + ]: 50736 : if (state.is_method) {
953 : 28272 : GIArgument* in_value = state.instance();
954 : 28272 : JS::RootedValue in_js_value(context, JS::ObjectValue(*obj));
955 : :
956 [ - + ]: 28272 : if (!m_arguments.instance()->in(context, &state, in_value, in_js_value))
957 : 0 : return false;
958 : :
959 : 28272 : ffi_arg_pointers[ffi_arg_pos] = in_value;
960 : 28272 : ++ffi_arg_pos;
961 : :
962 : : // Callback lifetimes will be attached to the instance object if it is
963 : : // a GObject or GInterface
964 : 28272 : GType gtype = m_arguments.instance_type();
965 [ + + ]: 28272 : if (gtype != G_TYPE_NONE) {
966 [ + + + + : 54737 : if (g_type_is_a(gtype, G_TYPE_OBJECT) ||
+ - + + ]
967 [ + + ]: 26675 : g_type_is_a(gtype, G_TYPE_INTERFACE))
968 : 1390 : state.instance_object = obj;
969 : :
970 [ + + + + : 28062 : if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
+ + ]
971 : 1387 : auto* o = ObjectBase::for_js(context, obj);
972 : 1387 : dynamicString = o->format_name();
973 : : }
974 : : }
975 [ + - ]: 28272 : }
976 : :
977 : 50736 : dynamicString += '.';
978 : 50736 : dynamicString += format_name();
979 : 50736 : AutoProfilerLabel label(context, "", dynamicString.c_str());
980 : :
981 : 50736 : g_assert(ffi_arg_pos + state.gi_argc <
982 : : std::numeric_limits<decltype(state.processed_c_args)>::max());
983 : :
984 : 50736 : state.processed_c_args = ffi_arg_pos;
985 [ + + ]: 106497 : for (gi_arg_pos = 0; gi_arg_pos < state.gi_argc;
986 : 55761 : gi_arg_pos++, ffi_arg_pos++) {
987 : 55772 : GIArgument* in_value = &state.in_cvalue(gi_arg_pos);
988 : 55772 : Argument* gjs_arg = m_arguments.argument(gi_arg_pos);
989 : :
990 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
991 : : "Marshalling argument '%s' in, %d/%d GI args, %u/%u "
992 : : "C args, %u/%u JS args",
993 : : gjs_arg ? gjs_arg->arg_name() : "<unknown>",
994 : : gi_arg_pos, state.gi_argc, ffi_arg_pos, ffi_argc,
995 : : js_arg_pos, args.length());
996 : :
997 : 55772 : ffi_arg_pointers[ffi_arg_pos] = in_value;
998 : :
999 [ - + ]: 55772 : if (!gjs_arg) {
1000 : : GIArgInfo arg_info;
1001 : 0 : g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info);
1002 : 0 : gjs_throw(context,
1003 : : "Error invoking %s: impossible to determine what to pass "
1004 : : "to the '%s' argument. It may be that the function is "
1005 : : "unsupported, or there may be a bug in its annotations.",
1006 : 0 : format_name().c_str(), g_base_info_get_name(&arg_info));
1007 : 0 : state.failed = true;
1008 : 0 : break;
1009 : : }
1010 : :
1011 : 55772 : JS::RootedValue js_in_arg(context);
1012 [ + + ]: 55772 : if (js_arg_pos < args.length())
1013 : 54550 : js_in_arg = args[js_arg_pos];
1014 : :
1015 [ + + ]: 55772 : if (!gjs_arg->in(context, &state, in_value, js_in_arg)) {
1016 : 11 : state.failed = true;
1017 : 11 : break;
1018 : : }
1019 : :
1020 [ + + ]: 55761 : if (!gjs_arg->skip_in())
1021 : 54450 : js_arg_pos++;
1022 : :
1023 : 55761 : state.processed_c_args++;
1024 [ + + ]: 55772 : }
1025 : :
1026 : : // This pointer needs to exist on the stack across the ffi_call() call
1027 : 50736 : GError** errorp = &state.local_error;
1028 : :
1029 : : /* Did argument conversion fail? In that case, skip invocation and jump to release
1030 : : * processing. */
1031 [ + + ]: 50736 : if (state.failed)
1032 : 11 : return finish_invoke(context, args, &state, r_value);
1033 : :
1034 [ + + ]: 50725 : if (state.can_throw_gerror) {
1035 : 321 : g_assert(ffi_arg_pos < ffi_argc && "GError** argument number mismatch");
1036 : 321 : ffi_arg_pointers[ffi_arg_pos] = &errorp;
1037 : 321 : ffi_arg_pos++;
1038 : :
1039 : : /* don't update state.processed_c_args as we deal with local_error
1040 : : * separately */
1041 : : }
1042 : :
1043 : 50725 : g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc);
1044 : 50725 : g_assert_cmpuint(gi_arg_pos, ==, state.gi_argc);
1045 : :
1046 : 50725 : GITypeTag return_tag = m_arguments.return_tag();
1047 : 50725 : GITypeInfo* return_type = m_arguments.return_type();
1048 : 50725 : return_value_p = get_return_ffi_pointer_from_gi_argument(
1049 : : return_tag, return_type, &return_value);
1050 : 50725 : ffi_call(&m_invoker.cif, FFI_FN(m_invoker.native_address), return_value_p,
1051 : : ffi_arg_pointers.get());
1052 : :
1053 : : /* Return value and out arguments are valid only if invocation doesn't
1054 : : * return error. In arguments need to be released always.
1055 : : */
1056 [ + - ]: 50723 : if (!r_value)
1057 : 50723 : args.rval().setUndefined();
1058 : :
1059 [ + + ]: 50723 : if (return_type) {
1060 : 17709 : gi_type_info_extract_ffi_return_value(return_type, &return_value,
1061 : : state.return_value());
1062 [ + + ]: 33014 : } else if (return_tag != GI_TYPE_TAG_VOID) {
1063 : 11232 : g_assert(GI_TYPE_TAG_IS_BASIC(return_tag));
1064 : 11232 : gi_type_tag_extract_ffi_return_value(return_tag, GI_INFO_TYPE_INVALID,
1065 : : &return_value,
1066 : : state.return_value());
1067 : : }
1068 : :
1069 : : // Process out arguments and return values. This loop is skipped if we fail
1070 : : // the type conversion above, or if state.did_throw_gerror is true.
1071 : 50723 : js_arg_pos = 0;
1072 [ + + ]: 157202 : for (gi_arg_pos = -1; gi_arg_pos < state.gi_argc; gi_arg_pos++) {
1073 : : Argument* gjs_arg;
1074 : : GIArgument* out_value;
1075 : :
1076 [ + + ]: 106479 : if (gi_arg_pos == -1) {
1077 : 50723 : out_value = state.return_value();
1078 : 50723 : gjs_arg = m_arguments.return_value();
1079 : : } else {
1080 : 55756 : out_value = &state.out_cvalue(gi_arg_pos);
1081 : 55756 : gjs_arg = m_arguments.argument(gi_arg_pos);
1082 : : }
1083 : :
1084 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
1085 : : "Marshalling argument '%s' out, %d/%d GI args",
1086 : : gjs_arg ? gjs_arg->arg_name() : "<unknown>",
1087 : : gi_arg_pos, state.gi_argc);
1088 : :
1089 : 106479 : JS::RootedValue js_out_arg(context);
1090 [ + - ]: 106479 : if (!r_value) {
1091 [ + + - + ]: 106479 : if (!gjs_arg && gi_arg_pos >= 0) {
1092 : : GIArgInfo arg_info;
1093 : 0 : g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info);
1094 : 0 : gjs_throw(
1095 : : context,
1096 : : "Error invoking %s.%s: impossible to determine what "
1097 : : "to pass to the out '%s' argument. It may be that the "
1098 : : "function is unsupported, or there may be a bug in "
1099 : : "its annotations.",
1100 : : m_info.ns(), m_info.name(),
1101 : : g_base_info_get_name(&arg_info));
1102 : 0 : state.failed = true;
1103 : 0 : break;
1104 : : }
1105 : :
1106 [ + + ]: 191176 : if (gjs_arg &&
1107 [ - + - + ]: 191176 : !gjs_arg->out(context, &state, out_value, &js_out_arg)) {
1108 : 0 : state.failed = true;
1109 : 0 : break;
1110 : : }
1111 : : }
1112 : :
1113 [ + + + + : 106479 : if (gjs_arg && !gjs_arg->skip_out()) {
+ + ]
1114 [ + - ]: 29689 : if (!r_value) {
1115 [ - + ]: 29689 : if (!state.return_values.append(js_out_arg)) {
1116 : 0 : JS_ReportOutOfMemory(context);
1117 : 0 : state.failed = true;
1118 : 0 : break;
1119 : : }
1120 : : }
1121 : 29689 : js_arg_pos++;
1122 : : }
1123 [ + - ]: 106479 : }
1124 : :
1125 : 50723 : g_assert(state.failed || state.did_throw_gerror() ||
1126 : : js_arg_pos == m_js_out_argc);
1127 : :
1128 : : // If we failed before calling the function, or if the function threw an
1129 : : // exception, then any GI_TRANSFER_EVERYTHING or GI_TRANSFER_CONTAINER
1130 : : // in-parameters were not transferred. Treat them as GI_TRANSFER_NOTHING so
1131 : : // that they are freed.
1132 : 50723 : return finish_invoke(context, args, &state, r_value);
1133 : 50736 : }
1134 : :
1135 : 50734 : bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args,
1136 : : GjsFunctionCallState* state,
1137 : : GIArgument* r_value /* = nullptr */) {
1138 : : // In this loop we use ffi_arg_pos just to ensure we don't release stuff
1139 : : // we haven't allocated yet, if we failed in type conversion above.
1140 : : // If we start from -1 (the return value), we need to process 1 more than
1141 : : // state.processed_c_args.
1142 : : // If we start from -2 (the instance parameter), we need to process 2 more
1143 : 50734 : unsigned ffi_arg_pos = state->first_arg_offset() - 1;
1144 : 50734 : unsigned ffi_arg_max = state->last_processed_index();
1145 : 50734 : bool postinvoke_release_failed = false;
1146 : 50734 : for (int gi_arg_pos = -(state->first_arg_offset());
1147 [ + + + + ]: 185499 : gi_arg_pos < state->gi_argc && ffi_arg_pos < ffi_arg_max;
1148 : 134765 : gi_arg_pos++, ffi_arg_pos++) {
1149 : : Argument* gjs_arg;
1150 : 134765 : GIArgument* in_value = nullptr;
1151 : 134765 : GIArgument* out_value = nullptr;
1152 : :
1153 [ + + ]: 134765 : if (gi_arg_pos == -2) {
1154 : 28270 : in_value = state->instance();
1155 : 28270 : gjs_arg = m_arguments.instance();
1156 [ + + ]: 106495 : } else if (gi_arg_pos == -1) {
1157 : 50734 : out_value = state->return_value();
1158 : 50734 : gjs_arg = m_arguments.return_value();
1159 : : } else {
1160 : 55761 : in_value = &state->in_cvalue(gi_arg_pos);
1161 : 55761 : out_value = &state->out_cvalue(gi_arg_pos);
1162 : 55761 : gjs_arg = m_arguments.argument(gi_arg_pos);
1163 : : }
1164 : :
1165 [ + + ]: 134765 : if (!gjs_arg)
1166 : 21786 : continue;
1167 : :
1168 : : gjs_debug_marshal(
1169 : : GJS_DEBUG_GFUNCTION,
1170 : : "Releasing argument '%s', %d/%d GI args, %u/%u C args",
1171 : : gjs_arg->arg_name(), gi_arg_pos, state->gi_argc, ffi_arg_pos,
1172 : : state->processed_c_args);
1173 : :
1174 : : // Only process in or inout arguments if we failed, the rest is garbage
1175 [ + + + + : 112979 : if (state->failed && gjs_arg->skip_in())
+ + ]
1176 : 9 : continue;
1177 : :
1178 : : // Save the return GIArgument if it was requested
1179 [ - + - - ]: 112970 : if (r_value && gi_arg_pos == -1) {
1180 : 0 : *r_value = *out_value;
1181 : 0 : continue;
1182 : : }
1183 : :
1184 [ - + ]: 112970 : if (!gjs_arg->release(cx, state, in_value, out_value)) {
1185 : 0 : postinvoke_release_failed = true;
1186 : : // continue with the release even if we fail, to avoid leaks
1187 : : }
1188 : : }
1189 : :
1190 [ - + ]: 50734 : if (postinvoke_release_failed)
1191 : 0 : state->failed = true;
1192 : :
1193 : 50734 : g_assert(ffi_arg_pos == state->last_processed_index());
1194 : :
1195 [ + - + + : 50734 : if (!r_value && m_js_out_argc > 0 && state->call_completed()) {
+ + + + ]
1196 : : // If we have one return value or out arg, return that item on its
1197 : : // own, otherwise return a JavaScript array with [return value,
1198 : : // out arg 1, out arg 2, ...]
1199 [ + + ]: 29055 : if (m_js_out_argc == 1) {
1200 : 28476 : args.rval().set(state->return_values[0]);
1201 : : } else {
1202 : 579 : JSObject* array = JS::NewArrayObject(cx, state->return_values);
1203 [ - + ]: 579 : if (!array) {
1204 : 0 : state->failed = true;
1205 : : } else {
1206 : 579 : args.rval().setObject(*array);
1207 : : }
1208 : : }
1209 : : }
1210 : :
1211 [ + + + + : 50734 : if (!state->failed && state->did_throw_gerror()) {
+ + ]
1212 : 37 : return gjs_throw_gerror(cx, state->local_error.release());
1213 [ + + ]: 50697 : } else if (state->failed) {
1214 : 11 : return false;
1215 : : } else {
1216 : 50686 : return true;
1217 : : }
1218 : : }
1219 : :
1220 : 50738 : bool Function::call(JSContext* context, unsigned js_argc, JS::Value* vp) {
1221 : 50738 : JS::CallArgs js_argv = JS::CallArgsFromVp(js_argc, vp);
1222 : 50738 : JS::RootedObject callee(context, &js_argv.callee());
1223 : :
1224 : : Function* priv;
1225 [ - + ]: 50738 : if (!Function::for_js_typecheck(context, callee, &priv, &js_argv))
1226 : 0 : return false;
1227 : :
1228 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p",
1229 : : callee.get(), priv);
1230 : :
1231 : 50738 : g_assert(priv);
1232 : 50738 : return priv->invoke(context, js_argv);
1233 : 50736 : }
1234 : :
1235 : 9881 : Function::~Function() {
1236 : 9881 : g_function_invoker_destroy(&m_invoker);
1237 : 9881 : GJS_DEC_COUNTER(function);
1238 : 9881 : }
1239 : :
1240 : 9881 : void Function::finalize_impl(JS::GCContext*, Function* priv) {
1241 : 9881 : g_assert(priv);
1242 [ + - ]: 9881 : delete priv;
1243 : 9881 : }
1244 : :
1245 : 13 : bool Function::get_length(JSContext* cx, unsigned argc, JS::Value* vp) {
1246 [ - + ]: 13 : GJS_GET_THIS(cx, argc, vp, args, this_obj);
1247 : : Function* priv;
1248 [ - + ]: 13 : if (!Function::for_js_instance(cx, this_obj, &priv, &args))
1249 : 0 : return false;
1250 : 13 : args.rval().setInt32(priv->m_js_in_argc);
1251 : 13 : return true;
1252 : 13 : }
1253 : :
1254 : 0 : bool Function::to_string(JSContext* context, unsigned argc, JS::Value* vp) {
1255 [ # # # # ]: 0 : GJS_CHECK_WRAPPER_PRIV(context, argc, vp, rec, this_obj, Function, priv);
1256 : 0 : return priv->to_string_impl(context, rec.rval());
1257 : 0 : }
1258 : :
1259 : 0 : bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) {
1260 : : int i, n_jsargs;
1261 : :
1262 : 0 : int n_args = g_callable_info_get_n_args(m_info);
1263 : 0 : n_jsargs = 0;
1264 : 0 : std::string arg_names;
1265 [ # # ]: 0 : for (i = 0; i < n_args; i++) {
1266 : 0 : Argument* gjs_arg = m_arguments.argument(i);
1267 [ # # # # : 0 : if (!gjs_arg || gjs_arg->skip_in())
# # ]
1268 : 0 : continue;
1269 : :
1270 [ # # ]: 0 : if (n_jsargs > 0)
1271 : 0 : arg_names += ", ";
1272 : :
1273 : 0 : n_jsargs++;
1274 : 0 : arg_names += gjs_arg->arg_name();
1275 : : }
1276 : :
1277 : 0 : GjsAutoChar descr;
1278 [ # # ]: 0 : if (m_info.type() == GI_INFO_TYPE_FUNCTION) {
1279 : : descr = g_strdup_printf(
1280 : : "%s(%s) {\n\t/* wrapper for native symbol %s() */\n}",
1281 : 0 : format_name().c_str(), arg_names.c_str(),
1282 : 0 : g_function_info_get_symbol(m_info));
1283 : : } else {
1284 : : descr =
1285 : : g_strdup_printf("%s(%s) {\n\t/* wrapper for native symbol */\n}",
1286 : 0 : format_name().c_str(), arg_names.c_str());
1287 : : }
1288 : :
1289 : 0 : return gjs_string_from_utf8(cx, descr, rval);
1290 : 0 : }
1291 : :
1292 : : const JSClassOps Function::class_ops = {
1293 : : nullptr, // addProperty
1294 : : nullptr, // deleteProperty
1295 : : nullptr, // enumerate
1296 : : nullptr, // newEnumerate
1297 : : nullptr, // resolve
1298 : : nullptr, // mayResolve
1299 : : &Function::finalize,
1300 : : &Function::call,
1301 : : };
1302 : :
1303 : : const JSPropertySpec Function::proto_props[] = {
1304 : : JS_PSG("length", &Function::get_length, JSPROP_PERMANENT),
1305 : : JS_STRING_SYM_PS(toStringTag, "GIRepositoryFunction", JSPROP_READONLY),
1306 : : JS_PS_END};
1307 : :
1308 : : /* The original Function.prototype.toString complains when
1309 : : given a GIRepository function as an argument */
1310 : : // clang-format off
1311 : : const JSFunctionSpec Function::proto_funcs[] = {
1312 : : JS_FN("toString", &Function::to_string, 0, 0),
1313 : : JS_FS_END};
1314 : : // clang-format on
1315 : :
1316 : 10058 : bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) {
1317 : : guint8 i;
1318 : 10058 : GjsAutoError error;
1319 : :
1320 [ + + ]: 10058 : if (m_info.type() == GI_INFO_TYPE_FUNCTION) {
1321 [ - + ]: 10057 : if (!g_function_info_prep_invoker(m_info, &m_invoker, &error))
1322 : 0 : return gjs_throw_gerror(context, error);
1323 [ + - ]: 1 : } else if (m_info.type() == GI_INFO_TYPE_VFUNC) {
1324 : 1 : void* addr = g_vfunc_info_get_address(m_info, gtype, &error);
1325 [ - + ]: 1 : if (error) {
1326 [ # # ]: 0 : if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND)
1327 : 0 : return gjs_throw_gerror(context, error);
1328 : :
1329 : 0 : gjs_throw(context, "Virtual function not implemented: %s",
1330 : 0 : error->message);
1331 : 0 : return false;
1332 : : }
1333 : :
1334 [ - + ]: 1 : if (!g_function_invoker_new_for_address(addr, m_info, &m_invoker,
1335 : : &error))
1336 : 0 : return gjs_throw_gerror(context, error);
1337 : : }
1338 : :
1339 : 10058 : uint8_t n_args = g_callable_info_get_n_args(m_info);
1340 : :
1341 [ - + ]: 10058 : if (!m_arguments.initialize(context, m_info))
1342 : 0 : return false;
1343 : :
1344 : 10058 : m_arguments.build_instance(m_info);
1345 : :
1346 : : bool inc_counter;
1347 : 10058 : m_arguments.build_return(m_info, &inc_counter);
1348 : :
1349 [ + + ]: 10058 : if (inc_counter)
1350 : 7973 : m_js_out_argc++;
1351 : :
1352 [ + + ]: 29732 : for (i = 0; i < n_args; i++) {
1353 : 19674 : Argument* gjs_arg = m_arguments.argument(i);
1354 : : GIDirection direction;
1355 : : GIArgInfo arg_info;
1356 : :
1357 [ + + - + : 19674 : if (gjs_arg && (gjs_arg->skip_in() || gjs_arg->skip_out())) {
- - + + ]
1358 : 1502 : continue;
1359 : : }
1360 : :
1361 : 18172 : g_callable_info_load_arg(m_info, i, &arg_info);
1362 : 18172 : direction = g_arg_info_get_direction(&arg_info);
1363 : :
1364 : 18172 : m_arguments.build_arg(i, direction, &arg_info, m_info, &inc_counter);
1365 : :
1366 [ + + ]: 18172 : if (inc_counter) {
1367 [ + + + - ]: 17964 : switch (direction) {
1368 : 61 : case GI_DIRECTION_INOUT:
1369 : 61 : m_js_out_argc++;
1370 : : [[fallthrough]];
1371 : 17305 : case GI_DIRECTION_IN:
1372 : 17305 : m_js_in_argc++;
1373 : 17305 : break;
1374 : 659 : case GI_DIRECTION_OUT:
1375 : 659 : m_js_out_argc++;
1376 : 659 : break;
1377 : 0 : default:
1378 : : g_assert_not_reached();
1379 : : }
1380 : : }
1381 : : }
1382 : :
1383 : 10058 : return true;
1384 : 10058 : }
1385 : :
1386 : 10058 : JSObject* Function::create(JSContext* context, GType gtype,
1387 : : GICallableInfo* info) {
1388 : 10058 : JS::RootedObject proto(context, Function::create_prototype(context));
1389 [ - + ]: 10058 : if (!proto)
1390 : 0 : return nullptr;
1391 : :
1392 : : JS::RootedObject function(
1393 : 10058 : context, JS_NewObjectWithGivenProto(context, &Function::klass, proto));
1394 [ - + ]: 10058 : if (!function) {
1395 : 0 : gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function");
1396 : 0 : return NULL;
1397 : : }
1398 : :
1399 : 10058 : auto* priv = new Function(info);
1400 : :
1401 : 10058 : Function::init_private(function, priv);
1402 : :
1403 : 10058 : debug_lifecycle(function, priv, "Constructor");
1404 : :
1405 [ - + ]: 10058 : if (!priv->init(context, gtype))
1406 : 0 : return nullptr;
1407 : :
1408 : 10058 : return function;
1409 : 10058 : }
1410 : :
1411 : : } // namespace Gjs
1412 : :
1413 : : GJS_JSAPI_RETURN_CONVENTION
1414 : : JSObject*
1415 : 10058 : gjs_define_function(JSContext *context,
1416 : : JS::HandleObject in_object,
1417 : : GType gtype,
1418 : : GICallableInfo *info)
1419 : : {
1420 : : GIInfoType info_type;
1421 : 10058 : std::string name;
1422 : :
1423 : 10058 : info_type = g_base_info_get_type((GIBaseInfo *)info);
1424 : :
1425 : : JS::RootedObject function(context,
1426 : 10058 : Gjs::Function::create(context, gtype, info));
1427 [ - + ]: 10058 : if (!function)
1428 : 0 : return NULL;
1429 : :
1430 [ + + ]: 10058 : if (info_type == GI_INFO_TYPE_FUNCTION) {
1431 : 10057 : name = g_base_info_get_name(info);
1432 [ + - ]: 1 : } else if (info_type == GI_INFO_TYPE_VFUNC) {
1433 : 2 : name = "vfunc_" + std::string(g_base_info_get_name(info));
1434 : : } else {
1435 : : g_assert_not_reached ();
1436 : : }
1437 : :
1438 [ - + ]: 10058 : if (!JS_DefineProperty(context, in_object, name.c_str(), function,
1439 : : GJS_MODULE_PROP_FLAGS)) {
1440 : 0 : gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function");
1441 : 0 : function = NULL;
1442 : : }
1443 : :
1444 : 10058 : return function;
1445 : 10058 : }
1446 : :
1447 : 0 : bool gjs_invoke_constructor_from_c(JSContext* context, GIFunctionInfo* info,
1448 : : JS::HandleObject obj,
1449 : : const JS::CallArgs& args,
1450 : : GIArgument* rvalue) {
1451 : 0 : return Gjs::Function::invoke_constructor_uncached(context, info, obj, args,
1452 : 0 : rvalue);
1453 : : }
|