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/PropertyAndElement.h>
29 : : #include <js/PropertyDescriptor.h> // for JSPROP_PERMANENT
30 : : #include <js/PropertySpec.h>
31 : : #include <js/Realm.h> // for GetRealmFunctionPrototype
32 : : #include <js/RootingAPI.h>
33 : : #include <js/TypeDecls.h>
34 : : #include <js/Value.h>
35 : : #include <js/ValueArray.h>
36 : : #include <js/Warnings.h>
37 : : #include <jsapi.h> // for HandleValueArray
38 : : #include <jsfriendapi.h> // for JS_GetObjectFunction
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 : 10918 : explicit Function(GICallableInfo* info)
81 : 10918 : : m_info(info, GjsAutoTakeOwnership()),
82 : 10918 : m_js_in_argc(0),
83 : 10918 : m_js_out_argc(0),
84 : 10918 : m_invoker({}) {
85 : 10918 : GJS_INC_COUNTER(function);
86 : 10918 : }
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 : 18 : static bool for_js_instance(JSContext* cx, JS::HandleObject obj,
98 : : Function** out, JS::CallArgs* args) {
99 : : Function* priv;
100 [ - + ]: 18 : if (!Function::for_js_typecheck(cx, obj, &priv, args))
101 : 0 : return false;
102 [ - + ]: 18 : if (!priv) {
103 : : // This is the prototype
104 : 0 : gjs_throw(cx, "Impossible on prototype; only on instances");
105 : 0 : return false;
106 : : }
107 : 18 : *out = priv;
108 : 18 : 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 : 62 : static JSObject* inherit_builtin_function(JSContext* cx, JSProtoKey) {
132 : : JS::RootedObject builtin_function_proto(
133 : 62 : cx, JS::GetRealmFunctionPrototype(cx));
134 : 62 : return JS_NewObjectWithGivenProto(cx, nullptr, builtin_function_proto);
135 : 62 : }
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 : 15 : static bool invoke_constructor_uncached(JSContext* cx, GIFunctionInfo* info,
169 : : JS::HandleObject obj,
170 : : const JS::CallArgs& args,
171 : : GIArgument* rvalue) {
172 : 15 : Function function(info);
173 [ - + ]: 15 : if (!function.init(cx))
174 : 0 : return false;
175 : 15 : return function.invoke(cx, args, obj, rvalue);
176 : 15 : }
177 : : };
178 : :
179 : : } // namespace Gjs
180 : :
181 : : template <typename T, GITypeTag TAG = GI_TYPE_TAG_VOID>
182 : 566 : static inline void set_ffi_arg(void* result, GIArgument* value) {
183 : : if constexpr (std::is_integral_v<T> && std::is_signed_v<T>) {
184 : 471 : *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 : 95 : *static_cast<ffi_arg*>(result) =
189 : 95 : gjs_pointer_to_int<ffi_arg>(gjs_arg_get<T, TAG>(value));
190 : : }
191 : 566 : }
192 : :
193 : 566 : 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 [ - - - - : 566 : 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 : 100 : case GI_TYPE_TAG_INT32:
213 : 100 : set_ffi_arg<int32_t>(result, return_value);
214 : 100 : break;
215 : 0 : case GI_TYPE_TAG_UINT32:
216 : 0 : set_ffi_arg<uint32_t>(result, return_value);
217 : 0 : break;
218 : 181 : case GI_TYPE_TAG_BOOLEAN:
219 : 181 : set_ffi_arg<gboolean, GI_TYPE_TAG_BOOLEAN>(result, return_value);
220 : 181 : 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 : 263 : case GI_TYPE_TAG_INTERFACE:
228 : : {
229 : 263 : GjsAutoBaseInfo interface_info(g_type_info_get_interface(ret_type));
230 : 263 : GIInfoType interface_type = interface_info.type();
231 : :
232 [ + + + + ]: 263 : if (interface_type == GI_INFO_TYPE_ENUM ||
233 : : interface_type == GI_INFO_TYPE_FLAGS)
234 : 172 : set_ffi_arg<int, GI_TYPE_TAG_INTERFACE>(result, return_value);
235 : : else
236 : 91 : set_ffi_arg<void*>(result, return_value);
237 : 263 : }
238 : 263 : 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 : 566 : }
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 : 485 : void GjsCallbackTrampoline::callback_closure(GIArgument** args, void* result) {
293 : : GITypeInfo ret_type;
294 : :
295 : : // Fill in the result with some hopefully neutral value
296 : 485 : g_callable_info_load_return_type(m_info, &ret_type);
297 [ + + ]: 485 : if (g_type_info_get_tag(&ret_type) != GI_TYPE_TAG_VOID) {
298 : 294 : GIArgument argument = {};
299 : 294 : gjs_gi_argument_init_default(&ret_type, &argument);
300 : 294 : set_return_ffi_arg_from_gi_argument(&ret_type, result, &argument);
301 : : }
302 : :
303 [ - + ]: 485 : 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 : 485 : JSContext* context = this->context();
313 : 485 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
314 [ + + ]: 485 : if (G_UNLIKELY(gjs->sweeping())) {
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 [ - + ]: 481 : 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 : 481 : JSAutoRealm ar(context, callable());
331 : :
332 : 481 : int n_args = g_callable_info_get_n_args(m_info);
333 : 481 : g_assert(n_args >= 0);
334 : :
335 : : struct AutoCallbackData {
336 : 481 : AutoCallbackData(GjsCallbackTrampoline* trampoline,
337 : : GjsContextPrivate* gjs)
338 : 481 : : trampoline(trampoline), gjs(gjs) {}
339 : 479 : ~AutoCallbackData() {
340 [ + + ]: 479 : 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 : 111 : gjs->async_closure_enqueue_for_gc(trampoline);
346 : : }
347 : 479 : gjs->schedule_gc_if_needed();
348 : 479 : }
349 : :
350 : : GjsCallbackTrampoline* trampoline;
351 : : GjsContextPrivate* gjs;
352 : : };
353 : :
354 : 481 : AutoCallbackData callback_data(this, gjs);
355 : 481 : JS::RootedObject this_object(context);
356 : 481 : int c_args_offset = 0;
357 : 481 : GObject* gobj = nullptr;
358 [ + + ]: 481 : 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 : 481 : JS::RootedValue rval(context);
380 : :
381 [ + + ]: 481 : 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 : JSFunction* fn = JS_GetObjectFunction(callable());
394 : : std::string descr =
395 : 0 : fn ? "function " + gjs_debug_string(JS_GetFunctionDisplayId(fn))
396 [ # # # # : 0 : : "callable object " + gjs_debug_object(callable());
# # ]
397 : 0 : g_error("Call to %s (%s.%s) terminated with uncatchable exception",
398 : : descr.c_str(), m_info.ns(), m_info.name());
399 : : }
400 : :
401 : : // If the callback has a GError** argument, then make a GError from the
402 : : // value that was thrown. Otherwise, log it as "uncaught" (critical
403 : : // instead of warning)
404 : :
405 [ + + ]: 24 : if (!g_callable_info_can_throw_gerror(m_info)) {
406 : 8 : gjs_log_exception_uncaught(context);
407 : 8 : return;
408 : : }
409 : :
410 : : // The GError** pointer is the last argument, and is not included in
411 : : // the n_args
412 : 16 : GIArgument* error_argument = args[n_args + c_args_offset];
413 : 16 : auto* gerror = gjs_arg_get<GError**>(error_argument);
414 : 16 : GError* local_error = gjs_gerror_make_from_thrown_value(context);
415 : 16 : g_propagate_error(gerror, local_error);
416 : : }
417 [ + + + + : 503 : }
+ + + + ]
418 : :
419 : 34 : inline GIArgument* get_argument_for_arg_info(GIArgInfo* arg_info,
420 : : GIArgument** args, int index) {
421 [ + + ]: 34 : if (!g_arg_info_is_caller_allocates(arg_info))
422 : 31 : return *reinterpret_cast<GIArgument**>(args[index]);
423 : : else
424 : 3 : return args[index];
425 : : }
426 : :
427 : 481 : bool GjsCallbackTrampoline::callback_closure_inner(
428 : : JSContext* context, JS::HandleObject this_object, GObject* gobject,
429 : : JS::MutableHandleValue rval, GIArgument** args, GITypeInfo* ret_type,
430 : : int n_args, int c_args_offset, void* result) {
431 : 481 : int n_outargs = 0;
432 : 481 : JS::RootedValueVector jsargs(context);
433 : :
434 [ - + ]: 481 : if (!jsargs.reserve(n_args))
435 : 0 : g_error("Unable to reserve space for vector");
436 : :
437 : 481 : GITypeTag ret_tag = g_type_info_get_tag(ret_type);
438 : 481 : bool ret_type_is_void = ret_tag == GI_TYPE_TAG_VOID;
439 : 481 : bool in_args_to_cleanup = false;
440 : :
441 [ + + ]: 1777 : for (int i = 0, n_jsargs = 0; i < n_args; i++) {
442 : : GIArgInfo arg_info;
443 : : GITypeInfo type_info;
444 : : GjsParamType param_type;
445 : :
446 : 1296 : g_callable_info_load_arg(m_info, i, &arg_info);
447 : 1296 : g_arg_info_load_type(&arg_info, &type_info);
448 : :
449 : : /* Skip void * arguments */
450 [ + + ]: 1296 : if (g_type_info_get_tag(&type_info) == GI_TYPE_TAG_VOID)
451 : 396 : continue;
452 : :
453 [ + + ]: 939 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_OUT) {
454 : 33 : n_outargs++;
455 : 33 : continue;
456 : : }
457 : :
458 [ + + ]: 906 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT)
459 : 6 : n_outargs++;
460 : :
461 [ + + ]: 906 : if (g_arg_info_get_ownership_transfer(&arg_info) != GI_TRANSFER_NOTHING)
462 : 7 : in_args_to_cleanup = m_scope != GI_SCOPE_TYPE_FOREVER;
463 : :
464 : 906 : param_type = m_param_types[i];
465 : :
466 [ + + + - ]: 906 : switch (param_type) {
467 : 6 : case PARAM_SKIPPED:
468 : 6 : continue;
469 : 6 : case PARAM_ARRAY: {
470 : 6 : gint array_length_pos = g_type_info_get_array_length(&type_info);
471 : : GIArgInfo array_length_arg;
472 : : GITypeInfo arg_type_info;
473 : :
474 : 6 : g_callable_info_load_arg(m_info, array_length_pos,
475 : : &array_length_arg);
476 : 6 : g_arg_info_load_type(&array_length_arg, &arg_type_info);
477 : 6 : size_t length = gjs_gi_argument_get_array_length(
478 : : g_type_info_get_tag(&arg_type_info),
479 : 6 : args[array_length_pos + c_args_offset]);
480 : :
481 [ - + ]: 6 : if (!jsargs.growBy(1))
482 : 0 : g_error("Unable to grow vector");
483 : :
484 [ - + ]: 6 : if (!gjs_value_from_explicit_array(
485 : 6 : context, jsargs[n_jsargs++], &type_info,
486 : : g_arg_info_get_ownership_transfer(&arg_info),
487 : 6 : args[i + c_args_offset], length))
488 : 0 : return false;
489 : 6 : break;
490 : : }
491 : 894 : case PARAM_NORMAL: {
492 [ - + ]: 894 : if (!jsargs.growBy(1))
493 : 0 : g_error("Unable to grow vector");
494 : :
495 : 894 : GIArgument* arg = args[i + c_args_offset];
496 [ + + + - : 900 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_INOUT &&
+ + ]
497 : 6 : !g_arg_info_is_caller_allocates(&arg_info))
498 : 6 : arg = *reinterpret_cast<GIArgument**>(arg);
499 : :
500 [ - + ]: 894 : if (!gjs_value_from_gi_argument(context, jsargs[n_jsargs++],
501 : : &type_info, arg, false))
502 : 0 : return false;
503 : 894 : break;
504 : : }
505 : 0 : case PARAM_CALLBACK:
506 : : /* Callbacks that accept another callback as a parameter are not
507 : : * supported, see gjs_callback_trampoline_new() */
508 : : case PARAM_UNKNOWN:
509 : : // PARAM_UNKNOWN is currently not ever set on a callback's args.
510 : : default:
511 : : g_assert_not_reached();
512 : 6 : }
513 : : }
514 : :
515 [ + + ]: 481 : if (!invoke(this_object, jsargs, rval))
516 : 17 : return false;
517 : :
518 [ + + + + ]: 463 : if (n_outargs == 0 && ret_type_is_void) {
519 : : /* void return value, no out args, nothing to do */
520 [ + + ]: 295 : } else if (n_outargs == 0) {
521 : : GIArgument argument;
522 : : GITransfer transfer;
523 : :
524 : 264 : transfer = g_callable_info_get_caller_owns(m_info);
525 : : /* non-void return value, no out args. Should
526 : : * be a single return value. */
527 [ + + ]: 264 : if (!gjs_value_to_gi_argument(context, rval, ret_type, "callback",
528 : : GJS_ARGUMENT_RETURN_VALUE, transfer,
529 : : GjsArgumentFlags::MAY_BE_NULL, &argument))
530 : 2 : return false;
531 : :
532 : 262 : set_return_ffi_arg_from_gi_argument(ret_type, result, &argument);
533 [ + + + + ]: 31 : } else if (n_outargs == 1 && ret_type_is_void) {
534 : : /* void return value, one out args. Should
535 : : * be a single return value. */
536 [ + - ]: 17 : for (int i = 0; i < n_args; i++) {
537 : : GIArgInfo arg_info;
538 : 17 : g_callable_info_load_arg(m_info, i, &arg_info);
539 [ + + ]: 17 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
540 : 2 : continue;
541 : :
542 [ + + ]: 15 : if (!gjs_value_to_callback_out_arg(
543 : : context, rval, &arg_info,
544 : : get_argument_for_arg_info(&arg_info, args,
545 : : i + c_args_offset)))
546 : 3 : return false;
547 : :
548 : 12 : break;
549 : : }
550 : 12 : } else {
551 : 16 : bool is_array = rval.isObject();
552 [ - + ]: 16 : if (!JS::IsArrayObject(context, rval, &is_array))
553 : 3 : return false;
554 : :
555 [ + + ]: 16 : if (!is_array) {
556 : 3 : JSFunction* fn = JS_GetObjectFunction(callable());
557 : : std::string descr =
558 : 6 : fn ? "function " + gjs_debug_string(JS_GetFunctionDisplayId(fn))
559 [ + - - + : 6 : : "callable object " + gjs_debug_object(callable());
+ - ]
560 : 3 : gjs_throw(context,
561 : : "Call to %s (%s.%s) returned unexpected value, expecting "
562 : : "an Array",
563 : : descr.c_str(), m_info.ns(), m_info.name());
564 : 3 : return false;
565 : 3 : }
566 : :
567 : 13 : JS::RootedValue elem(context);
568 : 13 : JS::RootedObject out_array(context, rval.toObjectOrNull());
569 : 13 : gsize elem_idx = 0;
570 : : /* more than one of a return value or an out argument.
571 : : * Should be an array of output values. */
572 : :
573 [ + + ]: 13 : if (!ret_type_is_void) {
574 : : GIArgument argument;
575 : 10 : GITransfer transfer = g_callable_info_get_caller_owns(m_info);
576 : :
577 [ - + ]: 10 : if (!JS_GetElement(context, out_array, elem_idx, &elem))
578 : 0 : return false;
579 : :
580 [ - + ]: 10 : if (!gjs_value_to_gi_argument(context, elem, ret_type, "callback",
581 : : GJS_ARGUMENT_RETURN_VALUE, transfer,
582 : : GjsArgumentFlags::MAY_BE_NULL,
583 : : &argument))
584 : 0 : return false;
585 : :
586 [ + - + + ]: 10 : if ((ret_tag == GI_TYPE_TAG_FILENAME ||
587 [ + - ]: 2 : ret_tag == GI_TYPE_TAG_UTF8) &&
588 : : transfer == GI_TRANSFER_NOTHING) {
589 : : // We duplicated the string so not to leak we need to both
590 : : // ensure that the string is bound to the object lifetime or
591 : : // created once
592 [ + - ]: 2 : if (gobject) {
593 : 2 : ObjectInstance::associate_string(
594 : : gobject, gjs_arg_get<char*>(&argument));
595 : : } else {
596 : 0 : GjsAutoChar str = gjs_arg_steal<char*>(&argument);
597 : 0 : gjs_arg_set<const char*>(&argument, g_intern_string(str));
598 : 0 : }
599 : : }
600 : :
601 : 10 : set_return_ffi_arg_from_gi_argument(ret_type, result, &argument);
602 : :
603 : 10 : elem_idx++;
604 : : }
605 : :
606 [ + + ]: 40 : for (int i = 0; i < n_args; i++) {
607 : : GIArgInfo arg_info;
608 : 27 : g_callable_info_load_arg(m_info, i, &arg_info);
609 [ + + ]: 27 : if (g_arg_info_get_direction(&arg_info) == GI_DIRECTION_IN)
610 : 8 : continue;
611 : :
612 [ - + ]: 19 : if (!JS_GetElement(context, out_array, elem_idx, &elem))
613 : 0 : return false;
614 : :
615 [ - + ]: 19 : if (!gjs_value_to_callback_out_arg(
616 : : context, elem, &arg_info,
617 : : get_argument_for_arg_info(&arg_info, args,
618 : : i + c_args_offset)))
619 : 0 : return false;
620 : :
621 : 19 : elem_idx++;
622 : : }
623 [ + - + - ]: 13 : }
624 : :
625 [ + + ]: 455 : if (!in_args_to_cleanup)
626 : 450 : return true;
627 : :
628 [ + + ]: 12 : for (int i = 0; i < n_args; i++) {
629 : : GIArgInfo arg_info;
630 : 7 : g_callable_info_load_arg(m_info, i, &arg_info);
631 : 7 : GITransfer transfer = g_arg_info_get_ownership_transfer(&arg_info);
632 : :
633 [ - + ]: 7 : if (transfer == GI_TRANSFER_NOTHING)
634 : 7 : continue;
635 : :
636 [ + + ]: 7 : if (g_arg_info_get_direction(&arg_info) != GI_DIRECTION_IN)
637 : 6 : continue;
638 : :
639 : 1 : GIArgument* arg = args[i + c_args_offset];
640 [ + - ]: 1 : if (m_scope == GI_SCOPE_TYPE_CALL) {
641 : : GITypeInfo type_info;
642 : 1 : g_arg_info_load_type(&arg_info, &type_info);
643 : :
644 [ - + ]: 1 : if (!gjs_gi_argument_release(context, transfer, &type_info, arg))
645 : 0 : return false;
646 : :
647 : 1 : continue;
648 : 1 : }
649 : :
650 : : struct InvalidateData {
651 : : GIArgInfo arg_info;
652 : : GIArgument arg;
653 : : };
654 : :
655 : 0 : auto* data = new InvalidateData({arg_info, *arg});
656 : 0 : g_closure_add_invalidate_notifier(
657 : 0 : this, data, [](void* invalidate_data, GClosure* c) {
658 : 0 : auto* self = static_cast<GjsCallbackTrampoline*>(c);
659 : : std::unique_ptr<InvalidateData> data(
660 : 0 : static_cast<InvalidateData*>(invalidate_data));
661 : : GITransfer transfer =
662 : 0 : g_arg_info_get_ownership_transfer(&data->arg_info);
663 : :
664 : : GITypeInfo type_info;
665 : 0 : g_arg_info_load_type(&data->arg_info, &type_info);
666 [ # # ]: 0 : if (!gjs_gi_argument_release(self->context(), transfer,
667 : 0 : &type_info, &data->arg)) {
668 : 0 : gjs_throw(self->context(),
669 : : "Impossible to release closure argument '%s'",
670 : 0 : g_base_info_get_name(&data->arg_info));
671 : : }
672 : 0 : });
673 : : }
674 : :
675 : 5 : return true;
676 : 480 : }
677 : :
678 : 296 : GjsCallbackTrampoline* GjsCallbackTrampoline::create(
679 : : JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info,
680 : : GIScopeType scope, bool has_scope_object, bool is_vfunc) {
681 : 296 : g_assert(JS::IsCallable(callable) &&
682 : : "tried to create a callback trampoline for a non-callable object");
683 : :
684 : : auto* trampoline = new GjsCallbackTrampoline(
685 : 296 : cx, callable, callable_info, scope, has_scope_object, is_vfunc);
686 : :
687 [ + + ]: 296 : if (!trampoline->initialize()) {
688 : 1 : g_closure_unref(trampoline);
689 : 1 : return nullptr;
690 : : }
691 : :
692 : 295 : return trampoline;
693 : : }
694 : :
695 : : decltype(GjsCallbackTrampoline::s_forever_closure_list)
696 : : GjsCallbackTrampoline::s_forever_closure_list;
697 : :
698 : 296 : GjsCallbackTrampoline::GjsCallbackTrampoline(
699 : : JSContext* cx, JS::HandleObject callable, GICallableInfo* callable_info,
700 : 296 : GIScopeType scope, bool has_scope_object, bool is_vfunc)
701 : : // The rooting rule is:
702 : : // - notify callbacks in GObject methods are traced from the scope object
703 : : // - async and call callbacks, and other notify callbacks, are rooted
704 : : // - vfuncs are traced from the GObject prototype
705 : : : Closure(cx, callable,
706 [ + + + + ]: 296 : scope != GI_SCOPE_TYPE_NOTIFIED || !has_scope_object,
707 : : g_base_info_get_name(callable_info)),
708 : 296 : m_info(callable_info, GjsAutoTakeOwnership()),
709 : 296 : m_param_types(std::make_unique<GjsParamType[]>(
710 : 296 : g_callable_info_get_n_args(callable_info))),
711 : 296 : m_scope(scope),
712 : 888 : m_is_vfunc(is_vfunc) {
713 : 296 : add_finalize_notifier<GjsCallbackTrampoline>();
714 : 296 : }
715 : :
716 : 280 : GjsCallbackTrampoline::~GjsCallbackTrampoline() {
717 [ + - + + : 280 : if (m_info && m_closure) {
+ + ]
718 : : #if GI_CHECK_VERSION(1, 71, 0)
719 : 279 : g_callable_info_destroy_closure(m_info, m_closure);
720 : : #else
721 : : g_callable_info_free_closure(m_info, m_closure);
722 : : #endif
723 : : }
724 : 280 : }
725 : :
726 : 1 : void GjsCallbackTrampoline::mark_forever() {
727 : 1 : s_forever_closure_list.emplace_back(this, GjsAutoTakeOwnership{});
728 : 1 : }
729 : :
730 : 239 : void GjsCallbackTrampoline::prepare_shutdown() {
731 : 239 : s_forever_closure_list.clear();
732 : 239 : }
733 : :
734 : 295 : ffi_closure* GjsCallbackTrampoline::create_closure() {
735 : 485 : auto callback = [](ffi_cif*, void* result, void** ffi_args, void* data) {
736 : 485 : auto** args = reinterpret_cast<GIArgument**>(ffi_args);
737 : 485 : g_assert(data && "Trampoline data is not set");
738 : : Gjs::Closure::Ptr trampoline(static_cast<GjsCallbackTrampoline*>(data),
739 : 485 : GjsAutoTakeOwnership());
740 : :
741 : 485 : trampoline.as<GjsCallbackTrampoline>()->callback_closure(args, result);
742 : 483 : };
743 : :
744 : : #if GI_CHECK_VERSION(1, 71, 0)
745 : 295 : return g_callable_info_create_closure(m_info, &m_cif, callback, this);
746 : : #else
747 : : return g_callable_info_prepare_closure(m_info, &m_cif, callback, this);
748 : : #endif
749 : : }
750 : :
751 : 296 : bool GjsCallbackTrampoline::initialize() {
752 : 296 : g_assert(is_valid());
753 : 296 : g_assert(!m_closure);
754 : :
755 : : /* Analyze param types and directions, similarly to
756 : : * init_cached_function_data */
757 : 296 : int n_param_types = g_callable_info_get_n_args(m_info);
758 [ + + ]: 898 : for (int i = 0; i < n_param_types; i++) {
759 : : GIDirection direction;
760 : : GIArgInfo arg_info;
761 : : GITypeInfo type_info;
762 : : GITypeTag type_tag;
763 : :
764 [ + + ]: 603 : if (m_param_types[i] == PARAM_SKIPPED)
765 : 45 : continue;
766 : :
767 : 600 : g_callable_info_load_arg(m_info, i, &arg_info);
768 : 600 : g_arg_info_load_type(&arg_info, &type_info);
769 : :
770 : 600 : direction = g_arg_info_get_direction(&arg_info);
771 : 600 : type_tag = g_type_info_get_tag(&type_info);
772 : :
773 [ + + ]: 600 : if (direction != GI_DIRECTION_IN) {
774 : : /* INOUT and OUT arguments are handled differently. */
775 : 42 : continue;
776 : : }
777 : :
778 [ + + ]: 558 : if (type_tag == GI_TYPE_TAG_INTERFACE) {
779 : : GjsAutoBaseInfo interface_info =
780 : 304 : g_type_info_get_interface(&type_info);
781 : 304 : GIInfoType interface_type = interface_info.type();
782 [ + + ]: 304 : if (interface_type == GI_INFO_TYPE_CALLBACK) {
783 : 2 : gjs_throw(context(),
784 : : "%s %s accepts another callback as a parameter. This "
785 : : "is not supported",
786 [ + - ]: 1 : m_is_vfunc ? "VFunc" : "Callback", m_info.name());
787 : 1 : return false;
788 : : }
789 [ + + + + ]: 558 : } else if (type_tag == GI_TYPE_TAG_ARRAY) {
790 [ + - ]: 3 : if (g_type_info_get_array_type(&type_info) == GI_ARRAY_TYPE_C) {
791 : 3 : int array_length_pos = g_type_info_get_array_length(&type_info);
792 : :
793 [ - + ]: 3 : if (array_length_pos < 0)
794 : 0 : continue;
795 : :
796 [ + - ]: 3 : if (array_length_pos < n_param_types) {
797 : : GIArgInfo length_arg_info;
798 : :
799 : 3 : g_callable_info_load_arg(m_info, array_length_pos,
800 : : &length_arg_info);
801 [ - + ]: 3 : if (g_arg_info_get_direction(&length_arg_info) != direction) {
802 : 0 : gjs_throw(context(),
803 : : "%s %s has an array with different-direction "
804 : : "length argument. This is not supported",
805 [ # # ]: 0 : m_is_vfunc ? "VFunc" : "Callback",
806 : : m_info.name());
807 : 0 : return false;
808 : : }
809 : :
810 : 3 : m_param_types[array_length_pos] = PARAM_SKIPPED;
811 : 3 : m_param_types[i] = PARAM_ARRAY;
812 : : }
813 : : }
814 : : }
815 : : }
816 : :
817 : 295 : m_closure = create_closure();
818 : 295 : return true;
819 : : }
820 : :
821 : : // Intended for error messages
822 : 64458 : std::string Gjs::Function::format_name() {
823 : 64458 : bool is_method = g_callable_info_is_method(m_info);
824 [ + + ]: 64458 : std::string retval = is_method ? "method" : "function";
825 : 64458 : retval += ' ';
826 : 64458 : retval += m_info.ns();
827 : 64458 : retval += '.';
828 [ + + ]: 64458 : if (is_method) {
829 : 35471 : retval += g_base_info_get_name(g_base_info_get_container(m_info));
830 : 35471 : retval += '.';
831 : : }
832 : 64458 : retval += m_info.name();
833 : 64458 : return retval;
834 : : }
835 : :
836 : : namespace Gjs {
837 : :
838 : 64397 : static void* get_return_ffi_pointer_from_gi_argument(
839 : : GITypeTag tag, GITypeInfo* return_type, GIFFIReturnValue* return_value) {
840 [ + + + + : 64397 : if (return_type && g_type_info_is_pointer(return_type))
+ + ]
841 : 17614 : return &gjs_arg_member<void*>(return_value);
842 [ + + + + : 46783 : switch (tag) {
+ + + + +
+ + + + +
+ ]
843 : 28750 : case GI_TYPE_TAG_VOID:
844 : 28750 : return nullptr;
845 : 14 : case GI_TYPE_TAG_INT8:
846 : 14 : return &gjs_arg_member<int8_t>(return_value);
847 : 20 : case GI_TYPE_TAG_INT16:
848 : 20 : return &gjs_arg_member<int16_t>(return_value);
849 : 129 : case GI_TYPE_TAG_INT32:
850 : 129 : return &gjs_arg_member<int32_t>(return_value);
851 : 21 : case GI_TYPE_TAG_UINT8:
852 : 21 : return &gjs_arg_member<uint8_t>(return_value);
853 : 13 : case GI_TYPE_TAG_UINT16:
854 : 13 : return &gjs_arg_member<uint16_t>(return_value);
855 : 11667 : case GI_TYPE_TAG_UINT32:
856 : 11667 : return &gjs_arg_member<uint32_t>(return_value);
857 : 1058 : case GI_TYPE_TAG_BOOLEAN:
858 : 1058 : return &gjs_arg_member<gboolean, GI_TYPE_TAG_BOOLEAN>(return_value);
859 : 3 : case GI_TYPE_TAG_UNICHAR:
860 : 3 : return &gjs_arg_member<uint32_t>(return_value);
861 : 72 : case GI_TYPE_TAG_INT64:
862 : 72 : return &gjs_arg_member<int64_t>(return_value);
863 : 657 : case GI_TYPE_TAG_UINT64:
864 : 657 : return &gjs_arg_member<uint64_t>(return_value);
865 : 11 : case GI_TYPE_TAG_FLOAT:
866 : 11 : return &gjs_arg_member<float>(return_value);
867 : 18 : case GI_TYPE_TAG_DOUBLE:
868 : 18 : return &gjs_arg_member<double>(return_value);
869 : 2276 : case GI_TYPE_TAG_INTERFACE: {
870 [ - + ]: 2276 : if (!return_type)
871 : 0 : return nullptr;
872 : :
873 : 2276 : GjsAutoBaseInfo info = g_type_info_get_interface(return_type);
874 : :
875 [ + - ]: 2276 : switch (info.type()) {
876 : 2276 : case GI_INFO_TYPE_ENUM:
877 : : case GI_INFO_TYPE_FLAGS:
878 : 2276 : return &gjs_arg_member<int, GI_TYPE_TAG_INTERFACE>(
879 : 2276 : return_value);
880 : 0 : default:
881 : 0 : return &gjs_arg_member<void*>(return_value);
882 : : }
883 : : break;
884 : 2276 : }
885 : 2074 : default:
886 : 2074 : return &gjs_arg_member<void*>(return_value);
887 : : }
888 : : }
889 : :
890 : : // This function can be called in two different ways. You can either use it to
891 : : // create JavaScript objects by calling it without @r_value, or you can decide
892 : : // to keep the return values in GIArgument format by providing a @r_value
893 : : // argument.
894 : 64461 : bool Function::invoke(JSContext* context, const JS::CallArgs& args,
895 : : JS::HandleObject this_obj /* = nullptr */,
896 : : GIArgument* r_value /* = nullptr */) {
897 : 64461 : g_assert((args.isConstructing() || !this_obj) &&
898 : : "If not a constructor, then pass the 'this' object via CallArgs");
899 : :
900 : : void* return_value_p; // will point inside the return GIArgument union
901 : : GIFFIReturnValue return_value;
902 : :
903 : 64461 : unsigned ffi_argc = m_invoker.cif.nargs;
904 : 64461 : GjsFunctionCallState state(context, m_info);
905 : :
906 [ - + ]: 64461 : if (state.gi_argc > Argument::MAX_ARGS) {
907 : 0 : gjs_throw(context, "Function %s has too many arguments",
908 : 0 : format_name().c_str());
909 : 0 : return false;
910 : : }
911 : :
912 : : // ffi_argc is the number of arguments that the underlying C function takes.
913 : : // state.gi_argc is the number of arguments the GICallableInfo describes
914 : : // (which does not include "this" or GError**). m_js_in_argc is the number
915 : : // of arguments we expect the JS function to take (which does not include
916 : : // PARAM_SKIPPED args).
917 : : // args.length() is the number of arguments that were actually passed.
918 [ + + ]: 64461 : if (args.length() > m_js_in_argc) {
919 : 2 : if (!JS::WarnUTF8(context,
920 : : "Too many arguments to %s: expected %u, got %u",
921 [ - + ]: 2 : format_name().c_str(), m_js_in_argc, args.length()))
922 : 0 : return false;
923 [ + + ]: 64460 : } else if (args.length() < m_js_in_argc) {
924 : 3 : args.reportMoreArgsNeeded(context, format_name().c_str(), m_js_in_argc,
925 : : args.length());
926 : 3 : return false;
927 : : }
928 : :
929 : : // These arrays hold argument pointers.
930 : : // - state.in_cvalue(): C values which are passed on input (in or inout)
931 : : // - state.out_cvalue(): C values which are returned as arguments (out or
932 : : // inout)
933 : : // - state.inout_original_cvalue(): For the special case of (inout) args,
934 : : // we need to keep track of the original values we passed into the
935 : : // function, in case we need to free it.
936 : : // - ffi_arg_pointers: For passing data to FFI, we need to create another
937 : : // layer of indirection; this array is a pointer to an element in
938 : : // state.in_cvalue() or state.out_cvalue().
939 : : // - return_value: The actual return value of the C function, i.e. not an
940 : : // (out) param
941 : : //
942 : : // The 3 GIArgument arrays are indexed by the GI argument index.
943 : : // ffi_arg_pointers, on the other hand, represents the actual C arguments,
944 : : // in the way ffi expects them.
945 : :
946 : 64458 : auto ffi_arg_pointers = std::make_unique<void*[]>(ffi_argc);
947 : :
948 : 64458 : int gi_arg_pos = 0; // index into GIArgument array
949 : 64458 : unsigned ffi_arg_pos = 0; // index into ffi_arg_pointers
950 : 64458 : unsigned js_arg_pos = 0; // index into args
951 : :
952 : 64458 : JS::RootedObject obj(context, this_obj);
953 [ + + - + : 64458 : if (!args.isConstructing() && !args.computeThis(context, &obj))
- + ]
954 : 0 : return false;
955 : :
956 : 64458 : std::string dynamicString("(unknown)");
957 : :
958 [ + + ]: 64458 : if (state.is_method) {
959 : 35474 : GIArgument* in_value = state.instance();
960 : 35474 : JS::RootedValue in_js_value(context, JS::ObjectValue(*obj));
961 : :
962 [ + + ]: 35474 : if (!m_arguments.instance()->in(context, &state, in_value, in_js_value))
963 : 4 : return false;
964 : :
965 : 35470 : ffi_arg_pointers[ffi_arg_pos] = in_value;
966 : 35470 : ++ffi_arg_pos;
967 : :
968 : : // Callback lifetimes will be attached to the instance object if it is
969 : : // a GObject or GInterface
970 : 35470 : GType gtype = m_arguments.instance_type();
971 [ + + ]: 35470 : if (gtype != G_TYPE_NONE) {
972 [ + + + + : 69026 : if (g_type_is_a(gtype, G_TYPE_OBJECT) ||
+ - + + ]
973 [ + + ]: 33812 : g_type_is_a(gtype, G_TYPE_INTERFACE))
974 : 1405 : state.instance_object = obj;
975 : :
976 [ + + + + : 35214 : if (g_type_is_a(gtype, G_TYPE_OBJECT)) {
+ + ]
977 : 1402 : auto* o = ObjectBase::for_js(context, obj);
978 : 1402 : dynamicString = o->format_name();
979 : : }
980 : : }
981 [ + + ]: 35474 : }
982 : :
983 : 64454 : dynamicString += '.';
984 : 64454 : dynamicString += format_name();
985 : 64454 : AutoProfilerLabel label(context, "", dynamicString.c_str());
986 : :
987 : 64454 : g_assert(ffi_arg_pos + state.gi_argc <
988 : : std::numeric_limits<decltype(state.processed_c_args)>::max());
989 : :
990 : 64454 : state.processed_c_args = ffi_arg_pos;
991 [ + + ]: 135779 : for (gi_arg_pos = 0; gi_arg_pos < state.gi_argc;
992 : 71325 : gi_arg_pos++, ffi_arg_pos++) {
993 : 71382 : GIArgument* in_value = &state.in_cvalue(gi_arg_pos);
994 : 71382 : Argument* gjs_arg = m_arguments.argument(gi_arg_pos);
995 : :
996 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
997 : : "Marshalling argument '%s' in, %d/%d GI args, %u/%u "
998 : : "C args, %u/%u JS args",
999 : : gjs_arg ? gjs_arg->arg_name() : "<unknown>",
1000 : : gi_arg_pos, state.gi_argc, ffi_arg_pos, ffi_argc,
1001 : : js_arg_pos, args.length());
1002 : :
1003 : 71382 : ffi_arg_pointers[ffi_arg_pos] = in_value;
1004 : :
1005 [ - + ]: 71382 : if (!gjs_arg) {
1006 : : GIArgInfo arg_info;
1007 : 0 : g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info);
1008 : 0 : gjs_throw(context,
1009 : : "Error invoking %s: impossible to determine what to pass "
1010 : : "to the '%s' argument. It may be that the function is "
1011 : : "unsupported, or there may be a bug in its annotations.",
1012 : 0 : format_name().c_str(), g_base_info_get_name(&arg_info));
1013 : 0 : state.failed = true;
1014 : 0 : break;
1015 : : }
1016 : :
1017 : 71382 : JS::RootedValue js_in_arg(context);
1018 [ + + ]: 71382 : if (js_arg_pos < args.length())
1019 : 70129 : js_in_arg = args[js_arg_pos];
1020 : :
1021 [ + + ]: 71382 : if (!gjs_arg->in(context, &state, in_value, js_in_arg)) {
1022 : 57 : state.failed = true;
1023 : 57 : break;
1024 : : }
1025 : :
1026 [ + + ]: 71325 : if (!gjs_arg->skip_in())
1027 : 69945 : js_arg_pos++;
1028 : :
1029 : 71325 : state.processed_c_args++;
1030 [ + + ]: 71382 : }
1031 : :
1032 : : // This pointer needs to exist on the stack across the ffi_call() call
1033 : 64454 : GError** errorp = &state.local_error;
1034 : :
1035 : : /* Did argument conversion fail? In that case, skip invocation and jump to release
1036 : : * processing. */
1037 [ + + ]: 64454 : if (state.failed)
1038 : 57 : return finish_invoke(context, args, &state, r_value);
1039 : :
1040 [ + + ]: 64397 : if (state.can_throw_gerror) {
1041 : 316 : g_assert(ffi_arg_pos < ffi_argc && "GError** argument number mismatch");
1042 : 316 : ffi_arg_pointers[ffi_arg_pos] = &errorp;
1043 : 316 : ffi_arg_pos++;
1044 : :
1045 : : /* don't update state.processed_c_args as we deal with local_error
1046 : : * separately */
1047 : : }
1048 : :
1049 : 64397 : g_assert_cmpuint(ffi_arg_pos, ==, ffi_argc);
1050 : 64397 : g_assert_cmpuint(gi_arg_pos, ==, state.gi_argc);
1051 : :
1052 : 64397 : GITypeTag return_tag = m_arguments.return_tag();
1053 : 64397 : GITypeInfo* return_type = m_arguments.return_type();
1054 : 64397 : return_value_p = get_return_ffi_pointer_from_gi_argument(
1055 : : return_tag, return_type, &return_value);
1056 : 64397 : ffi_call(&m_invoker.cif, FFI_FN(m_invoker.native_address), return_value_p,
1057 : : ffi_arg_pointers.get());
1058 : :
1059 : : /* Return value and out arguments are valid only if invocation doesn't
1060 : : * return error. In arguments need to be released always.
1061 : : */
1062 [ + + ]: 64395 : if (!r_value)
1063 : 64380 : args.rval().setUndefined();
1064 : :
1065 [ + + ]: 64395 : if (return_type) {
1066 : 21279 : gi_type_info_extract_ffi_return_value(return_type, &return_value,
1067 : : state.return_value());
1068 [ + + ]: 43116 : } else if (return_tag != GI_TYPE_TAG_VOID) {
1069 : 14368 : g_assert(GI_TYPE_TAG_IS_BASIC(return_tag));
1070 : 14368 : gi_type_tag_extract_ffi_return_value(return_tag, GI_INFO_TYPE_INVALID,
1071 : : &return_value,
1072 : : state.return_value());
1073 : : }
1074 : :
1075 : : // Process out arguments and return values. This loop is skipped if we fail
1076 : : // the type conversion above, or if state.did_throw_gerror is true.
1077 : 64395 : js_arg_pos = 0;
1078 [ + + ]: 200107 : for (gi_arg_pos = -1; gi_arg_pos < state.gi_argc; gi_arg_pos++) {
1079 : : Argument* gjs_arg;
1080 : : GIArgument* out_value;
1081 : :
1082 [ + + ]: 135712 : if (gi_arg_pos == -1) {
1083 : 64395 : out_value = state.return_value();
1084 : 64395 : gjs_arg = m_arguments.return_value();
1085 : : } else {
1086 : 71317 : out_value = &state.out_cvalue(gi_arg_pos);
1087 : 71317 : gjs_arg = m_arguments.argument(gi_arg_pos);
1088 : : }
1089 : :
1090 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION,
1091 : : "Marshalling argument '%s' out, %d/%d GI args",
1092 : : gjs_arg ? gjs_arg->arg_name() : "<unknown>",
1093 : : gi_arg_pos, state.gi_argc);
1094 : :
1095 : 135712 : JS::RootedValue js_out_arg(context);
1096 [ + + ]: 135712 : if (!r_value) {
1097 [ + + - + ]: 135682 : if (!gjs_arg && gi_arg_pos >= 0) {
1098 : : GIArgInfo arg_info;
1099 : 0 : g_callable_info_load_arg(m_info, gi_arg_pos, &arg_info);
1100 : 0 : gjs_throw(
1101 : : context,
1102 : : "Error invoking %s.%s: impossible to determine what "
1103 : : "to pass to the out '%s' argument. It may be that the "
1104 : : "function is unsupported, or there may be a bug in "
1105 : : "its annotations.",
1106 : : m_info.ns(), m_info.name(),
1107 : : g_base_info_get_name(&arg_info));
1108 : 0 : state.failed = true;
1109 : 0 : break;
1110 : : }
1111 : :
1112 [ + + ]: 242616 : if (gjs_arg &&
1113 [ - + - + ]: 242616 : !gjs_arg->out(context, &state, out_value, &js_out_arg)) {
1114 : 0 : state.failed = true;
1115 : 0 : break;
1116 : : }
1117 : : }
1118 : :
1119 [ + + + + : 135712 : if (gjs_arg && !gjs_arg->skip_out()) {
+ + ]
1120 [ + + ]: 36409 : if (!r_value) {
1121 [ - + ]: 36394 : if (!state.return_values.append(js_out_arg)) {
1122 : 0 : JS_ReportOutOfMemory(context);
1123 : 0 : state.failed = true;
1124 : 0 : break;
1125 : : }
1126 : : }
1127 : 36409 : js_arg_pos++;
1128 : : }
1129 [ + - ]: 135712 : }
1130 : :
1131 : 64395 : g_assert(state.failed || state.did_throw_gerror() ||
1132 : : js_arg_pos == m_js_out_argc);
1133 : :
1134 : : // If we failed before calling the function, or if the function threw an
1135 : : // exception, then any GI_TRANSFER_EVERYTHING or GI_TRANSFER_CONTAINER
1136 : : // in-parameters were not transferred. Treat them as GI_TRANSFER_NOTHING so
1137 : : // that they are freed.
1138 : 64395 : return finish_invoke(context, args, &state, r_value);
1139 : 64459 : }
1140 : :
1141 : 64452 : bool Function::finish_invoke(JSContext* cx, const JS::CallArgs& args,
1142 : : GjsFunctionCallState* state,
1143 : : GIArgument* r_value /* = nullptr */) {
1144 : : // In this loop we use ffi_arg_pos just to ensure we don't release stuff
1145 : : // we haven't allocated yet, if we failed in type conversion above.
1146 : : // If we start from -1 (the return value), we need to process 1 more than
1147 : : // state.processed_c_args.
1148 : : // If we start from -2 (the instance parameter), we need to process 2 more
1149 : 64452 : unsigned ffi_arg_pos = state->first_arg_offset() - 1;
1150 : 64452 : unsigned ffi_arg_max = state->last_processed_index();
1151 : 64452 : bool postinvoke_release_failed = false;
1152 : 64452 : for (int gi_arg_pos = -(state->first_arg_offset());
1153 [ + + + + ]: 235697 : gi_arg_pos < state->gi_argc && ffi_arg_pos < ffi_arg_max;
1154 : 171245 : gi_arg_pos++, ffi_arg_pos++) {
1155 : : Argument* gjs_arg;
1156 : 171245 : GIArgument* in_value = nullptr;
1157 : 171245 : GIArgument* out_value = nullptr;
1158 : :
1159 [ + + ]: 171245 : if (gi_arg_pos == -2) {
1160 : 35468 : in_value = state->instance();
1161 : 35468 : gjs_arg = m_arguments.instance();
1162 [ + + ]: 135777 : } else if (gi_arg_pos == -1) {
1163 : 64452 : out_value = state->return_value();
1164 : 64452 : gjs_arg = m_arguments.return_value();
1165 : : } else {
1166 : 71325 : in_value = &state->in_cvalue(gi_arg_pos);
1167 : 71325 : out_value = &state->out_cvalue(gi_arg_pos);
1168 : 71325 : gjs_arg = m_arguments.argument(gi_arg_pos);
1169 : : }
1170 : :
1171 [ + + ]: 171245 : if (!gjs_arg)
1172 : 28755 : continue;
1173 : :
1174 : : gjs_debug_marshal(
1175 : : GJS_DEBUG_GFUNCTION,
1176 : : "Releasing argument '%s', %d/%d GI args, %u/%u C args",
1177 : : gjs_arg->arg_name(), gi_arg_pos, state->gi_argc, ffi_arg_pos,
1178 : : state->processed_c_args);
1179 : :
1180 : : // Only process in or inout arguments if we failed, the rest is garbage
1181 [ + + + + : 142490 : if (state->failed && gjs_arg->skip_in())
+ + ]
1182 : 55 : continue;
1183 : :
1184 : : // Save the return GIArgument if it was requested
1185 [ + + + + ]: 142435 : if (r_value && gi_arg_pos == -1) {
1186 : 15 : *r_value = *out_value;
1187 : 15 : continue;
1188 : : }
1189 : :
1190 [ - + ]: 142420 : if (!gjs_arg->release(cx, state, in_value, out_value)) {
1191 : 0 : postinvoke_release_failed = true;
1192 : : // continue with the release even if we fail, to avoid leaks
1193 : : }
1194 : : }
1195 : :
1196 [ - + ]: 64452 : if (postinvoke_release_failed)
1197 : 0 : state->failed = true;
1198 : :
1199 : 64452 : g_assert(ffi_arg_pos == state->last_processed_index());
1200 : :
1201 [ + + + + : 64452 : if (!r_value && m_js_out_argc > 0 && state->call_completed()) {
+ + + + ]
1202 : : // If we have one return value or out arg, return that item on its
1203 : : // own, otherwise return a JavaScript array with [return value,
1204 : : // out arg 1, out arg 2, ...]
1205 [ + + ]: 35770 : if (m_js_out_argc == 1) {
1206 : 35216 : args.rval().set(state->return_values[0]);
1207 : : } else {
1208 : 554 : JSObject* array = JS::NewArrayObject(cx, state->return_values);
1209 [ - + ]: 554 : if (!array) {
1210 : 0 : state->failed = true;
1211 : : } else {
1212 : 554 : args.rval().setObject(*array);
1213 : : }
1214 : : }
1215 : : }
1216 : :
1217 [ + + + + : 64452 : if (!state->failed && state->did_throw_gerror()) {
+ + ]
1218 : 39 : return gjs_throw_gerror(cx, state->local_error.release());
1219 [ + + ]: 64413 : } else if (state->failed) {
1220 : 57 : return false;
1221 : : } else {
1222 : 64356 : return true;
1223 : : }
1224 : : }
1225 : :
1226 : 64446 : bool Function::call(JSContext* context, unsigned js_argc, JS::Value* vp) {
1227 : 64446 : JS::CallArgs js_argv = JS::CallArgsFromVp(js_argc, vp);
1228 : 64446 : JS::RootedObject callee(context, &js_argv.callee());
1229 : :
1230 : : Function* priv;
1231 [ - + ]: 64446 : if (!Function::for_js_typecheck(context, callee, &priv, &js_argv))
1232 : 0 : return false;
1233 : :
1234 : : gjs_debug_marshal(GJS_DEBUG_GFUNCTION, "Call callee %p priv %p",
1235 : : callee.get(), priv);
1236 : :
1237 : 64446 : g_assert(priv);
1238 : 64446 : return priv->invoke(context, js_argv);
1239 : 64444 : }
1240 : :
1241 : 10745 : Function::~Function() {
1242 : 10745 : g_function_invoker_destroy(&m_invoker);
1243 : 10745 : GJS_DEC_COUNTER(function);
1244 : 10745 : }
1245 : :
1246 : 10730 : void Function::finalize_impl(JS::GCContext*, Function* priv) {
1247 : 10730 : g_assert(priv);
1248 [ + - ]: 10730 : delete priv;
1249 : 10730 : }
1250 : :
1251 : 18 : bool Function::get_length(JSContext* cx, unsigned argc, JS::Value* vp) {
1252 [ - + ]: 18 : GJS_GET_THIS(cx, argc, vp, args, this_obj);
1253 : : Function* priv;
1254 [ - + ]: 18 : if (!Function::for_js_instance(cx, this_obj, &priv, &args))
1255 : 0 : return false;
1256 : 18 : args.rval().setInt32(priv->m_js_in_argc);
1257 : 18 : return true;
1258 : 18 : }
1259 : :
1260 : 0 : bool Function::to_string(JSContext* context, unsigned argc, JS::Value* vp) {
1261 [ # # # # ]: 0 : GJS_CHECK_WRAPPER_PRIV(context, argc, vp, rec, this_obj, Function, priv);
1262 : 0 : return priv->to_string_impl(context, rec.rval());
1263 : 0 : }
1264 : :
1265 : 0 : bool Function::to_string_impl(JSContext* cx, JS::MutableHandleValue rval) {
1266 : : int i, n_jsargs;
1267 : :
1268 : 0 : int n_args = g_callable_info_get_n_args(m_info);
1269 : 0 : n_jsargs = 0;
1270 : 0 : std::string arg_names;
1271 [ # # ]: 0 : for (i = 0; i < n_args; i++) {
1272 : 0 : Argument* gjs_arg = m_arguments.argument(i);
1273 [ # # # # : 0 : if (!gjs_arg || gjs_arg->skip_in())
# # ]
1274 : 0 : continue;
1275 : :
1276 [ # # ]: 0 : if (n_jsargs > 0)
1277 : 0 : arg_names += ", ";
1278 : :
1279 : 0 : n_jsargs++;
1280 : 0 : arg_names += gjs_arg->arg_name();
1281 : : }
1282 : :
1283 : 0 : GjsAutoChar descr;
1284 [ # # ]: 0 : if (m_info.type() == GI_INFO_TYPE_FUNCTION) {
1285 : : descr = g_strdup_printf(
1286 : : "%s(%s) {\n\t/* wrapper for native symbol %s() */\n}",
1287 : 0 : format_name().c_str(), arg_names.c_str(),
1288 : 0 : g_function_info_get_symbol(m_info));
1289 : : } else {
1290 : : descr =
1291 : : g_strdup_printf("%s(%s) {\n\t/* wrapper for native symbol */\n}",
1292 : 0 : format_name().c_str(), arg_names.c_str());
1293 : : }
1294 : :
1295 : 0 : return gjs_string_from_utf8(cx, descr, rval);
1296 : 0 : }
1297 : :
1298 : : const JSClassOps Function::class_ops = {
1299 : : nullptr, // addProperty
1300 : : nullptr, // deleteProperty
1301 : : nullptr, // enumerate
1302 : : nullptr, // newEnumerate
1303 : : nullptr, // resolve
1304 : : nullptr, // mayResolve
1305 : : &Function::finalize,
1306 : : &Function::call,
1307 : : };
1308 : :
1309 : : const JSPropertySpec Function::proto_props[] = {
1310 : : JS_PSG("length", &Function::get_length, JSPROP_PERMANENT),
1311 : : JS_STRING_SYM_PS(toStringTag, "GIRepositoryFunction", JSPROP_READONLY),
1312 : : JS_PS_END};
1313 : :
1314 : : /* The original Function.prototype.toString complains when
1315 : : given a GIRepository function as an argument */
1316 : : // clang-format off
1317 : : const JSFunctionSpec Function::proto_funcs[] = {
1318 : : JS_FN("toString", &Function::to_string, 0, 0),
1319 : : JS_FS_END};
1320 : : // clang-format on
1321 : :
1322 : 10918 : bool Function::init(JSContext* context, GType gtype /* = G_TYPE_NONE */) {
1323 : : guint8 i;
1324 : 10918 : GjsAutoError error;
1325 : :
1326 [ + + ]: 10918 : if (m_info.type() == GI_INFO_TYPE_FUNCTION) {
1327 [ - + ]: 10917 : if (!g_function_info_prep_invoker(m_info, &m_invoker, &error))
1328 : 0 : return gjs_throw_gerror(context, error);
1329 [ + - ]: 1 : } else if (m_info.type() == GI_INFO_TYPE_VFUNC) {
1330 : 1 : void* addr = g_vfunc_info_get_address(m_info, gtype, &error);
1331 [ - + ]: 1 : if (error) {
1332 [ # # ]: 0 : if (error->code != G_INVOKE_ERROR_SYMBOL_NOT_FOUND)
1333 : 0 : return gjs_throw_gerror(context, error);
1334 : :
1335 : 0 : gjs_throw(context, "Virtual function not implemented: %s",
1336 : 0 : error->message);
1337 : 0 : return false;
1338 : : }
1339 : :
1340 [ - + ]: 1 : if (!g_function_invoker_new_for_address(addr, m_info, &m_invoker,
1341 : : &error))
1342 : 0 : return gjs_throw_gerror(context, error);
1343 : : }
1344 : :
1345 : 10918 : uint8_t n_args = g_callable_info_get_n_args(m_info);
1346 : :
1347 [ - + ]: 10918 : if (!m_arguments.initialize(context, m_info))
1348 : 0 : return false;
1349 : :
1350 : 10918 : m_arguments.build_instance(m_info);
1351 : :
1352 : : bool inc_counter;
1353 : 10918 : m_arguments.build_return(m_info, &inc_counter);
1354 : :
1355 [ + + ]: 10918 : if (inc_counter)
1356 : 8621 : m_js_out_argc++;
1357 : :
1358 [ + + ]: 31596 : for (i = 0; i < n_args; i++) {
1359 : 20678 : Argument* gjs_arg = m_arguments.argument(i);
1360 : : GIDirection direction;
1361 : : GIArgInfo arg_info;
1362 : :
1363 [ + + - + : 20678 : if (gjs_arg && (gjs_arg->skip_in() || gjs_arg->skip_out())) {
- - + + ]
1364 : 1640 : continue;
1365 : : }
1366 : :
1367 : 19038 : g_callable_info_load_arg(m_info, i, &arg_info);
1368 : 19038 : direction = g_arg_info_get_direction(&arg_info);
1369 : :
1370 : 19038 : m_arguments.build_arg(i, direction, &arg_info, m_info, &inc_counter);
1371 : :
1372 [ + + ]: 19038 : if (inc_counter) {
1373 [ + + + - ]: 18806 : switch (direction) {
1374 : 61 : case GI_DIRECTION_INOUT:
1375 : 61 : m_js_out_argc++;
1376 : : [[fallthrough]];
1377 : 18156 : case GI_DIRECTION_IN:
1378 : 18156 : m_js_in_argc++;
1379 : 18156 : break;
1380 : 650 : case GI_DIRECTION_OUT:
1381 : 650 : m_js_out_argc++;
1382 : 650 : break;
1383 : 0 : default:
1384 : : g_assert_not_reached();
1385 : : }
1386 : : }
1387 : : }
1388 : :
1389 : 10918 : return true;
1390 : 10918 : }
1391 : :
1392 : 10903 : JSObject* Function::create(JSContext* context, GType gtype,
1393 : : GICallableInfo* info) {
1394 : 10903 : JS::RootedObject proto(context, Function::create_prototype(context));
1395 [ - + ]: 10903 : if (!proto)
1396 : 0 : return nullptr;
1397 : :
1398 : : JS::RootedObject function(
1399 : 10903 : context, JS_NewObjectWithGivenProto(context, &Function::klass, proto));
1400 [ - + ]: 10903 : if (!function) {
1401 : 0 : gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to construct function");
1402 : 0 : return NULL;
1403 : : }
1404 : :
1405 : 10903 : auto* priv = new Function(info);
1406 : :
1407 : 10903 : Function::init_private(function, priv);
1408 : :
1409 : 10903 : debug_lifecycle(function, priv, "Constructor");
1410 : :
1411 [ - + ]: 10903 : if (!priv->init(context, gtype))
1412 : 0 : return nullptr;
1413 : :
1414 : 10903 : return function;
1415 : 10903 : }
1416 : :
1417 : : } // namespace Gjs
1418 : :
1419 : : GJS_JSAPI_RETURN_CONVENTION
1420 : : JSObject*
1421 : 10903 : gjs_define_function(JSContext *context,
1422 : : JS::HandleObject in_object,
1423 : : GType gtype,
1424 : : GICallableInfo *info)
1425 : : {
1426 : : GIInfoType info_type;
1427 : 10903 : std::string name;
1428 : :
1429 : 10903 : info_type = g_base_info_get_type((GIBaseInfo *)info);
1430 : :
1431 : : JS::RootedObject function(context,
1432 : 10903 : Gjs::Function::create(context, gtype, info));
1433 [ - + ]: 10903 : if (!function)
1434 : 0 : return NULL;
1435 : :
1436 [ + + ]: 10903 : if (info_type == GI_INFO_TYPE_FUNCTION) {
1437 : 10902 : name = g_base_info_get_name(info);
1438 [ + - ]: 1 : } else if (info_type == GI_INFO_TYPE_VFUNC) {
1439 : 2 : name = "vfunc_" + std::string(g_base_info_get_name(info));
1440 : : } else {
1441 : : g_assert_not_reached ();
1442 : : }
1443 : :
1444 [ - + ]: 10903 : if (!JS_DefineProperty(context, in_object, name.c_str(), function,
1445 : : GJS_MODULE_PROP_FLAGS)) {
1446 : 0 : gjs_debug(GJS_DEBUG_GFUNCTION, "Failed to define function");
1447 : 0 : function = NULL;
1448 : : }
1449 : :
1450 : 10903 : return function;
1451 : 10903 : }
1452 : :
1453 : 15 : bool gjs_invoke_constructor_from_c(JSContext* context, GIFunctionInfo* info,
1454 : : JS::HandleObject obj,
1455 : : const JS::CallArgs& args,
1456 : : GIArgument* rvalue) {
1457 : 15 : return Gjs::Function::invoke_constructor_uncached(context, info, obj, args,
1458 : 15 : rvalue);
1459 : : }
|