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: 2013 Intel Corporation
4 : : // SPDX-FileCopyrightText: 2008-2010 litl, LLC
5 : :
6 : : #include <config.h>
7 : :
8 : : #include <girepository.h>
9 : : #include <glib.h>
10 : :
11 : : #include <js/AllocPolicy.h> // for SystemAllocPolicy
12 : : #include <js/Class.h>
13 : : #include <js/ErrorReport.h> // for JS_ReportOutOfMemory
14 : : #include <js/GCHashTable.h> // for WeakCache
15 : : #include <js/HashTable.h> // for DefaultHasher via WeakCache
16 : : #include <js/Object.h> // for GetClass
17 : : #include <js/PropertyAndElement.h>
18 : : #include <js/RootingAPI.h>
19 : : #include <js/TypeDecls.h>
20 : : #include <js/Utility.h> // for UniqueChars
21 : : #include <js/Value.h>
22 : : #include <jsapi.h> // for InformalValueTypeName, JS_NewObjectWithGivenP...
23 : : #include <mozilla/HashTable.h>
24 : :
25 : : #include "gi/arg-inl.h"
26 : : #include "gi/arg.h"
27 : : #include "gi/function.h"
28 : : #include "gi/fundamental.h"
29 : : #include "gi/repo.h"
30 : : #include "gi/value.h"
31 : : #include "gi/wrapperutils.h"
32 : : #include "gjs/atoms.h"
33 : : #include "gjs/context-private.h"
34 : : #include "gjs/jsapi-util.h"
35 : : #include "gjs/macros.h"
36 : : #include "gjs/mem-private.h"
37 : : #include "util/log.h"
38 : :
39 : : namespace JS {
40 : : class CallArgs;
41 : : }
42 : :
43 : 18 : FundamentalInstance::FundamentalInstance(FundamentalPrototype* prototype,
44 : 18 : JS::HandleObject obj)
45 : 18 : : GIWrapperInstance(prototype, obj) {
46 : 18 : GJS_INC_COUNTER(fundamental_instance);
47 : 18 : }
48 : :
49 : : /*
50 : : * FundamentalInstance::associate_js_instance:
51 : : *
52 : : * Associates @gfundamental with @object so that @object can be retrieved in the
53 : : * future if you have a pointer to @gfundamental. (Assuming @object has not been
54 : : * garbage collected in the meantime.)
55 : : */
56 : 17 : bool FundamentalInstance::associate_js_instance(JSContext* cx, JSObject* object,
57 : : void* gfundamental) {
58 : 17 : m_ptr = gfundamental;
59 : :
60 : 17 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(cx);
61 [ - + ]: 17 : if (!gjs->fundamental_table().putNew(gfundamental, object)) {
62 : 0 : JS_ReportOutOfMemory(cx);
63 : 0 : return false;
64 : : }
65 : :
66 : 17 : debug_lifecycle(object, "associated JSObject with fundamental");
67 : :
68 : 17 : ref();
69 : 17 : return true;
70 : : }
71 : :
72 : : /**/
73 : :
74 : : /* Find the first constructor */
75 : 9 : [[nodiscard]] static GIFunctionInfo* find_fundamental_constructor(
76 : : GIObjectInfo* info) {
77 : : int i, n_methods;
78 : :
79 : 9 : n_methods = g_object_info_get_n_methods(info);
80 : :
81 [ + + ]: 35 : for (i = 0; i < n_methods; ++i) {
82 : 31 : GjsAutoFunctionInfo func_info;
83 : : GIFunctionInfoFlags flags;
84 : :
85 : 31 : func_info = g_object_info_get_method(info, i);
86 : :
87 : 31 : flags = g_function_info_get_flags(func_info);
88 [ + + ]: 31 : if ((flags & GI_FUNCTION_IS_CONSTRUCTOR) != 0)
89 : 5 : return func_info.release();
90 [ + + ]: 31 : }
91 : :
92 : 4 : return nullptr;
93 : : }
94 : :
95 : : /**/
96 : :
97 : 58 : bool FundamentalPrototype::resolve_interface(JSContext* cx,
98 : : JS::HandleObject obj,
99 : : bool* resolved, const char* name) {
100 : : bool ret;
101 : : GType *interfaces;
102 : : guint n_interfaces;
103 : : guint i;
104 : :
105 : 58 : ret = true;
106 : 58 : interfaces = g_type_interfaces(gtype(), &n_interfaces);
107 [ - + ]: 58 : for (i = 0; i < n_interfaces; i++) {
108 : : GjsAutoInterfaceInfo iface_info =
109 : 0 : g_irepository_find_by_gtype(nullptr, interfaces[i]);
110 : :
111 [ # # ]: 0 : if (!iface_info)
112 : 0 : continue;
113 : :
114 : : GjsAutoFunctionInfo method_info =
115 : 0 : g_interface_info_find_method(iface_info, name);
116 : :
117 [ # # # # ]: 0 : if (method_info &&
118 [ # # ]: 0 : g_function_info_get_flags(method_info) & GI_FUNCTION_IS_METHOD) {
119 [ # # ]: 0 : if (gjs_define_function(cx, obj, gtype(), method_info)) {
120 : 0 : *resolved = true;
121 : : } else {
122 : 0 : ret = false;
123 : : }
124 : : }
125 [ # # ]: 0 : }
126 : :
127 : 58 : g_free(interfaces);
128 : 58 : return ret;
129 : : }
130 : :
131 : : // See GIWrapperBase::resolve().
132 : 58 : bool FundamentalPrototype::resolve_impl(JSContext* cx, JS::HandleObject obj,
133 : : JS::HandleId id, bool* resolved) {
134 : 58 : JS::UniqueChars prop_name;
135 [ - + ]: 58 : if (!gjs_get_string_id(cx, id, &prop_name))
136 : 0 : return false;
137 [ - + ]: 58 : if (!prop_name) {
138 : 0 : *resolved = false;
139 : 0 : return true; // not resolved, but no error
140 : : }
141 : :
142 : : /* We are the prototype, so look for methods and other class properties */
143 : : GjsAutoFunctionInfo method_info =
144 : 58 : g_object_info_find_method(info(), prop_name.get());
145 : :
146 [ + + ]: 58 : if (method_info) {
147 : : #if GJS_VERBOSE_ENABLE_GI_USAGE
148 : : _gjs_log_info_usage(method_info);
149 : : #endif
150 [ + - ]: 1 : if (g_function_info_get_flags (method_info) & GI_FUNCTION_IS_METHOD) {
151 : : /* we do not define deprecated methods in the prototype */
152 [ - + ]: 1 : if (g_base_info_is_deprecated(method_info)) {
153 : 0 : gjs_debug(GJS_DEBUG_GFUNDAMENTAL,
154 : : "Ignoring definition of deprecated method %s in "
155 : : "prototype %s.%s",
156 : : method_info.name(), ns(), name());
157 : 0 : *resolved = false;
158 : 0 : return true;
159 : : }
160 : :
161 : 1 : gjs_debug(GJS_DEBUG_GFUNDAMENTAL,
162 : : "Defining method %s in prototype for %s.%s",
163 : : method_info.name(), ns(), name());
164 : :
165 [ - + ]: 1 : if (!gjs_define_function(cx, obj, gtype(), method_info))
166 : 0 : return false;
167 : :
168 : 1 : *resolved = true;
169 : : }
170 : : } else {
171 : 57 : *resolved = false;
172 : : }
173 : :
174 : 58 : return resolve_interface(cx, obj, resolved, prop_name.get());
175 : 58 : }
176 : :
177 : : /*
178 : : * FundamentalInstance::invoke_constructor:
179 : : *
180 : : * Finds the type's static constructor method (the static method given by
181 : : * FundamentalPrototype::constructor_info()) and invokes it with the given
182 : : * arguments.
183 : : */
184 : 16 : bool FundamentalInstance::invoke_constructor(JSContext* context,
185 : : JS::HandleObject obj,
186 : : const JS::CallArgs& args,
187 : : GIArgument* rvalue) {
188 : 16 : GIFunctionInfo* constructor_info = get_prototype()->constructor_info();
189 [ + + ]: 16 : if (!constructor_info) {
190 : 1 : gjs_throw(context, "Couldn't find a constructor for type %s.%s", ns(),
191 : : name());
192 : 1 : return false;
193 : : }
194 : :
195 : 15 : return gjs_invoke_constructor_from_c(context, constructor_info, obj, args,
196 : 15 : rvalue);
197 : : }
198 : :
199 : : // See GIWrapperBase::constructor().
200 : 16 : bool FundamentalInstance::constructor_impl(JSContext* cx,
201 : : JS::HandleObject object,
202 : : const JS::CallArgs& argv) {
203 : : GArgument ret_value;
204 : : GITypeInfo return_info;
205 : :
206 [ + + + + ]: 31 : if (!invoke_constructor(cx, object, argv, &ret_value) ||
207 [ - + ]: 15 : !associate_js_instance(cx, object, gjs_arg_get<void*>(&ret_value)))
208 : 1 : return false;
209 : :
210 : 15 : GICallableInfo* constructor_info = get_prototype()->constructor_info();
211 : 15 : g_callable_info_load_return_type(constructor_info, &return_info);
212 : :
213 : 15 : return gjs_g_argument_release(
214 : : cx, g_callable_info_get_caller_owns(constructor_info), &return_info,
215 : 15 : &ret_value);
216 : : }
217 : :
218 : 18 : FundamentalInstance::~FundamentalInstance(void) {
219 [ + + ]: 18 : if (m_ptr) {
220 : 17 : unref();
221 : 17 : m_ptr = nullptr;
222 : : }
223 : 18 : GJS_DEC_COUNTER(fundamental_instance);
224 : 18 : }
225 : :
226 : 9 : FundamentalPrototype::FundamentalPrototype(GIObjectInfo* info, GType gtype)
227 : : : GIWrapperPrototype(info, gtype),
228 : 9 : m_ref_function(g_object_info_get_ref_function_pointer(info)),
229 : 9 : m_unref_function(g_object_info_get_unref_function_pointer(info)),
230 : 9 : m_get_value_function(g_object_info_get_get_value_function_pointer(info)),
231 : 9 : m_set_value_function(g_object_info_get_set_value_function_pointer(info)),
232 : 18 : m_constructor_info(find_fundamental_constructor(info)) {
233 : 9 : GJS_INC_COUNTER(fundamental_prototype);
234 : 9 : }
235 : :
236 : 9 : FundamentalPrototype::~FundamentalPrototype(void) {
237 : 9 : GJS_DEC_COUNTER(fundamental_prototype);
238 : 9 : }
239 : :
240 : : // clang-format off
241 : : const struct JSClassOps FundamentalBase::class_ops = {
242 : : nullptr, // addProperty
243 : : nullptr, // deleteProperty
244 : : nullptr, // enumerate
245 : : nullptr, // newEnumerate
246 : : &FundamentalBase::resolve,
247 : : nullptr, // mayResolve
248 : : &FundamentalBase::finalize,
249 : : nullptr, // call
250 : : nullptr, // construct
251 : : &FundamentalBase::trace
252 : : };
253 : :
254 : : const struct JSClass FundamentalBase::klass = {
255 : : "GFundamental_Object",
256 : : JSCLASS_HAS_RESERVED_SLOTS(1) | JSCLASS_FOREGROUND_FINALIZE,
257 : : &FundamentalBase::class_ops
258 : : };
259 : : // clang-format on
260 : :
261 : : GJS_JSAPI_RETURN_CONVENTION
262 : : static JSObject *
263 : 34 : gjs_lookup_fundamental_prototype(JSContext *context,
264 : : GIObjectInfo *info,
265 : : GType gtype)
266 : : {
267 : 34 : JS::RootedObject in_object(context);
268 : : const char *constructor_name;
269 : :
270 [ + - ]: 34 : if (info) {
271 : 34 : in_object = gjs_lookup_namespace_object(context, (GIBaseInfo*) info);
272 : 34 : constructor_name = g_base_info_get_name((GIBaseInfo*) info);
273 : : } else {
274 : 0 : in_object = gjs_lookup_private_namespace(context);
275 : 0 : constructor_name = g_type_name(gtype);
276 : : }
277 : :
278 [ - + ]: 34 : if (G_UNLIKELY (!in_object))
279 : 0 : return nullptr;
280 : :
281 : : bool found;
282 [ - + ]: 34 : if (!JS_HasProperty(context, in_object, constructor_name, &found))
283 : 0 : return nullptr;
284 : :
285 : 34 : JS::RootedValue value(context);
286 [ + - - + : 34 : if (found && !JS_GetProperty(context, in_object, constructor_name, &value))
- + ]
287 : 0 : return nullptr;
288 : :
289 : 34 : JS::RootedObject constructor(context);
290 [ - + ]: 34 : if (value.isUndefined()) {
291 : : /* In case we're looking for a private type, and we don't find it,
292 : : we need to define it first.
293 : : */
294 [ # # ]: 0 : if (!FundamentalPrototype::define_class(context, in_object, info,
295 : : &constructor))
296 : 0 : return nullptr;
297 : : } else {
298 [ - + ]: 34 : if (G_UNLIKELY(!value.isObject())) {
299 : 0 : gjs_throw(context,
300 : : "Fundamental constructor was not an object, it was a %s",
301 : : JS::InformalValueTypeName(value));
302 : 0 : return nullptr;
303 : : }
304 : :
305 : 34 : constructor = &value.toObject();
306 : : }
307 : :
308 : 34 : g_assert(constructor);
309 : :
310 : 34 : const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
311 : 34 : JS::RootedObject prototype(context);
312 [ - + ]: 34 : if (!gjs_object_require_property(context, constructor, "constructor object",
313 : : atoms.prototype(), &prototype))
314 : 0 : return nullptr;
315 : :
316 : 34 : return prototype;
317 : 34 : }
318 : :
319 : : GJS_JSAPI_RETURN_CONVENTION
320 : : static JSObject*
321 : 34 : gjs_lookup_fundamental_prototype_from_gtype(JSContext *context,
322 : : GType gtype)
323 : : {
324 : 34 : GjsAutoObjectInfo info;
325 : :
326 : : /* A given gtype might not have any definition in the introspection
327 : : * data. If that's the case, try to look for a definition of any of the
328 : : * parent type. */
329 [ + - + + ]: 72 : while (gtype != G_TYPE_INVALID &&
330 [ + - + + ]: 72 : !(info = g_irepository_find_by_gtype(nullptr, gtype)))
331 : 2 : gtype = g_type_parent(gtype);
332 : :
333 : 34 : return gjs_lookup_fundamental_prototype(context, info, gtype);
334 : 34 : }
335 : :
336 : : // Overrides GIWrapperPrototype::get_parent_proto().
337 : 9 : bool FundamentalPrototype::get_parent_proto(
338 : : JSContext* cx, JS::MutableHandleObject proto) const {
339 : 9 : GType parent_gtype = g_type_parent(gtype());
340 [ + + ]: 9 : if (parent_gtype != G_TYPE_INVALID) {
341 : 4 : proto.set(
342 : 4 : gjs_lookup_fundamental_prototype_from_gtype(cx, parent_gtype));
343 [ - + ]: 4 : if (!proto)
344 : 0 : return false;
345 : : }
346 : 9 : return true;
347 : : }
348 : :
349 : : // Overrides GIWrapperPrototype::constructor_nargs().
350 : 9 : unsigned FundamentalPrototype::constructor_nargs(void) const {
351 [ + + ]: 9 : if (m_constructor_info)
352 : 5 : return g_callable_info_get_n_args(m_constructor_info);
353 : 4 : return 0;
354 : : }
355 : :
356 : : /*
357 : : * FundamentalPrototype::define_class:
358 : : * @in_object: Object where the constructor is stored, typically a repo object.
359 : : * @info: Introspection info for the fundamental class.
360 : : * @constructor: Return location for the constructor object.
361 : : *
362 : : * Define a fundamental class constructor and prototype, including all the
363 : : * necessary methods and properties. Provides the constructor object as an out
364 : : * parameter, for convenience elsewhere.
365 : : */
366 : 9 : bool FundamentalPrototype::define_class(JSContext* cx,
367 : : JS::HandleObject in_object,
368 : : GIObjectInfo* info,
369 : : JS::MutableHandleObject constructor) {
370 : : GType gtype;
371 : :
372 : 9 : gtype = g_registered_type_info_get_g_type (info);
373 : :
374 : 9 : JS::RootedObject prototype(cx);
375 : 9 : FundamentalPrototype* priv = FundamentalPrototype::create_class(
376 : : cx, in_object, info, gtype, constructor, &prototype);
377 [ - + ]: 9 : if (!priv)
378 : 0 : return false;
379 : :
380 [ + + ]: 9 : if (g_object_info_get_n_fields(info) > 0) {
381 : 8 : gjs_debug(GJS_DEBUG_GFUNDAMENTAL,
382 : : "Fundamental type '%s.%s' apparently has accessible fields. "
383 : : "Gjs has no support for this yet, ignoring these.",
384 : : priv->ns(), priv->name());
385 : : }
386 : :
387 : 9 : return true;
388 : 9 : }
389 : :
390 : : /*
391 : : * FundamentalInstance::object_for_c_ptr:
392 : : *
393 : : * Given a pointer to a C fundamental object, returns a JS object. This JS
394 : : * object may have been cached, or it may be newly created.
395 : : */
396 : 13 : JSObject* FundamentalInstance::object_for_c_ptr(JSContext* context,
397 : : void* gfundamental) {
398 [ - + ]: 13 : if (!gfundamental) {
399 : 0 : gjs_throw(context, "Cannot get JSObject for null fundamental pointer");
400 : 0 : return nullptr;
401 : : }
402 : :
403 : 13 : GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
404 : 13 : auto p = gjs->fundamental_table().lookup(gfundamental);
405 [ + + ]: 13 : if (p)
406 : 11 : return p->value();
407 : :
408 : : gjs_debug_marshal(GJS_DEBUG_GFUNDAMENTAL,
409 : : "Wrapping fundamental %p with JSObject", gfundamental);
410 : :
411 : : JS::RootedObject proto(context,
412 : 6 : gjs_lookup_fundamental_prototype_from_gtype(context,
413 : 2 : G_TYPE_FROM_INSTANCE(gfundamental)));
414 [ - + ]: 2 : if (!proto)
415 : 0 : return nullptr;
416 : :
417 : 4 : JS::RootedObject object(context, JS_NewObjectWithGivenProto(
418 : 4 : context, JS::GetClass(proto), proto));
419 : :
420 [ - + ]: 2 : if (!object)
421 : 0 : return nullptr;
422 : :
423 : 2 : auto* priv = FundamentalInstance::new_for_js_object(context, object);
424 : :
425 [ - + ]: 2 : if (!priv->associate_js_instance(context, object, gfundamental))
426 : 0 : return nullptr;
427 : :
428 : 2 : return object;
429 : 2 : }
430 : :
431 : : /*
432 : : * FundamentalPrototype::for_gtype:
433 : : *
434 : : * Returns the FundamentalPrototype instance associated with the given GType.
435 : : * Use this if you don't have the prototype object.
436 : : */
437 : 28 : FundamentalPrototype* FundamentalPrototype::for_gtype(JSContext* cx,
438 : : GType gtype) {
439 : : JS::RootedObject proto(
440 : 28 : cx, gjs_lookup_fundamental_prototype_from_gtype(cx, gtype));
441 [ - + ]: 28 : if (!proto)
442 : 0 : return nullptr;
443 : :
444 : 28 : return FundamentalPrototype::for_js(cx, proto);
445 : 28 : }
446 : :
447 : 12 : bool FundamentalInstance::object_for_gvalue(
448 : : JSContext* cx, const GValue* value, GType gtype,
449 : : JS::MutableHandleObject object_out) {
450 : 12 : auto* proto_priv = FundamentalPrototype::for_gtype(cx, gtype);
451 : 12 : void* fobj = nullptr;
452 : :
453 [ + + ]: 12 : if (!proto_priv->call_get_value_function(value, &fobj)) {
454 [ - + + - : 6 : if (!G_VALUE_HOLDS(value, gtype) || !g_value_fits_pointer(value)) {
+ - - + -
+ ]
455 : 0 : gjs_throw(cx,
456 : : "Failed to convert GValue of type %s to a fundamental %s "
457 : : "instance",
458 : : G_VALUE_TYPE_NAME(value), g_type_name(gtype));
459 : 0 : return false;
460 : : }
461 : :
462 : 6 : fobj = g_value_peek_pointer(value);
463 : : }
464 : :
465 [ - + ]: 12 : if (!fobj) {
466 : 0 : object_out.set(nullptr);
467 : 0 : return true;
468 : : }
469 : :
470 : 12 : object_out.set(FundamentalInstance::object_for_c_ptr(cx, fobj));
471 : 12 : return object_out.get() != nullptr;
472 : : }
473 : :
474 : 11 : bool FundamentalBase::to_gvalue(JSContext* cx, JS::HandleObject obj,
475 : : GValue* gvalue) {
476 : : FundamentalBase* priv;
477 [ + - - + ]: 22 : if (!for_js_typecheck(cx, obj, &priv) ||
478 [ - + ]: 11 : !priv->check_is_instance(cx, "convert to GValue"))
479 : 0 : return false;
480 : :
481 : 11 : auto* instance = priv->to_instance();
482 [ + + ]: 11 : if (!instance->set_value(gvalue)) {
483 [ + + ]: 7 : if (g_value_type_compatible(instance->gtype(), G_VALUE_TYPE(gvalue))) {
484 : 4 : g_value_set_instance(gvalue, instance->m_ptr);
485 : 4 : return true;
486 [ + + ]: 3 : } else if (g_value_type_transformable(instance->gtype(),
487 : : G_VALUE_TYPE(gvalue))) {
488 : 1 : Gjs::AutoGValue instance_value;
489 : 1 : g_value_init(&instance_value, instance->gtype());
490 : 1 : g_value_set_instance(&instance_value, instance->m_ptr);
491 [ + - ]: 1 : if (g_value_transform(&instance_value, gvalue))
492 : 1 : return true;
493 [ - + ]: 1 : }
494 : :
495 : 2 : gjs_throw(cx,
496 : : "Fundamental object of type %s does not support conversion "
497 : : "to a GValue of type %s",
498 : : instance->type_name(), G_VALUE_TYPE_NAME(gvalue));
499 : 2 : return false;
500 : : }
501 : :
502 : 4 : return true;
503 : : }
504 : :
505 : 0 : void* FundamentalInstance::copy_ptr(JSContext* cx, GType gtype,
506 : : void* gfundamental) {
507 : 0 : auto* priv = FundamentalPrototype::for_gtype(cx, gtype);
508 : 0 : return priv->call_ref_function(gfundamental);
509 : : }
|