1 /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 /*
3  * Copyright (c) 2014 Colin Walters <walters@verbum.org>
4  *
5  * Permission is hereby granted, free of charge, to any person obtaining a copy
6  * of this software and associated documentation files (the "Software"), to
7  * deal in the Software without restriction, including without limitation the
8  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
9  * sell copies of the Software, and to permit persons to whom the Software is
10  * furnished to do so, subject to the following conditions:
11  *
12  * The above copyright notice and this permission notice shall be included in
13  * all copies or substantial portions of the Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  */
23 
24 #ifndef GJS_CONTEXT_PRIVATE_H_
25 #define GJS_CONTEXT_PRIVATE_H_
26 
27 #include <config.h>
28 
29 #include <stdint.h>
30 #include <sys/types.h>  // for ssize_t
31 
32 #include <type_traits>  // for is_same
33 #include <unordered_map>
34 
35 #include <glib-object.h>
36 #include <glib.h>
37 
38 #include <js/GCHashTable.h>
39 #include <js/GCVector.h>
40 #include <js/Promise.h>
41 #include <js/RootingAPI.h>
42 #include <js/TypeDecls.h>
43 #include <js/ValueArray.h>
44 #include <jsapi.h>        // for JS_GetContextPrivate
45 #include <jsfriendapi.h>  // for ScriptEnvironmentPreparer
46 #include <mozilla/HashTable.h>  // for DefaultHasher
47 #include <mozilla/UniquePtr.h>
48 
49 #include "cjs/context.h"
50 #include "cjs/jsapi-util.h"
51 #include "cjs/macros.h"
52 #include "cjs/profiler.h"
53 
54 namespace js {
55 class SystemAllocPolicy;
56 }
57 class GjsAtoms;
58 class JSTracer;
59 
60 using JobQueueStorage =
61     JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
62 using ObjectInitList =
63     JS::GCVector<JS::Heap<JSObject*>, 0, js::SystemAllocPolicy>;
64 using FundamentalTable =
65     JS::GCHashMap<void*, JS::Heap<JSObject*>, js::DefaultHasher<void*>,
66                   js::SystemAllocPolicy>;
67 using GTypeTable =
68     JS::GCHashMap<GType, JS::Heap<JSObject*>, js::DefaultHasher<GType>,
69                   js::SystemAllocPolicy>;
70 
71 struct Dummy {};
72 using GTypeNotUint64 =
73     std::conditional_t<!std::is_same_v<GType, uint64_t>, GType, Dummy>;
74 
75 // The GC sweep method should ignore FundamentalTable and GTypeTable's key types
76 namespace JS {
77 // Forward declarations
78 template <typename T>
79 class WeakCache;
80 template <typename T>
81 struct GCPolicy;
82 
83 template <>
84 struct GCPolicy<void*> : public IgnoreGCPolicy<void*> {};
85 // We need GCPolicy<GType> for GTypeTable. SpiderMonkey already defines
86 // GCPolicy<uint64_t> which is equal to GType on some systems; for others we
87 // need to define it. (macOS's uint64_t is unsigned long long, which is a
88 // different type from unsigned long, even if they are the same width)
89 template <>
90 struct GCPolicy<GTypeNotUint64> : public IgnoreGCPolicy<GTypeNotUint64> {};
91 }  // namespace JS
92 
93 class GjsContextPrivate : public JS::JobQueue {
94     GjsContext* m_public_context;
95     JSContext* m_cx;
96     JS::Heap<JSObject*> m_global;
97     GThread* m_owner_thread;
98 
99     char* m_program_name;
100 
101     char** m_search_path;
102 
103     unsigned m_auto_gc_id;
104 
105     GjsAtoms* m_atoms;
106 
107     JobQueueStorage m_job_queue;
108     unsigned m_idle_drain_handler;
109 
110     std::unordered_map<uint64_t, GjsAutoChar> m_unhandled_rejection_stacks;
111 
112     GjsProfiler* m_profiler;
113 
114     /* Environment preparer needed for debugger, taken from SpiderMonkey's
115      * JS shell */
116     struct EnvironmentPreparer final : protected js::ScriptEnvironmentPreparer {
117         JSContext* m_cx;
118 
119         explicit EnvironmentPreparer(JSContext* cx) : m_cx(cx) {
120             js::SetScriptEnvironmentPreparer(m_cx, this);
121         }
122 
123         void invoke(JS::HandleObject scope, Closure& closure) override;
124     };
125     EnvironmentPreparer m_environment_preparer;
126 
127     // Weak pointer mapping from fundamental native pointer to JSObject
128     JS::WeakCache<FundamentalTable>* m_fundamental_table;
129     JS::WeakCache<GTypeTable>* m_gtype_table;
130 
131     // List that holds JSObject GObject wrappers for JS-created classes, from
132     // the time of their creation until their GObject instance init function is
133     // called
134     ObjectInitList m_object_init_list;
135 
136     uint8_t m_exit_code;
137 
138     /* flags */
139     bool m_destroying : 1;
140     bool m_in_gc_sweep : 1;
141     bool m_should_exit : 1;
142     bool m_force_gc : 1;
143     bool m_draining_job_queue : 1;
144     bool m_should_profile : 1;
145     bool m_should_listen_sigusr2 : 1;
146 
147     int64_t m_sweep_begin_time;
148 
149     void schedule_gc_internal(bool force_gc);
150     static gboolean trigger_gc_if_needed(void* data);
151 
152     class SavedQueue;
153     void start_draining_job_queue(void);
154     void stop_draining_job_queue(void);
155     static gboolean drain_job_queue_idle_handler(void* data);
156 
157     void warn_about_unhandled_promise_rejections(void);
158 
159     class AutoResetExit {
160         GjsContextPrivate* m_self;
161 
162      public:
163         explicit AutoResetExit(GjsContextPrivate* self) { m_self = self; }
164         ~AutoResetExit() {
165             m_self->m_should_exit = false;
166             m_self->m_exit_code = 0;
167         }
168     };
169 
170  public:
171     /* Retrieving a GjsContextPrivate from JSContext or GjsContext */
172     [[nodiscard]] static GjsContextPrivate* from_cx(JSContext* cx) {
173         return static_cast<GjsContextPrivate*>(JS_GetContextPrivate(cx));
174     }
175     [[nodiscard]] static GjsContextPrivate* from_object(
176         GObject* public_context);
177     [[nodiscard]] static GjsContextPrivate* from_object(
178         GjsContext* public_context);
179     [[nodiscard]] static GjsContextPrivate* from_current_context();
180 
181     GjsContextPrivate(JSContext* cx, GjsContext* public_context);
182     ~GjsContextPrivate(void);
183 
184     /* Accessors */
185     [[nodiscard]] GjsContext* public_context() const {
186         return m_public_context;
187     }
188     [[nodiscard]] JSContext* context() const { return m_cx; }
189     [[nodiscard]] JSObject* global() const { return m_global.get(); }
190     [[nodiscard]] GjsProfiler* profiler() const { return m_profiler; }
191     [[nodiscard]] const GjsAtoms& atoms() const { return *m_atoms; }
192     [[nodiscard]] bool destroying() const { return m_destroying; }
193     [[nodiscard]] bool sweeping() const { return m_in_gc_sweep; }
194     [[nodiscard]] const char* program_name() const { return m_program_name; }
195     void set_program_name(char* value) { m_program_name = value; }
196     void set_search_path(char** value) { m_search_path = value; }
197     void set_should_profile(bool value) { m_should_profile = value; }
198     void set_should_listen_sigusr2(bool value) {
199         m_should_listen_sigusr2 = value;
200     }
201     [[nodiscard]] bool is_owner_thread() const {
202         return m_owner_thread == g_thread_self();
203     }
204     [[nodiscard]] JS::WeakCache<FundamentalTable>& fundamental_table() {
205         return *m_fundamental_table;
206     }
207     [[nodiscard]] JS::WeakCache<GTypeTable>& gtype_table() {
208         return *m_gtype_table;
209     }
210     [[nodiscard]] ObjectInitList& object_init_list() {
211         return m_object_init_list;
212     }
213     [[nodiscard]] static const GjsAtoms& atoms(JSContext* cx) {
214         return *(from_cx(cx)->m_atoms);
215     }
216 
217     GJS_JSAPI_RETURN_CONVENTION
218     bool eval(const char* script, ssize_t script_len, const char* filename,
219               int* exit_status_p, GError** error);
220     GJS_JSAPI_RETURN_CONVENTION
221     bool eval_with_scope(JS::HandleObject scope_object, const char* script,
222                          ssize_t script_len, const char* filename,
223                          JS::MutableHandleValue retval);
224     GJS_JSAPI_RETURN_CONVENTION
225     bool call_function(JS::HandleObject this_obj, JS::HandleValue func_val,
226                        const JS::HandleValueArray& args,
227                        JS::MutableHandleValue rval);
228 
229     void schedule_gc(void) { schedule_gc_internal(true); }
230     void schedule_gc_if_needed(void);
231 
232     void exit(uint8_t exit_code);
233     [[nodiscard]] bool should_exit(uint8_t* exit_code_p) const;
234 
235     // Implementations of JS::JobQueue virtual functions
236     GJS_JSAPI_RETURN_CONVENTION
237     JSObject* getIncumbentGlobal(JSContext* cx) override;
238     GJS_JSAPI_RETURN_CONVENTION
239     bool enqueuePromiseJob(JSContext* cx, JS::HandleObject promise,
240                            JS::HandleObject job,
241                            JS::HandleObject allocation_site,
242                            JS::HandleObject incumbent_global) override;
243     void runJobs(JSContext* cx) override;
244     [[nodiscard]] bool empty() const override { return m_job_queue.empty(); }
245     js::UniquePtr<JS::JobQueue::SavedJobQueue> saveJobQueue(
246         JSContext* cx) override;
247 
248     GJS_JSAPI_RETURN_CONVENTION bool run_jobs_fallible(void);
249     void register_unhandled_promise_rejection(uint64_t id, GjsAutoChar&& stack);
250     void unregister_unhandled_promise_rejection(uint64_t id);
251 
252     void set_sweeping(bool value);
253 
254     static void trace(JSTracer* trc, void* data);
255 
256     void free_profiler(void);
257     void dispose(void);
258 };
259 #endif  // GJS_CONTEXT_PRIVATE_H_
260