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