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