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