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