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