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 <signal.h>  // for sigaction, SIGUSR1, sa_handler
8 #include <stdint.h>
9 #include <stdio.h>      // for FILE, fclose, size_t
10 #include <string.h>     // for memset
11 
12 #ifdef HAVE_UNISTD_H
13 #    include <unistd.h>  // for getpid
14 #elif defined (_WIN32)
15 #    include <process.h>
16 #endif
17 
18 #ifdef DEBUG
19 #    include <algorithm>  // for find
20 #endif
21 #include <atomic>
22 #include <new>
23 #include <string>       // for u16string
24 #include <thread>       // for get_id
25 #include <unordered_map>
26 #include <utility>  // for move
27 #include <vector>
28 
29 #include <gio/gio.h>
30 #include <girepository.h>
31 #include <glib-object.h>
32 #include <glib.h>
33 
34 #ifdef G_OS_WIN32
35 #include <windows.h>
36 #endif
37 
38 #include <js/AllocPolicy.h>  // for SystemAllocPolicy
39 #include <js/CallArgs.h>     // for UndefinedHandleValue
40 #include <js/CharacterEncoding.h>
41 #include <js/CompilationAndEvaluation.h>
42 #include <js/CompileOptions.h>
43 #include <js/ErrorReport.h>
44 #include <js/Exception.h>           // for StealPendingExceptionStack
45 #include <js/GCAPI.h>               // for JS_GC, JS_AddExtraGCRootsTr...
46 #include <js/GCHashTable.h>         // for WeakCache
47 #include <js/GCVector.h>            // for RootedVector
48 #include <js/Id.h>
49 #include <js/Modules.h>
50 #include <js/Promise.h>             // for JobQueue::SavedJobQueue
51 #include <js/PropertyDescriptor.h>  // for JSPROP_PERMANENT, JSPROP_RE...
52 #include <js/RootingAPI.h>
53 #include <js/SourceText.h>
54 #include <js/TracingAPI.h>
55 #include <js/TypeDecls.h>
56 #include <js/UniquePtr.h>
57 #include <js/Value.h>
58 #include <js/ValueArray.h>
59 #include <jsapi.h>        // for JS_IsExceptionPending, ...
60 #include <jsfriendapi.h>  // for DumpHeap, IgnoreNurseryObjects
61 #include <mozilla/UniquePtr.h>
62 
63 #include "gi/closure.h"  // for Closure::Ptr, Closure
64 #include "gi/object.h"
65 #include "gi/private.h"
66 #include "gi/repo.h"
67 #include "gi/utils-inl.h"
68 #include "gjs/atoms.h"
69 #include "gjs/byteArray.h"
70 #include "gjs/context-private.h"
71 #include "gjs/context.h"
72 #include "gjs/engine.h"
73 #include "gjs/error-types.h"
74 #include "gjs/global.h"
75 #include "gjs/importer.h"
76 #include "gjs/internal.h"
77 #include "gjs/jsapi-util.h"
78 #include "gjs/mem.h"
79 #include "gjs/module.h"
80 #include "gjs/native.h"
81 #include "gjs/objectbox.h"
82 #include "gjs/profiler-private.h"
83 #include "gjs/profiler.h"
84 #include "gjs/text-encoding.h"
85 #include "modules/modules.h"
86 #include "util/log.h"
87 
88 static void     gjs_context_dispose           (GObject               *object);
89 static void     gjs_context_finalize          (GObject               *object);
90 static void     gjs_context_constructed       (GObject               *object);
91 static void     gjs_context_get_property      (GObject               *object,
92                                                   guint                  prop_id,
93                                                   GValue                *value,
94                                                   GParamSpec            *pspec);
95 static void     gjs_context_set_property      (GObject               *object,
96                                                   guint                  prop_id,
97                                                   const GValue          *value,
98                                                   GParamSpec            *pspec);
99 
invoke(JS::HandleObject scope,Closure & closure)100 void GjsContextPrivate::EnvironmentPreparer::invoke(JS::HandleObject scope,
101                                                     Closure& closure) {
102     g_assert(!JS_IsExceptionPending(m_cx));
103 
104     JSAutoRealm ar(m_cx, scope);
105     if (!closure(m_cx))
106         gjs_log_exception(m_cx);
107 }
108 
109 struct _GjsContext {
110     GObject parent;
111 };
112 
113 struct _GjsContextClass {
114     GObjectClass parent;
115 };
116 
117 G_DEFINE_TYPE_WITH_PRIVATE(GjsContext, gjs_context, G_TYPE_OBJECT);
118 
from_object(GObject * js_context)119 GjsContextPrivate* GjsContextPrivate::from_object(GObject* js_context) {
120     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr);
121     return static_cast<GjsContextPrivate*>(
122         gjs_context_get_instance_private(GJS_CONTEXT(js_context)));
123 }
124 
from_object(GjsContext * js_context)125 GjsContextPrivate* GjsContextPrivate::from_object(GjsContext* js_context) {
126     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), nullptr);
127     return static_cast<GjsContextPrivate*>(
128         gjs_context_get_instance_private(js_context));
129 }
130 
from_current_context()131 GjsContextPrivate* GjsContextPrivate::from_current_context() {
132     return from_object(gjs_context_get_current());
133 }
134 
135 enum {
136     PROP_CONTEXT_0,
137     PROP_PROGRAM_PATH,
138     PROP_SEARCH_PATH,
139     PROP_PROGRAM_NAME,
140     PROP_PROFILER_ENABLED,
141     PROP_PROFILER_SIGUSR2,
142     PROP_EXEC_AS_MODULE,
143 };
144 
145 static GMutex contexts_lock;
146 static GList *all_contexts = NULL;
147 
148 static GjsAutoChar dump_heap_output;
149 static unsigned dump_heap_idle_id = 0;
150 
151 #ifdef G_OS_UNIX
152 // Currently heap dumping via SIGUSR1 is only supported on UNIX platforms!
153 // This can reduce performance. See note in system.cpp on System.dumpHeap().
154 static void
gjs_context_dump_heaps(void)155 gjs_context_dump_heaps(void)
156 {
157     static unsigned counter = 0;
158 
159     gjs_memory_report("signal handler", false);
160 
161     /* dump to sequential files to allow easier comparisons */
162     GjsAutoChar filename = g_strdup_printf("%s.%jd.%u", dump_heap_output.get(),
163                                            intmax_t(getpid()), counter);
164     ++counter;
165 
166     FILE *fp = fopen(filename, "w");
167     if (!fp)
168         return;
169 
170     for (GList *l = all_contexts; l; l = g_list_next(l)) {
171         auto* gjs = static_cast<GjsContextPrivate*>(l->data);
172         js::DumpHeap(gjs->context(), fp, js::CollectNurseryBeforeDump);
173     }
174 
175     fclose(fp);
176 }
177 
dump_heap_idle(void *)178 static gboolean dump_heap_idle(void*) {
179     dump_heap_idle_id = 0;
180 
181     gjs_context_dump_heaps();
182 
183     return false;
184 }
185 
dump_heap_signal_handler(int signum)186 static void dump_heap_signal_handler(int signum [[maybe_unused]]) {
187     if (dump_heap_idle_id == 0)
188         dump_heap_idle_id = g_idle_add_full(G_PRIORITY_HIGH_IDLE,
189                                             dump_heap_idle, nullptr, nullptr);
190 }
191 #endif
192 
193 static void
setup_dump_heap(void)194 setup_dump_heap(void)
195 {
196     static bool dump_heap_initialized = false;
197     if (!dump_heap_initialized) {
198         dump_heap_initialized = true;
199 
200         /* install signal handler only if environment variable is set */
201         const char *heap_output = g_getenv("GJS_DEBUG_HEAP_OUTPUT");
202         if (heap_output) {
203 #ifdef G_OS_UNIX
204             struct sigaction sa;
205 
206             dump_heap_output = g_strdup(heap_output);
207 
208             memset(&sa, 0, sizeof(sa));
209             sa.sa_handler = dump_heap_signal_handler;
210             sigaction(SIGUSR1, &sa, nullptr);
211 #else
212             g_message(
213                 "heap dump is currently only supported on UNIX platforms");
214 #endif
215         }
216     }
217 }
218 
219 static void
gjs_context_init(GjsContext * js_context)220 gjs_context_init(GjsContext *js_context)
221 {
222     gjs_log_init();
223     gjs_context_make_current(js_context);
224 }
225 
226 static void
gjs_context_class_init(GjsContextClass * klass)227 gjs_context_class_init(GjsContextClass *klass)
228 {
229     GObjectClass *object_class = G_OBJECT_CLASS (klass);
230     GParamSpec *pspec;
231 
232     gjs_log_init();
233 
234     object_class->dispose = gjs_context_dispose;
235     object_class->finalize = gjs_context_finalize;
236 
237     object_class->constructed = gjs_context_constructed;
238     object_class->get_property = gjs_context_get_property;
239     object_class->set_property = gjs_context_set_property;
240 
241     pspec = g_param_spec_boxed("search-path",
242                                "Search path",
243                                "Path where modules to import should reside",
244                                G_TYPE_STRV,
245                                (GParamFlags) (G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
246 
247     g_object_class_install_property(object_class,
248                                     PROP_SEARCH_PATH,
249                                     pspec);
250     g_param_spec_unref(pspec);
251 
252     pspec = g_param_spec_string("program-name",
253                                 "Program Name",
254                                 "The filename of the launched JS program",
255                                 "",
256                                 (GParamFlags) (G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
257 
258     g_object_class_install_property(object_class,
259                                     PROP_PROGRAM_NAME,
260                                     pspec);
261     g_param_spec_unref(pspec);
262 
263     pspec = g_param_spec_string(
264         "program-path", "Executed File Path",
265         "The full path of the launched file or NULL if GJS was launched from "
266         "the C API or interactive console.",
267         nullptr, (GParamFlags)(G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
268 
269     g_object_class_install_property(object_class, PROP_PROGRAM_PATH, pspec);
270     g_param_spec_unref(pspec);
271 
272     /**
273      * GjsContext:profiler-enabled:
274      *
275      * Set this property to profile any JS code run by this context. By
276      * default, the profiler is started and stopped when you call
277      * gjs_context_eval().
278      *
279      * The value of this property is superseded by the GJS_ENABLE_PROFILER
280      * environment variable.
281      *
282      * You may only have one context with the profiler enabled at a time.
283      */
284     pspec = g_param_spec_boolean("profiler-enabled", "Profiler enabled",
285                                  "Whether to profile JS code run by this context",
286                                  FALSE,
287                                  GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
288     g_object_class_install_property(object_class, PROP_PROFILER_ENABLED, pspec);
289     g_param_spec_unref(pspec);
290 
291     /**
292      * GjsContext:profiler-sigusr2:
293      *
294      * Set this property to install a SIGUSR2 signal handler that starts and
295      * stops the profiler. This property also implies that
296      * #GjsContext:profiler-enabled is set.
297      */
298     pspec = g_param_spec_boolean("profiler-sigusr2", "Profiler SIGUSR2",
299                                  "Whether to activate the profiler on SIGUSR2",
300                                  FALSE,
301                                  GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
302     g_object_class_install_property(object_class, PROP_PROFILER_SIGUSR2, pspec);
303     g_param_spec_unref(pspec);
304 
305     pspec = g_param_spec_boolean(
306         "exec-as-module", "Execute as module",
307         "Whether to execute the file as a module", FALSE,
308         GParamFlags(G_PARAM_WRITABLE | G_PARAM_CONSTRUCT_ONLY));
309     g_object_class_install_property(object_class, PROP_EXEC_AS_MODULE, pspec);
310     g_param_spec_unref(pspec);
311 
312     /* For GjsPrivate */
313     if (!g_getenv("GJS_USE_UNINSTALLED_FILES")) {
314 #ifdef G_OS_WIN32
315         extern HMODULE gjs_dll;
316         char *basedir = g_win32_get_package_installation_directory_of_module (gjs_dll);
317         char *priv_typelib_dir = g_build_filename (basedir, "lib", "gjs", "girepository-1.0", NULL);
318         g_free (basedir);
319 #else
320         char *priv_typelib_dir = g_build_filename (PKGLIBDIR, "girepository-1.0", NULL);
321 #endif
322         g_irepository_prepend_search_path(priv_typelib_dir);
323     g_free (priv_typelib_dir);
324     }
325 
326     gjs_register_native_module("_byteArrayNative", gjs_define_byte_array_stuff);
327     gjs_register_native_module("_encodingNative",
328                                gjs_define_text_encoding_stuff);
329     gjs_register_native_module("_gi", gjs_define_private_gi_stuff);
330     gjs_register_native_module("gi", gjs_define_repo);
331 
332     gjs_register_static_modules();
333 }
334 
trace(JSTracer * trc,void * data)335 void GjsContextPrivate::trace(JSTracer* trc, void* data) {
336     auto* gjs = static_cast<GjsContextPrivate*>(data);
337     JS::TraceEdge<JSObject*>(trc, &gjs->m_global, "GJS global object");
338     JS::TraceEdge<JSObject*>(trc, &gjs->m_internal_global,
339                              "GJS internal global object");
340     gjs->m_atoms->trace(trc);
341     gjs->m_job_queue.trace(trc);
342     gjs->m_object_init_list.trace(trc);
343 }
344 
warn_about_unhandled_promise_rejections(void)345 void GjsContextPrivate::warn_about_unhandled_promise_rejections(void) {
346     for (auto& kv : m_unhandled_rejection_stacks) {
347         const char *stack = kv.second;
348         g_warning("Unhandled promise rejection. To suppress this warning, add "
349                   "an error handler to your promise chain with .catch() or a "
350                   "try-catch block around your await expression. %s%s",
351                   stack ? "Stack trace of the failed promise:\n" :
352                     "Unfortunately there is no stack trace of the failed promise.",
353                   stack ? stack : "");
354     }
355     m_unhandled_rejection_stacks.clear();
356 }
357 
358 static void
gjs_context_dispose(GObject * object)359 gjs_context_dispose(GObject *object)
360 {
361     gjs_debug(GJS_DEBUG_CONTEXT, "JS shutdown sequence");
362 
363     GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
364 
365     g_assert(gjs->is_owner_thread() &&
366              "Gjs Context disposed from another thread");
367 
368     /* Profiler must be stopped and freed before context is shut down */
369     gjs->free_profiler();
370 
371     /* Stop accepting entries in the toggle queue before running dispose
372      * notifications, which causes all GjsMaybeOwned instances to unroot.
373      * We don't want any objects to toggle down after that. */
374     gjs_debug(GJS_DEBUG_CONTEXT, "Shutting down toggle queue");
375     gjs_object_clear_toggles();
376     gjs_object_shutdown_toggle_queue();
377 
378     if (gjs->context())
379         ObjectInstance::context_dispose_notify(nullptr, object);
380 
381     gjs_debug(GJS_DEBUG_CONTEXT,
382               "Notifying external reference holders of GjsContext dispose");
383     G_OBJECT_CLASS(gjs_context_parent_class)->dispose(object);
384 
385     gjs->dispose();
386 }
387 
free_profiler(void)388 void GjsContextPrivate::free_profiler(void) {
389     gjs_debug(GJS_DEBUG_CONTEXT, "Stopping profiler");
390     if (m_profiler)
391         g_clear_pointer(&m_profiler, _gjs_profiler_free);
392 }
393 
register_notifier(DestroyNotify notify_func,void * data)394 void GjsContextPrivate::register_notifier(DestroyNotify notify_func,
395                                           void* data) {
396     m_destroy_notifications.push_back({notify_func, data});
397 }
398 
unregister_notifier(DestroyNotify notify_func,void * data)399 void GjsContextPrivate::unregister_notifier(DestroyNotify notify_func,
400                                             void* data) {
401     auto target = std::make_pair(notify_func, data);
402     Gjs::remove_one_from_unsorted_vector(&m_destroy_notifications, target);
403 }
404 
dispose(void)405 void GjsContextPrivate::dispose(void) {
406     if (m_cx) {
407         gjs_debug(GJS_DEBUG_CONTEXT,
408                   "Notifying reference holders of GjsContext dispose");
409 
410         for (auto const& destroy_notify : m_destroy_notifications)
411             destroy_notify.first(m_cx, destroy_notify.second);
412 
413         gjs_debug(GJS_DEBUG_CONTEXT,
414                   "Checking unhandled promise rejections");
415         warn_about_unhandled_promise_rejections();
416 
417         gjs_debug(GJS_DEBUG_CONTEXT, "Releasing cached JS wrappers");
418         m_fundamental_table->clear();
419         m_gtype_table->clear();
420 
421         /* Do a full GC here before tearing down, since once we do
422          * that we may not have the JS_GetPrivate() to access the
423          * context
424          */
425         gjs_debug(GJS_DEBUG_CONTEXT, "Final triggered GC");
426         JS_GC(m_cx, Gjs::GCReason::GJS_CONTEXT_DISPOSE);
427 
428         gjs_debug(GJS_DEBUG_CONTEXT, "Destroying JS context");
429         m_destroying.store(true);
430 
431         /* Now, release all native objects, to avoid recursion between
432          * the JS teardown and the C teardown.  The JSObject proxies
433          * still exist, but point to NULL.
434          */
435         gjs_debug(GJS_DEBUG_CONTEXT, "Releasing all native objects");
436         ObjectInstance::prepare_shutdown();
437 
438         gjs_debug(GJS_DEBUG_CONTEXT, "Disabling auto GC");
439         if (m_auto_gc_id > 0) {
440             g_source_remove(m_auto_gc_id);
441             m_auto_gc_id = 0;
442         }
443 
444         gjs_debug(GJS_DEBUG_CONTEXT, "Ending trace on global object");
445         JS_RemoveExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
446         m_global = nullptr;
447         m_internal_global = nullptr;
448 
449         gjs_debug(GJS_DEBUG_CONTEXT, "Freeing allocated resources");
450         delete m_fundamental_table;
451         delete m_gtype_table;
452         delete m_atoms;
453 
454         /* Tear down JS */
455         JS_DestroyContext(m_cx);
456         m_cx = nullptr;
457         // don't use g_clear_pointer() as we want the pointer intact while we
458         // destroy the context in case we dump stack
459         gjs_debug(GJS_DEBUG_CONTEXT, "JS context destroyed");
460     }
461 }
462 
~GjsContextPrivate(void)463 GjsContextPrivate::~GjsContextPrivate(void) {
464     g_clear_pointer(&m_search_path, g_strfreev);
465     g_clear_pointer(&m_program_path, g_free);
466     g_clear_pointer(&m_program_name, g_free);
467 }
468 
469 static void
gjs_context_finalize(GObject * object)470 gjs_context_finalize(GObject *object)
471 {
472     if (gjs_context_get_current() == (GjsContext*)object)
473         gjs_context_make_current(NULL);
474 
475     g_mutex_lock(&contexts_lock);
476     all_contexts = g_list_remove(all_contexts, object);
477     g_mutex_unlock(&contexts_lock);
478 
479     GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
480     gjs->~GjsContextPrivate();
481     G_OBJECT_CLASS(gjs_context_parent_class)->finalize(object);
482 
483     g_mutex_lock(&contexts_lock);
484     if (!all_contexts)
485         gjs_log_cleanup();
486     g_mutex_unlock(&contexts_lock);
487 }
488 
489 static void
gjs_context_constructed(GObject * object)490 gjs_context_constructed(GObject *object)
491 {
492     GjsContext *js_context = GJS_CONTEXT(object);
493 
494     G_OBJECT_CLASS(gjs_context_parent_class)->constructed(object);
495 
496     GjsContextPrivate* gjs_location = GjsContextPrivate::from_object(object);
497     JSContext* cx = gjs_create_js_context(gjs_location);
498     if (!cx)
499         g_error("Failed to create javascript context");
500 
501     new (gjs_location) GjsContextPrivate(cx, js_context);
502 
503     g_mutex_lock(&contexts_lock);
504     all_contexts = g_list_prepend(all_contexts, object);
505     g_mutex_unlock(&contexts_lock);
506 
507     setup_dump_heap();
508 }
509 
load_context_module(JSContext * cx,const char * uri,const char * debug_identifier)510 static void load_context_module(JSContext* cx, const char* uri,
511                                 const char* debug_identifier) {
512     JS::RootedObject loader(cx, gjs_module_load(cx, uri, uri));
513 
514     if (!loader) {
515         gjs_log_exception(cx);
516         g_error("Failed to load %s module.", debug_identifier);
517     }
518 
519     if (!JS::ModuleInstantiate(cx, loader)) {
520         gjs_log_exception(cx);
521         g_error("Failed to instantiate %s module.", debug_identifier);
522     }
523 
524     if (!JS::ModuleEvaluate(cx, loader)) {
525         gjs_log_exception(cx);
526         g_error("Failed to evaluate %s module.", debug_identifier);
527     }
528 }
529 
GjsContextPrivate(JSContext * cx,GjsContext * public_context)530 GjsContextPrivate::GjsContextPrivate(JSContext* cx, GjsContext* public_context)
531     : m_public_context(public_context),
532       m_cx(cx),
533       m_owner_thread(std::this_thread::get_id()),
534       m_environment_preparer(cx) {
535 
536     JS_SetGCCallback(
537         cx,
538         [](JSContext*, JSGCStatus status, JS::GCReason reason, void* data) {
539             static_cast<GjsContextPrivate*>(data)->on_garbage_collection(
540                 status, reason);
541         },
542         this);
543 
544     const char *env_profiler = g_getenv("GJS_ENABLE_PROFILER");
545     if (env_profiler || m_should_listen_sigusr2)
546         m_should_profile = true;
547 
548     if (m_should_profile) {
549         m_profiler = _gjs_profiler_new(public_context);
550 
551         if (!m_profiler) {
552             m_should_profile = false;
553         } else {
554             if (m_should_listen_sigusr2)
555                 _gjs_profiler_setup_signals(m_profiler, public_context);
556         }
557     }
558 
559     JSRuntime* rt = JS_GetRuntime(m_cx);
560     m_fundamental_table = new JS::WeakCache<FundamentalTable>(rt);
561     m_gtype_table = new JS::WeakCache<GTypeTable>(rt);
562 
563     m_atoms = new GjsAtoms();
564 
565     if (ObjectBox::gtype() == 0)
566         g_error("Failed to initialize JSObject GType");
567 
568     JS::RootedObject internal_global(
569         m_cx, gjs_create_global_object(cx, GjsGlobalType::INTERNAL));
570 
571     if (!internal_global) {
572         gjs_log_exception(m_cx);
573         g_error("Failed to initialize internal global object");
574     }
575 
576     JSAutoRealm ar(m_cx, internal_global);
577 
578     m_internal_global = internal_global;
579     JS_AddExtraGCRootsTracer(m_cx, &GjsContextPrivate::trace, this);
580 
581     if (!m_atoms->init_atoms(m_cx)) {
582         gjs_log_exception(m_cx);
583         g_error("Failed to initialize global strings");
584     }
585 
586     if (!gjs_define_global_properties(m_cx, internal_global,
587                                       GjsGlobalType::INTERNAL,
588                                       "GJS internal global", "nullptr")) {
589         gjs_log_exception(m_cx);
590         g_error("Failed to define properties on internal global object");
591     }
592 
593     JS::RootedObject global(
594         m_cx,
595         gjs_create_global_object(cx, GjsGlobalType::DEFAULT, internal_global));
596 
597     if (!global) {
598         gjs_log_exception(m_cx);
599         g_error("Failed to initialize global object");
600     }
601 
602     m_global = global;
603 
604     {
605         JSAutoRealm ar(cx, global);
606 
607         std::vector<std::string> paths;
608         if (m_search_path)
609             paths = {m_search_path,
610                      m_search_path + g_strv_length(m_search_path)};
611         JS::RootedObject importer(m_cx, gjs_create_root_importer(m_cx, paths));
612         if (!importer) {
613             gjs_log_exception(cx);
614             g_error("Failed to create root importer");
615         }
616 
617         g_assert(
618             gjs_get_global_slot(global, GjsGlobalSlot::IMPORTS).isUndefined() &&
619             "Someone else already created root importer");
620 
621         gjs_set_global_slot(global, GjsGlobalSlot::IMPORTS,
622                             JS::ObjectValue(*importer));
623 
624         if (!gjs_define_global_properties(m_cx, global, GjsGlobalType::DEFAULT,
625                                           "GJS", "default")) {
626             gjs_log_exception(m_cx);
627             g_error("Failed to define properties on global object");
628         }
629     }
630 
631     JS::SetModuleResolveHook(rt, gjs_module_resolve);
632     JS::SetModuleDynamicImportHook(rt, gjs_dynamic_module_resolve);
633     JS::SetModuleMetadataHook(rt, gjs_populate_module_meta);
634 
635     if (!JS_DefineProperty(m_cx, internal_global, "moduleGlobalThis", global,
636                            JSPROP_PERMANENT)) {
637         gjs_log_exception(m_cx);
638         g_error("Failed to define module global in internal global.");
639     }
640 
641     if (!gjs_load_internal_module(cx, "internalLoader")) {
642         gjs_log_exception(cx);
643         g_error("Failed to load internal module loaders.");
644     }
645 
646     load_context_module(cx,
647                         "resource:///org/gnome/gjs/modules/internal/loader.js",
648                         "module loader");
649 
650     {
651         JSAutoRealm ar(cx, global);
652         load_context_module(
653             cx, "resource:///org/gnome/gjs/modules/esm/_bootstrap/default.js",
654             "ESM bootstrap");
655     }
656 }
657 
set_args(std::vector<std::string> && args)658 void GjsContextPrivate::set_args(std::vector<std::string>&& args) {
659     m_args = args;
660 }
661 
build_args_array()662 JSObject* GjsContextPrivate::build_args_array() {
663     return gjs_build_string_array(m_cx, m_args);
664 }
665 
666 static void
gjs_context_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)667 gjs_context_get_property (GObject     *object,
668                           guint        prop_id,
669                           GValue      *value,
670                           GParamSpec  *pspec)
671 {
672     GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
673 
674     switch (prop_id) {
675     case PROP_PROGRAM_NAME:
676         g_value_set_string(value, gjs->program_name());
677         break;
678     case PROP_PROGRAM_PATH:
679         g_value_set_string(value, gjs->program_path());
680         break;
681     default:
682         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
683         break;
684     }
685 }
686 
687 static void
gjs_context_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)688 gjs_context_set_property (GObject      *object,
689                           guint         prop_id,
690                           const GValue *value,
691                           GParamSpec   *pspec)
692 {
693     GjsContextPrivate* gjs = GjsContextPrivate::from_object(object);
694 
695     switch (prop_id) {
696     case PROP_SEARCH_PATH:
697         gjs->set_search_path(static_cast<char**>(g_value_dup_boxed(value)));
698         break;
699     case PROP_PROGRAM_NAME:
700         gjs->set_program_name(g_value_dup_string(value));
701         break;
702     case PROP_PROGRAM_PATH:
703         gjs->set_program_path(g_value_dup_string(value));
704         break;
705     case PROP_PROFILER_ENABLED:
706         gjs->set_should_profile(g_value_get_boolean(value));
707         break;
708     case PROP_PROFILER_SIGUSR2:
709         gjs->set_should_listen_sigusr2(g_value_get_boolean(value));
710         break;
711     case PROP_EXEC_AS_MODULE:
712         gjs->set_execute_as_module(g_value_get_boolean(value));
713         break;
714     default:
715         G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
716         break;
717     }
718 }
719 
720 
721 GjsContext*
gjs_context_new(void)722 gjs_context_new(void)
723 {
724     return (GjsContext*) g_object_new (GJS_TYPE_CONTEXT, NULL);
725 }
726 
727 GjsContext*
gjs_context_new_with_search_path(char ** search_path)728 gjs_context_new_with_search_path(char** search_path)
729 {
730     return (GjsContext*) g_object_new (GJS_TYPE_CONTEXT,
731                          "search-path", search_path,
732                          NULL);
733 }
734 
trigger_gc_if_needed(void * data)735 gboolean GjsContextPrivate::trigger_gc_if_needed(void* data) {
736     auto* gjs = static_cast<GjsContextPrivate*>(data);
737     gjs->m_auto_gc_id = 0;
738 
739     if (gjs->m_force_gc) {
740         gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Big Hammer hit");
741         JS_GC(gjs->m_cx, Gjs::GCReason::BIG_HAMMER);
742     } else {
743         gjs_gc_if_needed(gjs->m_cx);
744     }
745     gjs->m_force_gc = false;
746 
747     return G_SOURCE_REMOVE;
748 }
749 
schedule_gc_internal(bool force_gc)750 void GjsContextPrivate::schedule_gc_internal(bool force_gc) {
751     m_force_gc |= force_gc;
752 
753     if (m_auto_gc_id > 0)
754         return;
755 
756     if (force_gc)
757         gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "Big Hammer scheduled");
758 
759     m_auto_gc_id = g_timeout_add_seconds_full(G_PRIORITY_LOW, 10,
760                                               trigger_gc_if_needed, this,
761                                               nullptr);
762 }
763 
764 /*
765  * GjsContextPrivate::schedule_gc_if_needed:
766  *
767  * Does a minor GC immediately if the JS engine decides one is needed, but also
768  * schedules a full GC in the next idle time.
769  */
schedule_gc_if_needed(void)770 void GjsContextPrivate::schedule_gc_if_needed(void) {
771     // We call JS_MaybeGC immediately, but defer a check for a full GC cycle
772     // to an idle handler.
773     JS_MaybeGC(m_cx);
774 
775     schedule_gc_internal(false);
776 }
777 
on_garbage_collection(JSGCStatus status,JS::GCReason reason)778 void GjsContextPrivate::on_garbage_collection(JSGCStatus status, JS::GCReason reason) {
779     int64_t now = 0;
780     if (m_profiler)
781         now = g_get_monotonic_time() * 1000L;
782 
783     switch (status) {
784         case JSGC_BEGIN:
785             m_gc_begin_time = now;
786             m_gc_reason = gjs_explain_gc_reason(reason);
787             gjs_debug_lifecycle(GJS_DEBUG_CONTEXT,
788                                 "Begin garbage collection because of %s",
789                                 m_gc_reason);
790 
791             // We finalize any pending toggle refs before doing any garbage
792             // collection, so that we can collect the JS wrapper objects, and in
793             // order to minimize the chances of objects having a pending toggle
794             // up queued when they are garbage collected.
795             gjs_object_clear_toggles();
796 
797             m_async_closures.clear();
798             m_async_closures.shrink_to_fit();
799             break;
800         case JSGC_END:
801             if (m_profiler && m_gc_begin_time != 0) {
802                 _gjs_profiler_add_mark(m_profiler, m_gc_begin_time,
803                                        now - m_gc_begin_time, "GJS",
804                                        "Garbage collection", m_gc_reason);
805             }
806             m_gc_begin_time = 0;
807             m_gc_reason = nullptr;
808 
809             m_destroy_notifications.shrink_to_fit();
810             gjs_debug_lifecycle(GJS_DEBUG_CONTEXT, "End garbage collection");
811             break;
812         default:
813             g_assert_not_reached();
814     }
815 }
816 
set_finalize_status(JSFinalizeStatus status)817 void GjsContextPrivate::set_finalize_status(JSFinalizeStatus status) {
818     // Implementation note for mozjs-24:
819     //
820     // Sweeping happens in two phases, in the first phase all GC things from the
821     // allocation arenas are queued for sweeping, then the actual sweeping
822     // happens. The first phase is marked by JSFINALIZE_GROUP_START, the second
823     // one by JSFINALIZE_GROUP_END, and finally we will see
824     // JSFINALIZE_COLLECTION_END at the end of all GC. (see jsgc.cpp,
825     // BeginSweepPhase/BeginSweepingZoneGroup and SweepPhase, all called from
826     // IncrementalCollectSlice).
827     //
828     // Incremental GC muddies the waters, because BeginSweepPhase is always run
829     // to entirety, but SweepPhase can be run incrementally and mixed with JS
830     // code runs or even native code, when MaybeGC/IncrementalGC return.
831     //
832     // Luckily for us, objects are treated specially, and are not really queued
833     // for deferred incremental finalization (unless they are marked for
834     // background sweeping). Instead, they are finalized immediately during
835     // phase 1, so the following guarantees are true (and we rely on them):
836     // - phase 1 of GC will begin and end in the same JSAPI call (i.e., our
837     //   callback will be called with GROUP_START and the triggering JSAPI call
838     //   will not return until we see a GROUP_END)
839     // - object finalization will begin and end in the same JSAPI call
840     // - therefore, if there is a finalizer frame somewhere in the stack,
841     //   GjsContextPrivate::sweeping() will return true.
842     //
843     // Comments in mozjs-24 imply that this behavior might change in the future,
844     // but it hasn't changed in mozilla-central as of 2014-02-23. In addition to
845     // that, the mozilla-central version has a huge comment in a different
846     // portion of the file, explaining why finalization of objects can't be
847     // mixed with JS code, so we can probably rely on this behavior.
848 
849     int64_t now = 0;
850 
851     if (m_profiler)
852         now = g_get_monotonic_time() * 1000L;
853 
854     switch (status) {
855         case JSFINALIZE_GROUP_PREPARE:
856             m_in_gc_sweep = true;
857             m_sweep_begin_time = now;
858             break;
859         case JSFINALIZE_GROUP_START:
860             m_group_sweep_begin_time = now;
861             break;
862         case JSFINALIZE_GROUP_END:
863             if (m_profiler && m_group_sweep_begin_time != 0) {
864                 _gjs_profiler_add_mark(m_profiler, m_group_sweep_begin_time,
865                                        now - m_group_sweep_begin_time, "GJS",
866                                        "Group sweep", nullptr);
867             }
868             m_group_sweep_begin_time = 0;
869             break;
870         case JSFINALIZE_COLLECTION_END:
871             m_in_gc_sweep = false;
872             if (m_profiler && m_sweep_begin_time != 0) {
873                 _gjs_profiler_add_mark(m_profiler, m_sweep_begin_time,
874                                        now - m_sweep_begin_time, "GJS", "Sweep",
875                                        nullptr);
876             }
877             m_sweep_begin_time = 0;
878             break;
879         default:
880             g_assert_not_reached();
881     }
882 }
883 
exit(uint8_t exit_code)884 void GjsContextPrivate::exit(uint8_t exit_code) {
885     g_assert(!m_should_exit);
886     m_should_exit = true;
887     m_exit_code = exit_code;
888 }
889 
should_exit(uint8_t * exit_code_p) const890 bool GjsContextPrivate::should_exit(uint8_t* exit_code_p) const {
891     if (exit_code_p != NULL)
892         *exit_code_p = m_exit_code;
893     return m_should_exit;
894 }
895 
start_draining_job_queue(void)896 void GjsContextPrivate::start_draining_job_queue(void) {
897     if (!m_idle_drain_handler) {
898         gjs_debug(GJS_DEBUG_CONTEXT, "Starting promise job queue handler");
899         m_idle_drain_handler = g_idle_add_full(
900             G_PRIORITY_DEFAULT, drain_job_queue_idle_handler, this, nullptr);
901     }
902 }
903 
stop_draining_job_queue(void)904 void GjsContextPrivate::stop_draining_job_queue(void) {
905     m_draining_job_queue = false;
906     if (m_idle_drain_handler) {
907         gjs_debug(GJS_DEBUG_CONTEXT, "Stopping promise job queue handler");
908         g_source_remove(m_idle_drain_handler);
909         m_idle_drain_handler = 0;
910     }
911 }
912 
drain_job_queue_idle_handler(void * data)913 gboolean GjsContextPrivate::drain_job_queue_idle_handler(void* data) {
914     gjs_debug(GJS_DEBUG_CONTEXT, "Promise job queue handler");
915     auto* gjs = static_cast<GjsContextPrivate*>(data);
916     gjs->runJobs(gjs->context());
917     /* Uncatchable exceptions are swallowed here - no way to get a handle on
918      * the main loop to exit it from this idle handler */
919     gjs_debug(GJS_DEBUG_CONTEXT, "Promise job queue handler finished");
920     g_assert(gjs->empty() && gjs->m_idle_drain_handler == 0 &&
921              "GjsContextPrivate::runJobs() should have emptied queue");
922     return G_SOURCE_REMOVE;
923 }
924 
getIncumbentGlobal(JSContext * cx)925 JSObject* GjsContextPrivate::getIncumbentGlobal(JSContext* cx) {
926     // This is equivalent to SpiderMonkey's behavior.
927     return JS::CurrentGlobalOrNull(cx);
928 }
929 
930 // See engine.cpp and JS::SetJobQueue().
enqueuePromiseJob(JSContext * cx,JS::HandleObject promise,JS::HandleObject job,JS::HandleObject allocation_site,JS::HandleObject incumbent_global)931 bool GjsContextPrivate::enqueuePromiseJob(JSContext* cx [[maybe_unused]],
932                                           JS::HandleObject promise,
933                                           JS::HandleObject job,
934                                           JS::HandleObject allocation_site,
935                                           JS::HandleObject incumbent_global
936                                           [[maybe_unused]]) {
937     g_assert(cx == m_cx);
938     g_assert(from_cx(cx) == this);
939 
940     gjs_debug(GJS_DEBUG_CONTEXT,
941               "Enqueue job %s, promise=%s, allocation site=%s",
942               gjs_debug_object(job).c_str(), gjs_debug_object(promise).c_str(),
943               gjs_debug_object(allocation_site).c_str());
944 
945     if (m_idle_drain_handler)
946         g_assert(!empty());
947     else
948         g_assert(empty());
949 
950     if (!m_job_queue.append(job)) {
951         JS_ReportOutOfMemory(m_cx);
952         return false;
953     }
954 
955     JS::JobQueueMayNotBeEmpty(m_cx);
956     start_draining_job_queue();
957     return true;
958 }
959 
960 // Override of JobQueue::runJobs(). Called by js::RunJobs(), and when execution
961 // of the job queue was interrupted by the debugger and is resuming.
runJobs(JSContext * cx)962 void GjsContextPrivate::runJobs(JSContext* cx) {
963     g_assert(cx == m_cx);
964     g_assert(from_cx(cx) == this);
965     if (!run_jobs_fallible())
966         gjs_log_exception(cx);
967 }
968 
969 /*
970  * GjsContext::run_jobs_fallible:
971  *
972  * Drains the queue of promise callbacks that the JS engine has reported
973  * finished, calling each one and logging any exceptions that it throws.
974  *
975  * Adapted from js::RunJobs() in SpiderMonkey's default job queue
976  * implementation.
977  *
978  * Returns: false if one of the jobs threw an uncatchable exception;
979  * otherwise true.
980  */
run_jobs_fallible(void)981 bool GjsContextPrivate::run_jobs_fallible(void) {
982     bool retval = true;
983 
984     if (m_draining_job_queue || m_should_exit)
985         return true;
986 
987     m_draining_job_queue = true;  // Ignore reentrant calls
988 
989     JS::RootedObject job(m_cx);
990     JS::HandleValueArray args(JS::HandleValueArray::empty());
991     JS::RootedValue rval(m_cx);
992 
993     /* Execute jobs in a loop until we've reached the end of the queue.
994      * Since executing a job can trigger enqueueing of additional jobs,
995      * it's crucial to recheck the queue length during each iteration. */
996     for (size_t ix = 0; ix < m_job_queue.length(); ix++) {
997         /* A previous job might have set this flag. e.g., System.exit(). */
998         if (m_should_exit)
999             break;
1000 
1001         job = m_job_queue[ix];
1002 
1003         /* It's possible that job draining was interrupted prematurely,
1004          * leaving the queue partly processed. In that case, slots for
1005          * already-executed entries will contain nullptrs, which we should
1006          * just skip. */
1007         if (!job)
1008             continue;
1009 
1010         m_job_queue[ix] = nullptr;
1011         {
1012             JSAutoRealm ar(m_cx, job);
1013             gjs_debug(GJS_DEBUG_CONTEXT, "handling job %s",
1014                       gjs_debug_object(job).c_str());
1015             if (!JS::Call(m_cx, JS::UndefinedHandleValue, job, args, &rval)) {
1016                 /* Uncatchable exception - return false so that
1017                  * System.exit() works in the interactive shell and when
1018                  * exiting the interpreter. */
1019                 if (!JS_IsExceptionPending(m_cx)) {
1020                     /* System.exit() is an uncatchable exception, but does not
1021                      * indicate a bug. Log everything else. */
1022                     if (!should_exit(nullptr))
1023                         g_critical("Promise callback terminated with uncatchable exception");
1024                     retval = false;
1025                     continue;
1026                 }
1027 
1028                 /* There's nowhere for the exception to go at this point */
1029                 gjs_log_exception_uncaught(m_cx);
1030             }
1031         }
1032     }
1033 
1034     m_job_queue.clear();
1035     stop_draining_job_queue();
1036     JS::JobQueueIsEmpty(m_cx);
1037     return retval;
1038 }
1039 
1040 class GjsContextPrivate::SavedQueue : public JS::JobQueue::SavedJobQueue {
1041  private:
1042     GjsContextPrivate* m_gjs;
1043     JS::PersistentRooted<JobQueueStorage> m_queue;
1044     bool m_idle_was_pending : 1;
1045     bool m_was_draining : 1;
1046 
1047  public:
SavedQueue(GjsContextPrivate * gjs)1048     explicit SavedQueue(GjsContextPrivate* gjs)
1049         : m_gjs(gjs),
1050           m_queue(gjs->m_cx, std::move(gjs->m_job_queue)),
1051           m_idle_was_pending(gjs->m_idle_drain_handler != 0),
1052           m_was_draining(gjs->m_draining_job_queue) {
1053         gjs_debug(GJS_DEBUG_CONTEXT, "Pausing job queue");
1054         gjs->stop_draining_job_queue();
1055     }
1056 
~SavedQueue(void)1057     ~SavedQueue(void) {
1058         gjs_debug(GJS_DEBUG_CONTEXT, "Unpausing job queue");
1059         m_gjs->m_job_queue = std::move(m_queue.get());
1060         m_gjs->m_draining_job_queue = m_was_draining;
1061         if (m_idle_was_pending)
1062             m_gjs->start_draining_job_queue();
1063     }
1064 };
1065 
saveJobQueue(JSContext * cx)1066 js::UniquePtr<JS::JobQueue::SavedJobQueue> GjsContextPrivate::saveJobQueue(
1067     JSContext* cx) {
1068     g_assert(cx == m_cx);
1069     g_assert(from_cx(cx) == this);
1070 
1071     auto saved_queue = js::MakeUnique<SavedQueue>(this);
1072     if (!saved_queue) {
1073         JS_ReportOutOfMemory(cx);
1074         return nullptr;
1075     }
1076 
1077     g_assert(m_job_queue.empty());
1078     return saved_queue;
1079 }
1080 
register_unhandled_promise_rejection(uint64_t id,GjsAutoChar && stack)1081 void GjsContextPrivate::register_unhandled_promise_rejection(
1082     uint64_t id, GjsAutoChar&& stack) {
1083     m_unhandled_rejection_stacks[id] = std::move(stack);
1084 }
1085 
unregister_unhandled_promise_rejection(uint64_t id)1086 void GjsContextPrivate::unregister_unhandled_promise_rejection(uint64_t id) {
1087     // Return value unused in G_DISABLE_ASSERT case
1088     [[maybe_unused]] size_t erased = m_unhandled_rejection_stacks.erase(id);
1089     g_assert(((void)"Handler attached to rejected promise that wasn't "
1090               "previously marked as unhandled", erased == 1));
1091 }
1092 
async_closure_enqueue_for_gc(Gjs::Closure * trampoline)1093 void GjsContextPrivate::async_closure_enqueue_for_gc(Gjs::Closure* trampoline) {
1094     //  Because we can't free the mmap'd data for a callback
1095     //  while it's in use, this list keeps track of ones that
1096     //  will be freed the next time gc happens
1097     g_assert(!trampoline->context() || trampoline->context() == m_cx);
1098     m_async_closures.emplace_back(trampoline);
1099 }
1100 
1101 /**
1102  * gjs_context_maybe_gc:
1103  * @context: a #GjsContext
1104  *
1105  * Similar to the Spidermonkey JS_MaybeGC() call which
1106  * heuristically looks at JS runtime memory usage and
1107  * may initiate a garbage collection.
1108  *
1109  * This function always unconditionally invokes JS_MaybeGC(), but
1110  * additionally looks at memory usage from the system malloc()
1111  * when available, and if the delta has grown since the last run
1112  * significantly, also initiates a full JavaScript garbage
1113  * collection.  The idea is that since GJS is a bridge between
1114  * JavaScript and system libraries, and JS objects act as proxies
1115  * for these system memory objects, GJS consumers need a way to
1116  * hint to the runtime that it may be a good idea to try a
1117  * collection.
1118  *
1119  * A good time to call this function is when your application
1120  * transitions to an idle state.
1121  */
1122 void
gjs_context_maybe_gc(GjsContext * context)1123 gjs_context_maybe_gc (GjsContext  *context)
1124 {
1125     GjsContextPrivate* gjs = GjsContextPrivate::from_object(context);
1126     gjs_maybe_gc(gjs->context());
1127 }
1128 
1129 /**
1130  * gjs_context_gc:
1131  * @context: a #GjsContext
1132  *
1133  * Initiate a full GC; may or may not block until complete.  This
1134  * function just calls Spidermonkey JS_GC().
1135  */
1136 void
gjs_context_gc(GjsContext * context)1137 gjs_context_gc (GjsContext  *context)
1138 {
1139     GjsContextPrivate* gjs = GjsContextPrivate::from_object(context);
1140     JS_GC(gjs->context(), Gjs::GCReason::GJS_API_CALL);
1141 }
1142 
1143 /**
1144  * gjs_context_get_all:
1145  *
1146  * Returns a newly-allocated list containing all known instances of #GjsContext.
1147  * This is useful for operating on the contexts from a process-global situation
1148  * such as a debugger.
1149  *
1150  * Return value: (element-type GjsContext) (transfer full): Known #GjsContext instances
1151  */
1152 GList*
gjs_context_get_all(void)1153 gjs_context_get_all(void)
1154 {
1155   GList *result;
1156   GList *iter;
1157   g_mutex_lock (&contexts_lock);
1158   result = g_list_copy(all_contexts);
1159   for (iter = result; iter; iter = iter->next)
1160     g_object_ref((GObject*)iter->data);
1161   g_mutex_unlock (&contexts_lock);
1162   return result;
1163 }
1164 
1165 /**
1166  * gjs_context_get_native_context:
1167  *
1168  * Returns a pointer to the underlying native context.  For SpiderMonkey, this
1169  * is a JSContext *
1170  */
1171 void*
gjs_context_get_native_context(GjsContext * js_context)1172 gjs_context_get_native_context (GjsContext *js_context)
1173 {
1174     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), NULL);
1175     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1176     return gjs->context();
1177 }
1178 
1179 bool
gjs_context_eval(GjsContext * js_context,const char * script,gssize script_len,const char * filename,int * exit_status_p,GError ** error)1180 gjs_context_eval(GjsContext   *js_context,
1181                  const char   *script,
1182                  gssize        script_len,
1183                  const char   *filename,
1184                  int          *exit_status_p,
1185                  GError      **error)
1186 {
1187     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
1188 
1189     GjsAutoUnref<GjsContext> js_context_ref(js_context, GjsAutoTakeOwnership());
1190 
1191     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1192     return gjs->eval(script, script_len, filename, exit_status_p, error);
1193 }
1194 
gjs_context_eval_module(GjsContext * js_context,const char * identifier,uint8_t * exit_code,GError ** error)1195 bool gjs_context_eval_module(GjsContext* js_context, const char* identifier,
1196                              uint8_t* exit_code, GError** error) {
1197     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
1198 
1199     GjsAutoUnref<GjsContext> js_context_ref(js_context, GjsAutoTakeOwnership());
1200 
1201     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1202     return gjs->eval_module(identifier, exit_code, error);
1203 }
1204 
gjs_context_register_module(GjsContext * js_context,const char * identifier,const char * uri,GError ** error)1205 bool gjs_context_register_module(GjsContext* js_context, const char* identifier,
1206                                  const char* uri, GError** error) {
1207     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
1208 
1209     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1210 
1211     return gjs->register_module(identifier, uri, error);
1212 }
1213 
auto_profile_enter()1214 bool GjsContextPrivate::auto_profile_enter() {
1215     bool auto_profile = m_should_profile;
1216     if (auto_profile &&
1217         (_gjs_profiler_is_running(m_profiler) || m_should_listen_sigusr2))
1218         auto_profile = false;
1219 
1220     JSAutoRealm ar(m_cx, m_global);
1221 
1222     if (auto_profile)
1223         gjs_profiler_start(m_profiler);
1224 
1225     return auto_profile;
1226 }
1227 
auto_profile_exit(bool auto_profile)1228 void GjsContextPrivate::auto_profile_exit(bool auto_profile) {
1229     if (auto_profile)
1230         gjs_profiler_stop(m_profiler);
1231 }
1232 
handle_exit_code(const char * type,const char * identifier,GError ** error)1233 uint8_t GjsContextPrivate::handle_exit_code(const char* type,
1234                                             const char* identifier,
1235                                             GError** error) {
1236     uint8_t code;
1237     if (should_exit(&code)) {
1238         /* exit_status_p is public API so can't be changed, but should be
1239          * uint8_t, not int */
1240         g_set_error(error, GJS_ERROR, GJS_ERROR_SYSTEM_EXIT,
1241                     "Exit with code %d", code);
1242         return code;  // Don't log anything
1243     }
1244     if (!JS_IsExceptionPending(m_cx)) {
1245         g_critical("%s %s terminated with an uncatchable exception", type,
1246                    identifier);
1247         g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
1248                     "%s %s terminated with an uncatchable exception", type,
1249                     identifier);
1250     } else {
1251         g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
1252                     "%s %s threw an exception", type, identifier);
1253     }
1254 
1255     gjs_log_exception_uncaught(m_cx);
1256     /* No exit code from script, but we don't want to exit(0) */
1257     return 1;
1258 }
1259 
eval(const char * script,ssize_t script_len,const char * filename,int * exit_status_p,GError ** error)1260 bool GjsContextPrivate::eval(const char* script, ssize_t script_len,
1261                              const char* filename, int* exit_status_p,
1262                              GError** error) {
1263     AutoResetExit reset(this);
1264 
1265     bool auto_profile = auto_profile_enter();
1266 
1267     JSAutoRealm ar(m_cx, m_global);
1268 
1269     JS::RootedValue retval(m_cx);
1270     bool ok = eval_with_scope(nullptr, script, script_len, filename, &retval);
1271 
1272     /* The promise job queue should be drained even on error, to finish
1273      * outstanding async tasks before the context is torn down. Drain after
1274      * uncaught exceptions have been reported since draining runs callbacks. */
1275     {
1276         JS::AutoSaveExceptionState saved_exc(m_cx);
1277         ok = run_jobs_fallible() && ok;
1278     }
1279 
1280     auto_profile_exit(auto_profile);
1281 
1282     if (!ok) {
1283         *exit_status_p = handle_exit_code("Script", filename, error);
1284         return false;
1285     }
1286 
1287     if (exit_status_p) {
1288         if (retval.isInt32()) {
1289             int code = retval.toInt32();
1290             gjs_debug(GJS_DEBUG_CONTEXT,
1291                       "Script returned integer code %d", code);
1292             *exit_status_p = code;
1293         } else {
1294             /* Assume success if no integer was returned */
1295             *exit_status_p = 0;
1296         }
1297     }
1298 
1299     return true;
1300 }
1301 
eval_module(const char * identifier,uint8_t * exit_status_p,GError ** error)1302 bool GjsContextPrivate::eval_module(const char* identifier,
1303                                     uint8_t* exit_status_p, GError** error) {
1304     AutoResetExit reset(this);
1305 
1306     bool auto_profile = auto_profile_enter();
1307 
1308     JSAutoRealm ac(m_cx, m_global);
1309 
1310     JS::RootedObject registry(m_cx, gjs_get_module_registry(m_global));
1311     JS::RootedId key(m_cx, gjs_intern_string_to_id(m_cx, identifier));
1312     JS::RootedObject obj(m_cx);
1313     if (!gjs_global_registry_get(m_cx, registry, key, &obj) || !obj) {
1314         g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
1315                     "Cannot load module with identifier: '%s'", identifier);
1316         *exit_status_p = 1;
1317         return false;
1318     }
1319 
1320     if (!JS::ModuleInstantiate(m_cx, obj)) {
1321         gjs_log_exception(m_cx);
1322         g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
1323                     "Failed to resolve imports for module: '%s'", identifier);
1324         *exit_status_p = 1;
1325         return false;
1326     }
1327 
1328     bool ok = true;
1329     if (!JS::ModuleEvaluate(m_cx, obj))
1330         ok = false;
1331 
1332     /* The promise job queue should be drained even on error, to finish
1333      * outstanding async tasks before the context is torn down. Drain after
1334      * uncaught exceptions have been reported since draining runs callbacks.
1335      */
1336     {
1337         JS::AutoSaveExceptionState saved_exc(m_cx);
1338         ok = run_jobs_fallible() && ok;
1339     }
1340 
1341     auto_profile_exit(auto_profile);
1342 
1343     if (!ok) {
1344         *exit_status_p = handle_exit_code("Module", identifier, error);
1345         return false;
1346     }
1347 
1348     /* Assume success if no errors were thrown or exit code set. */
1349     *exit_status_p = 0;
1350     return true;
1351 }
1352 
register_module(const char * identifier,const char * uri,GError ** error)1353 bool GjsContextPrivate::register_module(const char* identifier, const char* uri,
1354                                         GError** error) {
1355     JSAutoRealm ar(m_cx, m_global);
1356 
1357     if (gjs_module_load(m_cx, identifier, uri))
1358         return true;
1359 
1360     const char* msg = "unknown";
1361     JS::ExceptionStack exn_stack(m_cx);
1362     JS::ErrorReportBuilder builder(m_cx);
1363     if (JS::StealPendingExceptionStack(m_cx, &exn_stack) &&
1364         builder.init(m_cx, exn_stack,
1365                      JS::ErrorReportBuilder::WithSideEffects)) {
1366         msg = builder.toStringResult().c_str();
1367     } else {
1368         JS_ClearPendingException(m_cx);
1369     }
1370 
1371     g_set_error(error, GJS_ERROR, GJS_ERROR_FAILED,
1372                 "Failed to parse module '%s': %s", identifier,
1373                 msg ? msg : "unknown");
1374 
1375     return false;
1376 }
1377 
1378 bool
gjs_context_eval_file(GjsContext * js_context,const char * filename,int * exit_status_p,GError ** error)1379 gjs_context_eval_file(GjsContext    *js_context,
1380                       const char    *filename,
1381                       int           *exit_status_p,
1382                       GError       **error)
1383 {
1384     char *script;
1385     size_t script_len;
1386     GjsAutoUnref<GFile> file = g_file_new_for_commandline_arg(filename);
1387 
1388     if (!g_file_load_contents(file, nullptr, &script, &script_len, nullptr,
1389                               error))
1390         return false;
1391     GjsAutoChar script_ref = script;
1392 
1393     return gjs_context_eval(js_context, script, script_len, filename,
1394                             exit_status_p, error);
1395 }
1396 
gjs_context_eval_module_file(GjsContext * js_context,const char * filename,uint8_t * exit_status_p,GError ** error)1397 bool gjs_context_eval_module_file(GjsContext* js_context, const char* filename,
1398                                   uint8_t* exit_status_p, GError** error) {
1399     GjsAutoUnref<GFile> file = g_file_new_for_commandline_arg(filename);
1400     GjsAutoChar uri = g_file_get_uri(file);
1401 
1402     return gjs_context_register_module(js_context, uri, uri, error) &&
1403            gjs_context_eval_module(js_context, uri, exit_status_p, error);
1404 }
1405 
1406 /*
1407  * GjsContextPrivate::eval_with_scope:
1408  * @scope_object: an object to use as the global scope, or nullptr
1409  * @script: JavaScript program encoded in UTF-8
1410  * @script_len: length of @script, or -1 if @script is 0-terminated
1411  * @filename: filename to use as the origin of @script
1412  * @retval: location for the return value of @script
1413  *
1414  * Executes @script with a local scope so that nothing from the script leaks out
1415  * into the global scope.
1416  * If @scope_object is given, then everything that @script placed in the global
1417  * namespace is defined on @scope_object.
1418  * Otherwise, the global definitions are just discarded.
1419  */
eval_with_scope(JS::HandleObject scope_object,const char * script,ssize_t script_len,const char * filename,JS::MutableHandleValue retval)1420 bool GjsContextPrivate::eval_with_scope(JS::HandleObject scope_object,
1421                                         const char* script, ssize_t script_len,
1422                                         const char* filename,
1423                                         JS::MutableHandleValue retval) {
1424     /* log and clear exception if it's set (should not be, normally...) */
1425     if (JS_IsExceptionPending(m_cx)) {
1426         g_warning("eval_with_scope() called with a pending exception");
1427         return false;
1428     }
1429 
1430     JS::RootedObject eval_obj(m_cx, scope_object);
1431     if (!eval_obj)
1432         eval_obj = JS_NewPlainObject(m_cx);
1433 
1434     long items_written;  // NOLINT(runtime/int) - this type required by GLib API
1435     GError* error;
1436     GjsAutoChar16 utf16_string =
1437         g_utf8_to_utf16(script, script_len,
1438                         /* items_read = */ nullptr, &items_written, &error);
1439     if (!utf16_string)
1440         return gjs_throw_gerror_message(m_cx, error);
1441 
1442     // COMPAT: This could use JS::SourceText<mozilla::Utf8Unit> directly,
1443     // but that messes up code coverage. See bug
1444     // https://bugzilla.mozilla.org/show_bug.cgi?id=1404784
1445     JS::SourceText<char16_t> buf;
1446     if (!buf.init(m_cx, reinterpret_cast<char16_t*>(utf16_string.get()),
1447                   items_written, JS::SourceOwnership::Borrowed))
1448         return false;
1449 
1450     JS::RootedObjectVector scope_chain(m_cx);
1451     if (!scope_chain.append(eval_obj)) {
1452         JS_ReportOutOfMemory(m_cx);
1453         return false;
1454     }
1455 
1456     JS::CompileOptions options(m_cx);
1457     options.setFileAndLine(filename, 1);
1458 
1459     GjsAutoUnref<GFile> file = g_file_new_for_commandline_arg(filename);
1460     GjsAutoChar uri = g_file_get_uri(file);
1461     JS::RootedObject priv(m_cx, gjs_script_module_build_private(m_cx, uri));
1462     if (!priv)
1463         return false;
1464 
1465     options.setPrivateValue(JS::ObjectValue(*priv));
1466 
1467     if (!JS::Evaluate(m_cx, scope_chain, options, buf, retval))
1468         return false;
1469 
1470     schedule_gc_if_needed();
1471 
1472     if (JS_IsExceptionPending(m_cx)) {
1473         g_warning(
1474             "JS::Evaluate() returned true but exception was pending; "
1475             "did somebody call gjs_throw() without returning false?");
1476         return false;
1477     }
1478 
1479     gjs_debug(GJS_DEBUG_CONTEXT, "Script evaluation succeeded");
1480 
1481     return true;
1482 }
1483 
1484 /*
1485  * GjsContextPrivate::call_function:
1486  * @this_obj: Object to use as the 'this' for the function call
1487  * @func_val: Callable to call, as a JS value
1488  * @args: Arguments to pass to the callable
1489  * @rval: Location for the return value
1490  *
1491  * Use this instead of JS_CallFunctionValue(), because it schedules a GC if
1492  * one is needed. It's good practice to check if a GC should be run every time
1493  * we return from JS back into C++.
1494  */
call_function(JS::HandleObject this_obj,JS::HandleValue func_val,const JS::HandleValueArray & args,JS::MutableHandleValue rval)1495 bool GjsContextPrivate::call_function(JS::HandleObject this_obj,
1496                                       JS::HandleValue func_val,
1497                                       const JS::HandleValueArray& args,
1498                                       JS::MutableHandleValue rval) {
1499     if (!JS_CallFunctionValue(m_cx, this_obj, func_val, args, rval))
1500         return false;
1501 
1502     schedule_gc_if_needed();
1503 
1504     return true;
1505 }
1506 
1507 bool
gjs_context_define_string_array(GjsContext * js_context,const char * array_name,gssize array_length,const char ** array_values,GError ** error)1508 gjs_context_define_string_array(GjsContext  *js_context,
1509                                 const char    *array_name,
1510                                 gssize         array_length,
1511                                 const char   **array_values,
1512                                 GError       **error)
1513 {
1514     g_return_val_if_fail(GJS_IS_CONTEXT(js_context), false);
1515     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1516 
1517     JSAutoRealm ar(gjs->context(), gjs->global());
1518 
1519     std::vector<std::string> strings;
1520     if (array_values) {
1521         if (array_length < 0)
1522             array_length = g_strv_length(const_cast<char**>(array_values));
1523         strings = {array_values, array_values + array_length};
1524     }
1525 
1526     // ARGV is a special case to preserve backwards compatibility.
1527     if (strcmp(array_name, "ARGV") == 0) {
1528         gjs->set_args(std::move(strings));
1529 
1530         return true;
1531     }
1532 
1533     JS::RootedObject global_root(gjs->context(), gjs->global());
1534     if (!gjs_define_string_array(gjs->context(), global_root, array_name,
1535                                  strings, JSPROP_READONLY | JSPROP_PERMANENT)) {
1536         gjs_log_exception(gjs->context());
1537         g_set_error(error,
1538                     GJS_ERROR,
1539                     GJS_ERROR_FAILED,
1540                     "gjs_define_string_array() failed");
1541         return false;
1542     }
1543 
1544     return true;
1545 }
1546 
gjs_context_set_argv(GjsContext * js_context,ssize_t array_length,const char ** array_values)1547 void gjs_context_set_argv(GjsContext* js_context, ssize_t array_length,
1548                           const char** array_values) {
1549     g_return_if_fail(GJS_IS_CONTEXT(js_context));
1550     GjsContextPrivate* gjs = GjsContextPrivate::from_object(js_context);
1551     std::vector<std::string> args(array_values, array_values + array_length);
1552     gjs->set_args(std::move(args));
1553 }
1554 
1555 static GjsContext *current_context;
1556 
1557 GjsContext *
gjs_context_get_current(void)1558 gjs_context_get_current (void)
1559 {
1560     return current_context;
1561 }
1562 
1563 void
gjs_context_make_current(GjsContext * context)1564 gjs_context_make_current (GjsContext *context)
1565 {
1566     g_assert (context == NULL || current_context == NULL);
1567 
1568     current_context = context;
1569 }
1570 
1571 /**
1572  * gjs_context_get_profiler:
1573  * @self: the #GjsContext
1574  *
1575  * Returns the profiler's internal instance of #GjsProfiler for you to
1576  * customize, or %NULL if profiling is not enabled on this #GjsContext.
1577  *
1578  * Returns: (transfer none) (nullable): a #GjsProfiler
1579  */
1580 GjsProfiler *
gjs_context_get_profiler(GjsContext * self)1581 gjs_context_get_profiler(GjsContext *self)
1582 {
1583     return GjsContextPrivate::from_object(self)->profiler();
1584 }
1585 
1586 /**
1587  * gjs_get_js_version:
1588  *
1589  * Returns the underlying version of the JS engine.
1590  *
1591  * Returns: a string
1592  */
1593 const char *
gjs_get_js_version(void)1594 gjs_get_js_version(void)
1595 {
1596     return JS_GetImplementationVersion();
1597 }
1598