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: 2014 Colin Walters <walters@verbum.org>
4 
5 #ifndef GJS_CONTEXT_PRIVATE_H_
6 #define GJS_CONTEXT_PRIVATE_H_
7 
8 #include <config.h>
9 
10 #include <stdint.h>
11 #include <sys/types.h>  // for ssize_t
12 
13 #include <atomic>
14 #include <string>
15 #include <thread>
16 #include <unordered_map>
17 #include <utility>  // for pair
18 #include <vector>
19 
20 #include <glib-object.h>
21 #include <glib.h>
22 
23 #include <js/GCAPI.h>
24 #include <js/GCHashTable.h>
25 #include <js/GCVector.h>
26 #include <js/HashTable.h>  // for DefaultHasher
27 #include <js/Promise.h>
28 #include <js/RootingAPI.h>
29 #include <js/TypeDecls.h>
30 #include <js/UniquePtr.h>
31 #include <js/ValueArray.h>
32 #include <jsapi.h>        // for JS_GetContextPrivate
33 #include <jsfriendapi.h>  // for ScriptEnvironmentPreparer
34 
35 #include "gi/closure.h"
36 #include "gjs/context.h"
37 #include "gjs/jsapi-util.h"
38 #include "gjs/macros.h"
39 #include "gjs/profiler.h"
40 
41 namespace js {
42 class SystemAllocPolicy;
43 }
44 class GjsAtoms;
45 class JSTracer;
46 
47 using JobQueueStorage =
48     JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
49 using ObjectInitList =
50     JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
51 using FundamentalTable =
52     JS::GCHashMap<void*, JS::Heap<JSObject*>, js::DefaultHasher<void*>,
53                   js::SystemAllocPolicy>;
54 using GTypeTable =
55     JS::GCHashMap<GType, JS::Heap<JSObject*>, js::DefaultHasher<GType>,
56                   js::SystemAllocPolicy>;
57 
58 class GjsContextPrivate : public JS::JobQueue {
59  public:
60     using DestroyNotify = void (*)(JSContext*, void* data);
61 
62  private:
63     GjsContext* m_public_context;
64     JSContext* m_cx;
65     JS::Heap<JSObject*> m_global;
66     JS::Heap<JSObject*> m_internal_global;
67     std::thread::id m_owner_thread;
68 
69     char* m_program_name;
70     char* m_program_path;
71 
72     char** m_search_path;
73 
74     unsigned m_auto_gc_id;
75 
76     GjsAtoms* m_atoms;
77 
78     std::vector<std::string> m_args;
79 
80     JobQueueStorage m_job_queue;
81     unsigned m_idle_drain_handler;
82 
83     std::vector<std::pair<DestroyNotify, void*>> m_destroy_notifications;
84     std::vector<Gjs::Closure::Ptr> m_async_closures;
85     std::unordered_map<uint64_t, GjsAutoChar> m_unhandled_rejection_stacks;
86 
87     GjsProfiler* m_profiler;
88 
89     /* Environment preparer needed for debugger, taken from SpiderMonkey's
90      * JS shell */
91     struct EnvironmentPreparer final : protected js::ScriptEnvironmentPreparer {
92         JSContext* m_cx;
93 
EnvironmentPreparerfinal94         explicit EnvironmentPreparer(JSContext* cx) : m_cx(cx) {
95             js::SetScriptEnvironmentPreparer(m_cx, this);
96         }
97 
98         void invoke(JS::HandleObject scope, Closure& closure) override;
99     };
100     EnvironmentPreparer m_environment_preparer;
101 
102     // Weak pointer mapping from fundamental native pointer to JSObject
103     JS::WeakCache<FundamentalTable>* m_fundamental_table;
104     JS::WeakCache<GTypeTable>* m_gtype_table;
105 
106     // List that holds JSObject GObject wrappers for JS-created classes, from
107     // the time of their creation until their GObject instance init function is
108     // called
109     ObjectInitList m_object_init_list;
110 
111     uint8_t m_exit_code;
112 
113     /* flags */
114     std::atomic_bool m_destroying = ATOMIC_VAR_INIT(false);
115     bool m_in_gc_sweep : 1;
116     bool m_should_exit : 1;
117     bool m_force_gc : 1;
118     bool m_draining_job_queue : 1;
119     bool m_should_profile : 1;
120     bool m_exec_as_module : 1;
121     bool m_should_listen_sigusr2 : 1;
122 
123     // If profiling is enabled, we record the durations and reason for GC mark
124     // and sweep
125     int64_t m_gc_begin_time;
126     int64_t m_sweep_begin_time;
127     int64_t m_group_sweep_begin_time;
128     const char* m_gc_reason;  // statically allocated
129 
130     void schedule_gc_internal(bool force_gc);
131     static gboolean trigger_gc_if_needed(void* data);
132     void on_garbage_collection(JSGCStatus, JS::GCReason);
133 
134     class SavedQueue;
135     void start_draining_job_queue(void);
136     void stop_draining_job_queue(void);
137     static gboolean drain_job_queue_idle_handler(void* data);
138 
139     uint8_t handle_exit_code(const char* type, const char* identifier,
140                              GError** error);
141     [[nodiscard]] bool auto_profile_enter(void);
142     void auto_profile_exit(bool status);
143 
144     class AutoResetExit {
145         GjsContextPrivate* m_self;
146 
147      public:
AutoResetExit(GjsContextPrivate * self)148         explicit AutoResetExit(GjsContextPrivate* self) { m_self = self; }
~AutoResetExit()149         ~AutoResetExit() {
150             m_self->m_should_exit = false;
151             m_self->m_exit_code = 0;
152         }
153     };
154 
155  public:
156     /* Retrieving a GjsContextPrivate from JSContext or GjsContext */
from_cx(JSContext * cx)157     [[nodiscard]] static GjsContextPrivate* from_cx(JSContext* cx) {
158         return static_cast<GjsContextPrivate*>(JS_GetContextPrivate(cx));
159     }
160     [[nodiscard]] static GjsContextPrivate* from_object(
161         GObject* public_context);
162     [[nodiscard]] static GjsContextPrivate* from_object(
163         GjsContext* public_context);
164     [[nodiscard]] static GjsContextPrivate* from_current_context();
165 
166     GjsContextPrivate(JSContext* cx, GjsContext* public_context);
167     ~GjsContextPrivate(void);
168 
169     /* Accessors */
public_context()170     [[nodiscard]] GjsContext* public_context() const {
171         return m_public_context;
172     }
context()173     [[nodiscard]] JSContext* context() const { return m_cx; }
global()174     [[nodiscard]] JSObject* global() const { return m_global.get(); }
internal_global()175     [[nodiscard]] JSObject* internal_global() const {
176         return m_internal_global.get();
177     }
profiler()178     [[nodiscard]] GjsProfiler* profiler() const { return m_profiler; }
atoms()179     [[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; }
destroying()180     [[nodiscard]] bool destroying() const { return m_destroying.load(); }
sweeping()181     [[nodiscard]] bool sweeping() const { return m_in_gc_sweep; }
program_name()182     [[nodiscard]] const char* program_name() const { return m_program_name; }
set_program_name(char * value)183     void set_program_name(char* value) { m_program_name = value; }
program_path(void)184     GJS_USE const char* program_path(void) const { return m_program_path; }
set_program_path(char * value)185     void set_program_path(char* value) { m_program_path = value; }
set_search_path(char ** value)186     void set_search_path(char** value) { m_search_path = value; }
set_should_profile(bool value)187     void set_should_profile(bool value) { m_should_profile = value; }
set_execute_as_module(bool value)188     void set_execute_as_module(bool value) { m_exec_as_module = value; }
set_should_listen_sigusr2(bool value)189     void set_should_listen_sigusr2(bool value) {
190         m_should_listen_sigusr2 = value;
191     }
192     void set_args(std::vector<std::string>&& args);
193     GJS_JSAPI_RETURN_CONVENTION JSObject* build_args_array();
is_owner_thread()194     [[nodiscard]] bool is_owner_thread() const {
195         return m_owner_thread == std::this_thread::get_id();
196     }
fundamental_table()197     [[nodiscard]] JS::WeakCache<FundamentalTable>& fundamental_table() {
198         return *m_fundamental_table;
199     }
gtype_table()200     [[nodiscard]] JS::WeakCache<GTypeTable>& gtype_table() {
201         return *m_gtype_table;
202     }
object_init_list()203     [[nodiscard]] ObjectInitList& object_init_list() {
204         return m_object_init_list;
205     }
atoms(JSContext * cx)206     [[nodiscard]] static const GjsAtoms& atoms(JSContext* cx) {
207         return *(from_cx(cx)->m_atoms);
208     }
209 
210     [[nodiscard]] bool eval(const char* script, ssize_t script_len,
211                             const char* filename, int* exit_status_p,
212                             GError** error);
213     GJS_JSAPI_RETURN_CONVENTION
214     bool eval_with_scope(JS::HandleObject scope_object, const char* script,
215                          ssize_t script_len, const char* filename,
216                          JS::MutableHandleValue retval);
217     [[nodiscard]] bool eval_module(const char* identifier, uint8_t* exit_code_p,
218                                    GError** error);
219     GJS_JSAPI_RETURN_CONVENTION
220     bool call_function(JS::HandleObject this_obj, JS::HandleValue func_val,
221                        const JS::HandleValueArray& args,
222                        JS::MutableHandleValue rval);
223 
schedule_gc(void)224     void schedule_gc(void) { schedule_gc_internal(true); }
225     void schedule_gc_if_needed(void);
226 
227     void exit(uint8_t exit_code);
228     [[nodiscard]] bool should_exit(uint8_t* exit_code_p) const;
229 
230     // Implementations of JS::JobQueue virtual functions
231     GJS_JSAPI_RETURN_CONVENTION
232     JSObject* getIncumbentGlobal(JSContext* cx) override;
233     GJS_JSAPI_RETURN_CONVENTION
234     bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
235                            JS::HandleObject job,
236                            JS::HandleObject allocation_site,
237                            JS::HandleObject incumbent_global) override;
238     void runJobs(JSContext* cx) override;
empty()239     [[nodiscard]] bool empty() const override { return m_job_queue.empty(); }
240     js::UniquePtr<JS::JobQueue::SavedJobQueue> saveJobQueue(
241         JSContext* cx) override;
242 
243     GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(void);
244     void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack);
245     void unregister_unhandled_promise_rejection(uint64_t id);
246     void warn_about_unhandled_promise_rejections();
247 
248     void register_notifier(DestroyNotify notify_func, void* data);
249     void unregister_notifier(DestroyNotify notify_func, void* data);
250     void async_closure_enqueue_for_gc(Gjs::Closure*);
251 
252     [[nodiscard]] bool register_module(const char* identifier,
253                                        const char* filename, GError** error);
254 
255     void set_gc_status(JSGCStatus status, JS::GCReason reason);
256     void set_finalize_status(JSFinalizeStatus status);
257 
258     static void trace(JSTracer* trc, void* data);
259 
260     void free_profiler(void);
261     void dispose(void);
262 };
263 #endif  // GJS_CONTEXT_PRIVATE_H_
264