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 <stdint.h>
8 #include <string.h>  // for strlen
9 
10 #if GJS_VERBOSE_ENABLE_GI_USAGE
11 #    include <string>
12 #endif
13 
14 #include <girepository.h>
15 #include <glib-object.h>
16 #include <glib.h>
17 
18 #include <js/Class.h>
19 #include <js/ComparisonOperators.h>
20 #include <js/Id.h>                  // for JSID_IS_STRING, JSID_VOID
21 #include <js/PropertyDescriptor.h>  // for JSPROP_PERMANENT, JSPROP_RESOLVING
22 #include <js/RootingAPI.h>
23 #include <js/TypeDecls.h>
24 #include <js/Utility.h>  // for UniqueChars
25 #include <js/Value.h>
26 #include <js/ValueArray.h>
27 #include <js/Warnings.h>
28 #include <jsapi.h>  // for JS_DefinePropertyById, JS_GetProp...
29 
30 #include "gi/arg.h"
31 #include "gi/boxed.h"
32 #include "gi/enumeration.h"
33 #include "gi/function.h"
34 #include "gi/fundamental.h"
35 #include "gi/gerror.h"
36 #include "gi/interface.h"
37 #include "gi/ns.h"
38 #include "gi/object.h"
39 #include "gi/param.h"
40 #include "gi/repo.h"
41 #include "gi/union.h"
42 #include "gjs/atoms.h"
43 #include "gjs/context-private.h"
44 #include "gjs/global.h"
45 #include "gjs/jsapi-util.h"
46 #include "gjs/module.h"
47 #include "util/log.h"
48 
49 GJS_JSAPI_RETURN_CONVENTION
50 static bool lookup_override_function(JSContext *, JS::HandleId,
51                                      JS::MutableHandleValue);
52 
53 GJS_JSAPI_RETURN_CONVENTION
get_version_for_ns(JSContext * context,JS::HandleObject repo_obj,JS::HandleId ns_id,JS::UniqueChars * version)54 static bool get_version_for_ns(JSContext* context, JS::HandleObject repo_obj,
55                                JS::HandleId ns_id, JS::UniqueChars* version) {
56     JS::RootedObject versions(context);
57     bool found;
58     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
59 
60     if (!gjs_object_require_property(context, repo_obj, "GI repository object",
61                                      atoms.versions(), &versions))
62         return false;
63 
64     if (!JS_AlreadyHasOwnPropertyById(context, versions, ns_id, &found))
65         return false;
66 
67     if (!found)
68         return true;
69 
70     return gjs_object_require_property(context, versions, NULL, ns_id, version);
71 }
72 
strlist_free(GList * l)73 static void strlist_free(GList* l) { g_list_free_full(l, g_free); }
74 
75 GJS_JSAPI_RETURN_CONVENTION
resolve_namespace_object(JSContext * context,JS::HandleObject repo_obj,JS::HandleId ns_id)76 static bool resolve_namespace_object(JSContext* context,
77                                      JS::HandleObject repo_obj,
78                                      JS::HandleId ns_id) {
79     GError *error;
80 
81     JS::UniqueChars version;
82     if (!get_version_for_ns(context, repo_obj, ns_id, &version))
83         return false;
84 
85     JS::UniqueChars ns_name;
86     if (!gjs_get_string_id(context, ns_id, &ns_name))
87         return false;
88     if (!ns_name) {
89         gjs_throw(context, "Requiring invalid namespace on imports.gi");
90         return false;
91     }
92 
93     GjsAutoPointer<GList, GList, strlist_free> versions =
94         g_irepository_enumerate_versions(nullptr, ns_name.get());
95     unsigned nversions = g_list_length(versions);
96     if (nversions > 1 && !version &&
97         !g_irepository_is_registered(nullptr, ns_name.get(), nullptr) &&
98         !JS::WarnUTF8(context,
99                       "Requiring %s but it has %u versions available; use "
100                       "imports.gi.versions to pick one",
101                       ns_name.get(), nversions))
102         return false;
103 
104     error = NULL;
105     g_irepository_require(nullptr, ns_name.get(), version.get(),
106                           GIRepositoryLoadFlags(0), &error);
107     if (error != NULL) {
108         gjs_throw(context, "Requiring %s, version %s: %s", ns_name.get(),
109                   version ? version.get() : "none", error->message);
110 
111         g_error_free(error);
112         return false;
113     }
114 
115     /* Defines a property on "obj" (the javascript repo object)
116      * with the given namespace name, pointing to that namespace
117      * in the repo.
118      */
119     JS::RootedObject gi_namespace(context,
120                                   gjs_create_ns(context, ns_name.get()));
121 
122     /* Define the property early, to avoid reentrancy issues if
123        the override module looks for namespaces that import this */
124     if (!JS_DefinePropertyById(context, repo_obj, ns_id, gi_namespace,
125                                GJS_MODULE_PROP_FLAGS))
126         return false;
127 
128     JS::RootedValue override(context);
129     if (!lookup_override_function(context, ns_id, &override))
130         return false;
131 
132     JS::RootedValue result(context);
133     if (!override.isUndefined() &&
134         !JS_CallFunctionValue (context, gi_namespace, /* thisp */
135                                override, /* callee */
136                                JS::HandleValueArray::empty(), &result))
137         return false;
138 
139     gjs_debug(GJS_DEBUG_GNAMESPACE,
140               "Defined namespace '%s' %p in GIRepository %p", ns_name.get(),
141               gi_namespace.get(), repo_obj.get());
142 
143     GjsContextPrivate* gjs = GjsContextPrivate::from_cx(context);
144     gjs->schedule_gc_if_needed();
145     return true;
146 }
147 
148 /*
149  * The *resolved out parameter, on success, should be false to indicate that id
150  * was not resolved; and true if id was resolved.
151  */
152 GJS_JSAPI_RETURN_CONVENTION
153 static bool
repo_resolve(JSContext * context,JS::HandleObject obj,JS::HandleId id,bool * resolved)154 repo_resolve(JSContext       *context,
155              JS::HandleObject obj,
156              JS::HandleId     id,
157              bool            *resolved)
158 {
159     if (!JSID_IS_STRING(id)) {
160         *resolved = false;
161         return true; /* not resolved, but no error */
162     }
163 
164     /* let Object.prototype resolve these */
165     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
166     if (id == atoms.to_string() || id == atoms.value_of()) {
167         *resolved = false;
168         return true;
169     }
170 
171     gjs_debug_jsprop(GJS_DEBUG_GREPO, "Resolve prop '%s' hook, obj %s",
172                      gjs_debug_id(id).c_str(), gjs_debug_object(obj).c_str());
173 
174     if (!resolve_namespace_object(context, obj, id))
175         return false;
176 
177     *resolved = true;
178     return true;
179 }
180 
181 static const struct JSClassOps gjs_repo_class_ops = {
182     nullptr,  // addProperty
183     nullptr,  // deleteProperty
184     nullptr,  // enumerate
185     nullptr,  // newEnumerate
186     repo_resolve,
187 };
188 
189 struct JSClass gjs_repo_class = {
190     "GIRepository",
191     0,
192     &gjs_repo_class_ops,
193 };
194 
195 GJS_JSAPI_RETURN_CONVENTION
196 static JSObject*
repo_new(JSContext * context)197 repo_new(JSContext *context)
198 {
199     JS::RootedObject repo(context, JS_NewObject(context, &gjs_repo_class));
200     if (repo == nullptr)
201         return nullptr;
202 
203     gjs_debug_lifecycle(GJS_DEBUG_GREPO, "repo constructor, obj %p",
204                         repo.get());
205 
206     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
207     JS::RootedObject versions(context, JS_NewPlainObject(context));
208     if (!JS_DefinePropertyById(context, repo, atoms.versions(), versions,
209                                JSPROP_PERMANENT | JSPROP_RESOLVING))
210         return nullptr;
211 
212     /* GLib/GObject/Gio are fixed at 2.0, since we depend on them
213      * internally.
214      */
215     JS::RootedString two_point_oh(context, JS_NewStringCopyZ(context, "2.0"));
216     if (!JS_DefinePropertyById(context, versions, atoms.glib(), two_point_oh,
217                                JSPROP_PERMANENT))
218         return nullptr;
219     if (!JS_DefinePropertyById(context, versions, atoms.gobject(), two_point_oh,
220                                JSPROP_PERMANENT))
221         return nullptr;
222     if (!JS_DefinePropertyById(context, versions, atoms.gio(), two_point_oh,
223                                JSPROP_PERMANENT))
224         return nullptr;
225 
226     JS::RootedObject private_ns(context, JS_NewPlainObject(context));
227     if (!JS_DefinePropertyById(context, repo, atoms.private_ns_marker(),
228                                private_ns, JSPROP_PERMANENT | JSPROP_RESOLVING))
229         return nullptr;
230 
231     return repo;
232 }
233 
234 bool
gjs_define_repo(JSContext * cx,JS::MutableHandleObject repo)235 gjs_define_repo(JSContext              *cx,
236                 JS::MutableHandleObject repo)
237 {
238     repo.set(repo_new(cx));
239     return true;
240 }
241 
242 GJS_JSAPI_RETURN_CONVENTION
gjs_value_from_constant_info(JSContext * cx,GIConstantInfo * info,JS::MutableHandleValue value)243 static bool gjs_value_from_constant_info(JSContext* cx, GIConstantInfo* info,
244                                          JS::MutableHandleValue value) {
245     GIArgument garg;
246     g_constant_info_get_value(info, &garg);
247 
248     GjsAutoTypeInfo type_info = g_constant_info_get_type(info);
249 
250     bool ok = gjs_value_from_g_argument(cx, value, type_info, &garg, true);
251 
252     g_constant_info_free_value(info, &garg);
253     return ok;
254 }
255 
256 GJS_JSAPI_RETURN_CONVENTION
257 static bool
gjs_define_constant(JSContext * context,JS::HandleObject in_object,GIConstantInfo * info)258 gjs_define_constant(JSContext       *context,
259                     JS::HandleObject in_object,
260                     GIConstantInfo  *info)
261 {
262     JS::RootedValue value(context);
263     const char *name;
264 
265     if (!gjs_value_from_constant_info(context, info, &value))
266         return false;
267 
268     name = g_base_info_get_name((GIBaseInfo*) info);
269 
270     return JS_DefineProperty(context, in_object, name, value,
271                              GJS_MODULE_PROP_FLAGS);
272 }
273 
274 #if GJS_VERBOSE_ENABLE_GI_USAGE
275 void
_gjs_log_info_usage(GIBaseInfo * info)276 _gjs_log_info_usage(GIBaseInfo *info)
277 {
278 #define DIRECTION_STRING(d) ( ((d) == GI_DIRECTION_IN) ? "IN" : ((d) == GI_DIRECTION_OUT) ? "OUT" : "INOUT" )
279 #define TRANSFER_STRING(t) ( ((t) == GI_TRANSFER_NOTHING) ? "NOTHING" : ((t) == GI_TRANSFER_CONTAINER) ? "CONTAINER" : "EVERYTHING" )
280 
281     {
282         char *details;
283         GIInfoType info_type;
284         GIBaseInfo *container;
285 
286         info_type = g_base_info_get_type(info);
287 
288         if (info_type == GI_INFO_TYPE_FUNCTION) {
289             std::string args("{ ");
290             int n_args;
291             int i;
292             GITransfer retval_transfer;
293 
294             n_args = g_callable_info_get_n_args((GICallableInfo*) info);
295             for (i = 0; i < n_args; ++i) {
296                 GIArgInfo *arg;
297                 GIDirection direction;
298                 GITransfer transfer;
299 
300                 arg = g_callable_info_get_arg((GICallableInfo*)info, i);
301                 direction = g_arg_info_get_direction(arg);
302                 transfer = g_arg_info_get_ownership_transfer(arg);
303 
304                 if (i > 0)
305                     args += ", ";
306 
307                 args += std::string("{ GI_DIRECTION_") +
308                         DIRECTION_STRING(direction) + ", GI_TRANSFER_" +
309                         TRANSFER_STRING(transfer) + " }";
310 
311                 g_base_info_unref((GIBaseInfo*) arg);
312             }
313 
314             args += " }";
315 
316             retval_transfer = g_callable_info_get_caller_owns((GICallableInfo*) info);
317 
318             details = g_strdup_printf(
319                 ".details = { .func = { .retval_transfer = GI_TRANSFER_%s, "
320                 ".n_args = %d, .args = %s } }",
321                 TRANSFER_STRING(retval_transfer), n_args, args.c_str());
322         } else {
323             details = g_strdup_printf(".details = { .nothing = {} }");
324         }
325 
326         container = g_base_info_get_container(info);
327 
328         gjs_debug_gi_usage("{ GI_INFO_TYPE_%s, \"%s\", \"%s\", \"%s\", %s },",
329                            gjs_info_type_name(info_type),
330                            g_base_info_get_namespace(info),
331                            container ? g_base_info_get_name(container) : "",
332                            g_base_info_get_name(info),
333                            details);
334         g_free(details);
335     }
336 }
337 #endif /* GJS_VERBOSE_ENABLE_GI_USAGE */
338 
339 bool
gjs_define_info(JSContext * context,JS::HandleObject in_object,GIBaseInfo * info,bool * defined)340 gjs_define_info(JSContext       *context,
341                 JS::HandleObject in_object,
342                 GIBaseInfo      *info,
343                 bool            *defined)
344 {
345 #if GJS_VERBOSE_ENABLE_GI_USAGE
346     _gjs_log_info_usage(info);
347 #endif
348 
349     *defined = true;
350 
351     switch (g_base_info_get_type(info)) {
352     case GI_INFO_TYPE_FUNCTION:
353         {
354             JSObject *f;
355             f = gjs_define_function(context, in_object, 0, (GICallableInfo*) info);
356             if (f == NULL)
357                 return false;
358         }
359         break;
360     case GI_INFO_TYPE_OBJECT:
361         {
362             GType gtype;
363             gtype = g_registered_type_info_get_g_type((GIRegisteredTypeInfo*)info);
364 
365             if (g_type_is_a (gtype, G_TYPE_PARAM)) {
366                 if (!gjs_define_param_class(context, in_object))
367                     return false;
368             } else if (g_type_is_a (gtype, G_TYPE_OBJECT)) {
369                 JS::RootedObject ignored1(context), ignored2(context);
370                 if (!ObjectPrototype::define_class(context, in_object, info,
371                                                    gtype, &ignored1, &ignored2))
372                     return false;
373             } else if (G_TYPE_IS_INSTANTIATABLE(gtype)) {
374                 JS::RootedObject ignored(context);
375                 if (!FundamentalPrototype::define_class(context, in_object,
376                                                         info, &ignored))
377                     return false;
378             } else {
379                 gjs_throw (context,
380                            "Unsupported type %s, deriving from fundamental %s",
381                            g_type_name(gtype), g_type_name(g_type_fundamental(gtype)));
382                 return false;
383             }
384         }
385         break;
386     case GI_INFO_TYPE_STRUCT:
387         /* We don't want GType structures in the namespace,
388            we expose their fields as vfuncs and their methods
389            as static methods
390         */
391         if (g_struct_info_is_gtype_struct((GIStructInfo*) info)) {
392             *defined = false;
393             break;
394         }
395         /* Fall through */
396 
397     case GI_INFO_TYPE_BOXED:
398         if (!BoxedPrototype::define_class(context, in_object, info))
399             return false;
400         break;
401     case GI_INFO_TYPE_UNION:
402         if (!gjs_define_union_class(context, in_object, (GIUnionInfo*) info))
403             return false;
404         break;
405     case GI_INFO_TYPE_ENUM:
406         if (g_enum_info_get_error_domain((GIEnumInfo*) info)) {
407             /* define as GError subclass */
408             if (!ErrorPrototype::define_class(context, in_object, info))
409                 return false;
410             break;
411         }
412         [[fallthrough]];
413 
414     case GI_INFO_TYPE_FLAGS:
415         if (!gjs_define_enumeration(context, in_object, (GIEnumInfo*) info))
416             return false;
417         break;
418     case GI_INFO_TYPE_CONSTANT:
419         if (!gjs_define_constant(context, in_object, (GIConstantInfo*) info))
420             return false;
421         break;
422     case GI_INFO_TYPE_INTERFACE:
423         {
424             JS::RootedObject ignored1(context), ignored2(context);
425             if (!InterfacePrototype::create_class(
426                     context, in_object, info,
427                     g_registered_type_info_get_g_type(info), &ignored1,
428                     &ignored2))
429                 return false;
430         }
431         break;
432     case GI_INFO_TYPE_INVALID:
433     case GI_INFO_TYPE_INVALID_0:
434     case GI_INFO_TYPE_CALLBACK:
435     case GI_INFO_TYPE_VALUE:
436     case GI_INFO_TYPE_SIGNAL:
437     case GI_INFO_TYPE_VFUNC:
438     case GI_INFO_TYPE_PROPERTY:
439     case GI_INFO_TYPE_FIELD:
440     case GI_INFO_TYPE_ARG:
441     case GI_INFO_TYPE_TYPE:
442     case GI_INFO_TYPE_UNRESOLVED:
443     default:
444         gjs_throw(context, "API of type %s not implemented, cannot define %s.%s",
445                   gjs_info_type_name(g_base_info_get_type(info)),
446                   g_base_info_get_namespace(info),
447                   g_base_info_get_name(info));
448         return false;
449     }
450 
451     return true;
452 }
453 
454 /* Get the "unknown namespace", which should be used for unnamespaced types */
455 JSObject*
gjs_lookup_private_namespace(JSContext * context)456 gjs_lookup_private_namespace(JSContext *context)
457 {
458     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
459     return gjs_lookup_namespace_object_by_name(context,
460                                                atoms.private_ns_marker());
461 }
462 
463 /* Get the namespace object that the GIBaseInfo should be inside */
464 JSObject*
gjs_lookup_namespace_object(JSContext * context,GIBaseInfo * info)465 gjs_lookup_namespace_object(JSContext  *context,
466                             GIBaseInfo *info)
467 {
468     const char *ns;
469 
470     ns = g_base_info_get_namespace(info);
471     if (ns == NULL) {
472         gjs_throw(context, "%s '%s' does not have a namespace",
473                      gjs_info_type_name(g_base_info_get_type(info)),
474                      g_base_info_get_name(info));
475 
476         return NULL;
477     }
478 
479     JS::RootedId ns_name(context, gjs_intern_string_to_id(context, ns));
480     if (ns_name == JSID_VOID)
481         return nullptr;
482     return gjs_lookup_namespace_object_by_name(context, ns_name);
483 }
484 
485 /* Check if an exception's 'name' property is equal to compare_name. Ignores
486  * all errors that might arise. */
error_has_name(JSContext * cx,JS::HandleValue thrown_value,JSString * compare_name)487 [[nodiscard]] static bool error_has_name(JSContext* cx,
488                                          JS::HandleValue thrown_value,
489                                          JSString* compare_name) {
490     if (!thrown_value.isObject())
491         return false;
492 
493     JS::AutoSaveExceptionState saved_exc(cx);
494     JS::RootedObject exc(cx, &thrown_value.toObject());
495     JS::RootedValue exc_name(cx);
496     bool retval = false;
497     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
498 
499     if (!JS_GetPropertyById(cx, exc, atoms.name(), &exc_name))
500         goto out;
501 
502     int32_t cmp_result;
503     if (!JS_CompareStrings(cx, exc_name.toString(), compare_name, &cmp_result))
504         goto out;
505 
506     if (cmp_result == 0)
507         retval = true;
508 
509 out:
510     saved_exc.restore();
511     return retval;
512 }
513 
514 GJS_JSAPI_RETURN_CONVENTION
515 static bool
lookup_override_function(JSContext * cx,JS::HandleId ns_name,JS::MutableHandleValue function)516 lookup_override_function(JSContext             *cx,
517                          JS::HandleId           ns_name,
518                          JS::MutableHandleValue function)
519 {
520     JS::AutoSaveExceptionState saved_exc(cx);
521 
522     JS::RootedObject global(cx, gjs_get_import_global(cx));
523     JS::RootedValue importer(
524         cx, gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS));
525     g_assert(importer.isObject());
526 
527     JS::RootedObject overridespkg(cx), module(cx);
528     JS::RootedObject importer_obj(cx, &importer.toObject());
529     const GjsAtoms& atoms = GjsContextPrivate::atoms(cx);
530     if (!gjs_object_require_property(cx, importer_obj, "importer",
531                                      atoms.overrides(), &overridespkg))
532         goto fail;
533 
534     if (!gjs_object_require_property(cx, overridespkg,
535                                      "GI repository object", ns_name,
536                                      &module)) {
537         JS::RootedValue exc(cx);
538         JS_GetPendingException(cx, &exc);
539 
540         /* If the exception was an ImportError (i.e., module not found) then
541          * we simply didn't have an override, don't throw an exception */
542         if (error_has_name(cx, exc, JS_AtomizeAndPinString(cx, "ImportError"))) {
543             saved_exc.restore();
544             return true;
545         }
546 
547         goto fail;
548     }
549 
550     if (!gjs_object_require_property(cx, module, "override module",
551                                      atoms.init(), function) ||
552         !function.isObjectOrNull()) {
553         gjs_throw(cx, "Unexpected value for _init in overrides module");
554         goto fail;
555     }
556     return true;
557 
558 fail:
559     saved_exc.drop();
560     return false;
561 }
562 
563 GJS_JSAPI_RETURN_CONVENTION
lookup_namespace(JSContext * cx,JSObject * global,JS::HandleId ns_name)564 static JSObject* lookup_namespace(JSContext* cx, JSObject* global,
565                                   JS::HandleId ns_name) {
566     JS::RootedObject native_registry(cx, gjs_get_native_registry(global));
567     auto priv = GjsContextPrivate::from_cx(cx);
568     const GjsAtoms& atoms = priv->atoms();
569     JS::RootedObject gi(cx);
570 
571     if (!gjs_global_registry_get(cx, native_registry, atoms.gi(), &gi))
572         return nullptr;
573 
574     if (!gi) {
575         gjs_throw(cx, "No gi property in native registry");
576         return nullptr;
577     }
578 
579     JS::RootedObject retval(cx);
580     if (!gjs_object_require_property(cx, gi, "GI repository object", ns_name,
581                                      &retval))
582         return NULL;
583 
584     return retval;
585 }
586 
gjs_lookup_namespace_object_by_name(JSContext * cx,JS::HandleId ns_name)587 JSObject* gjs_lookup_namespace_object_by_name(JSContext* cx,
588                                               JS::HandleId ns_name) {
589     JS::RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
590 
591     g_assert(gjs_global_get_type(global) == GjsGlobalType::DEFAULT);
592     return lookup_namespace(cx, global, ns_name);
593 }
594 
595 const char*
gjs_info_type_name(GIInfoType type)596 gjs_info_type_name(GIInfoType type)
597 {
598     switch (type) {
599     case GI_INFO_TYPE_INVALID:
600         return "INVALID";
601     case GI_INFO_TYPE_FUNCTION:
602         return "FUNCTION";
603     case GI_INFO_TYPE_CALLBACK:
604         return "CALLBACK";
605     case GI_INFO_TYPE_STRUCT:
606         return "STRUCT";
607     case GI_INFO_TYPE_BOXED:
608         return "BOXED";
609     case GI_INFO_TYPE_ENUM:
610         return "ENUM";
611     case GI_INFO_TYPE_FLAGS:
612         return "FLAGS";
613     case GI_INFO_TYPE_OBJECT:
614         return "OBJECT";
615     case GI_INFO_TYPE_INTERFACE:
616         return "INTERFACE";
617     case GI_INFO_TYPE_CONSTANT:
618         return "CONSTANT";
619     case GI_INFO_TYPE_UNION:
620         return "UNION";
621     case GI_INFO_TYPE_VALUE:
622         return "VALUE";
623     case GI_INFO_TYPE_SIGNAL:
624         return "SIGNAL";
625     case GI_INFO_TYPE_VFUNC:
626         return "VFUNC";
627     case GI_INFO_TYPE_PROPERTY:
628         return "PROPERTY";
629     case GI_INFO_TYPE_FIELD:
630         return "FIELD";
631     case GI_INFO_TYPE_ARG:
632         return "ARG";
633     case GI_INFO_TYPE_TYPE:
634         return "TYPE";
635     case GI_INFO_TYPE_UNRESOLVED:
636         return "UNRESOLVED";
637     case GI_INFO_TYPE_INVALID_0:
638         g_assert_not_reached();
639         return "----INVALID0----";
640     default:
641       return "???";
642     }
643 }
644 
645 char*
gjs_hyphen_from_camel(const char * camel_name)646 gjs_hyphen_from_camel(const char *camel_name)
647 {
648     GString *s;
649     const char *p;
650 
651     /* four hyphens should be reasonable guess */
652     s = g_string_sized_new(strlen(camel_name) + 4 + 1);
653 
654     for (p = camel_name; *p; ++p) {
655         if (g_ascii_isupper(*p)) {
656             g_string_append_c(s, '-');
657             g_string_append_c(s, g_ascii_tolower(*p));
658         } else {
659             g_string_append_c(s, *p);
660         }
661     }
662 
663     return g_string_free(s, false);
664 }
665 
666 JSObject *
gjs_lookup_generic_constructor(JSContext * context,GIBaseInfo * info)667 gjs_lookup_generic_constructor(JSContext  *context,
668                                GIBaseInfo *info)
669 {
670     const char *constructor_name;
671 
672     JS::RootedObject in_object(context,
673         gjs_lookup_namespace_object(context, (GIBaseInfo*) info));
674     constructor_name = g_base_info_get_name((GIBaseInfo*) info);
675 
676     if (G_UNLIKELY (!in_object))
677         return NULL;
678 
679     JS::RootedValue value(context);
680     if (!JS_GetProperty(context, in_object, constructor_name, &value))
681         return NULL;
682 
683     if (G_UNLIKELY(!value.isObject())) {
684         gjs_throw(context,
685                   "Constructor of %s.%s was the wrong type, expected an object",
686                   g_base_info_get_namespace(info), constructor_name);
687         return NULL;
688     }
689 
690     return &value.toObject();
691 }
692 
693 JSObject *
gjs_lookup_generic_prototype(JSContext * context,GIBaseInfo * info)694 gjs_lookup_generic_prototype(JSContext  *context,
695                              GIBaseInfo *info)
696 {
697     JS::RootedObject constructor(context,
698                                  gjs_lookup_generic_constructor(context, info));
699     if (G_UNLIKELY(!constructor))
700         return NULL;
701 
702     const GjsAtoms& atoms = GjsContextPrivate::atoms(context);
703     JS::RootedValue value(context);
704     if (!JS_GetPropertyById(context, constructor, atoms.prototype(), &value))
705         return NULL;
706 
707     if (G_UNLIKELY(!value.isObject())) {
708         gjs_throw(context,
709                   "Prototype of %s.%s was the wrong type, expected an object",
710                   g_base_info_get_namespace(info), g_base_info_get_name(info));
711         return NULL;
712     }
713 
714     return &value.toObject();
715 }
716 
gjs_new_object_with_generic_prototype(JSContext * cx,GIBaseInfo * info)717 JSObject* gjs_new_object_with_generic_prototype(JSContext* cx,
718                                                 GIBaseInfo* info) {
719     JS::RootedObject proto(cx, gjs_lookup_generic_prototype(cx, info));
720     if (!proto)
721         return nullptr;
722 
723     return JS_NewObjectWithGivenProto(cx, JS_GetClass(proto), proto);
724 }
725