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