Branch data Line data Source code
1 : : /* -*- mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; -*- */
2 : : // SPDX-License-Identifier: MIT OR LGPL-2.0-or-later
3 : : // SPDX-FileCopyrightText: 2013 Giovanni Campagna <scampa.giovanni@gmail.com>
4 : :
5 : : #include <config.h>
6 : :
7 : : #include <stdint.h>
8 : :
9 : : #ifdef _WIN32
10 : : # include <windows.h>
11 : : #endif
12 : :
13 : : #include <utility> // for move
14 : :
15 : : #include <gio/gio.h>
16 : : #include <glib.h>
17 : :
18 : : #include <js/Context.h>
19 : : #include <js/ContextOptions.h>
20 : : #include <js/GCAPI.h> // for JS_SetGCParameter, JS_AddFin...
21 : : #include <js/Initialization.h> // for JS_Init, JS_ShutDown
22 : : #include <js/Principals.h>
23 : : #include <js/Promise.h>
24 : : #include <js/RootingAPI.h>
25 : : #include <js/Stack.h> // for JS_SetNativeStackQuota
26 : : #include <js/StructuredClone.h> // for JS_WriteUint32Pair
27 : : #include <js/TypeDecls.h>
28 : : #include <js/Utility.h> // for UniqueChars
29 : : #include <js/Warnings.h>
30 : : #include <js/experimental/SourceHook.h>
31 : : #include <jsapi.h> // for JS_SetGlobalJitCompilerOption
32 : : #include <jsfriendapi.h> // for SetDOMCallbacks, DOMCallbacks
33 : : #include <mozilla/UniquePtr.h>
34 : :
35 : : #ifndef G_DISABLE_ASSERT
36 : : # include <mozilla/Atomics.h> // for Atomic::operator==
37 : : #endif
38 : :
39 : : #include "gi/gerror.h"
40 : : #include "gjs/context-private.h"
41 : : #include "gjs/engine.h"
42 : : #include "gjs/gerror-result.h"
43 : : #include "gjs/jsapi-util.h"
44 : : #include "gjs/profiler-private.h"
45 : : #include "util/log.h"
46 : :
47 : 2412 : static void gjs_finalize_callback(JS::GCContext*, JSFinalizeStatus status,
48 : : void* data) {
49 : 2412 : auto* gjs = static_cast<GjsContextPrivate*>(data);
50 [ - + ]: 2412 : if (gjs->profiler())
51 : 0 : gjs_profiler_set_finalize_status(gjs->profiler(), status);
52 : 2412 : }
53 : :
54 : 51 : static void on_promise_unhandled_rejection(
55 : : JSContext* cx, bool mutedErrors [[maybe_unused]], JS::HandleObject promise,
56 : : JS::PromiseRejectionHandlingState state, void* data) {
57 : 51 : auto* gjs = static_cast<GjsContextPrivate*>(data);
58 : 51 : uint64_t id = JS::GetPromiseID(promise);
59 : :
60 [ + + ]: 51 : if (state == JS::PromiseRejectionHandlingState::Handled) {
61 : : // This happens when catching an exception from an await expression.
62 : 25 : gjs->unregister_unhandled_promise_rejection(id);
63 : 25 : return;
64 : : }
65 : :
66 : 26 : JS::RootedObject allocation_site(cx, JS::GetPromiseAllocationSite(promise));
67 : 26 : JS::UniqueChars stack = format_saved_frame(cx, allocation_site);
68 : 52 : gjs->register_unhandled_promise_rejection(id, std::move(stack));
69 : 26 : }
70 : :
71 : 5 : static void on_cleanup_finalization_registry(JSFunction* cleanup_task,
72 : : JSObject* incumbent_global
73 : : [[maybe_unused]],
74 : : void* data) {
75 : 5 : auto* gjs = static_cast<GjsContextPrivate*>(data);
76 [ - + ]: 5 : if (!gjs->queue_finalization_registry_cleanup(cleanup_task))
77 : 0 : g_critical("Out of memory queueing FinalizationRegistry cleanup task");
78 : 5 : }
79 : :
80 : 617 : bool gjs_load_internal_source(JSContext* cx, const char* filename, char** src,
81 : : size_t* length) {
82 : 617 : Gjs::AutoError error;
83 : 617 : const char* path = filename + 11; // len("resource://")
84 : : GBytes* script_bytes =
85 : 617 : g_resources_lookup_data(path, G_RESOURCE_LOOKUP_FLAGS_NONE, &error);
86 [ - + ]: 617 : if (!script_bytes)
87 : 0 : return gjs_throw_gerror_message(cx, error);
88 : :
89 : 617 : *src = static_cast<char*>(g_bytes_unref_to_data(script_bytes, length));
90 : 617 : return true;
91 : 617 : }
92 : :
93 : : class GjsSourceHook : public js::SourceHook {
94 : 0 : bool load(JSContext* cx, const char* filename,
95 : : char16_t** two_byte_source [[maybe_unused]], char** utf8_source,
96 : : size_t* length) override {
97 : : // caller owns the source, per documentation of SourceHook
98 : 0 : return gjs_load_internal_source(cx, filename, utf8_source, length);
99 : : }
100 : : };
101 : :
102 : : #ifdef G_OS_WIN32
103 : : HMODULE gjs_dll;
104 : : static bool gjs_is_inited = false;
105 : :
106 : : BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) {
107 : : switch (fdwReason) {
108 : : case DLL_PROCESS_ATTACH: {
109 : : gjs_dll = hinstDLL;
110 : : const char* reason = JS_InitWithFailureDiagnostic();
111 : : if (reason)
112 : : g_error("Could not initialize JavaScript: %s", reason);
113 : : gjs_is_inited = true;
114 : : } break;
115 : :
116 : : case DLL_THREAD_DETACH:
117 : : JS_ShutDown();
118 : : break;
119 : :
120 : : default: {
121 : : }
122 : : }
123 : :
124 : : return TRUE;
125 : : }
126 : :
127 : : #else
128 : : class GjsInit {
129 : : public:
130 : 130 : GjsInit() {
131 : 130 : const char* reason = JS_InitWithFailureDiagnostic();
132 [ - + ]: 130 : if (reason)
133 : 0 : g_error("Could not initialize JavaScript: %s", reason);
134 : 130 : }
135 : :
136 : 130 : ~GjsInit() {
137 : 130 : JS_ShutDown();
138 : 130 : }
139 : :
140 : 256 : explicit operator bool() const { return true; }
141 : : };
142 : :
143 : : static GjsInit gjs_is_inited;
144 : : #endif
145 : :
146 : : // JSPrincipals (basically a weird name for security callbacks) which are in
147 : : // effect in the module loader's realm (GjsInternalGlobal). This prevents module
148 : : // loader stack frames from showing up in public stack traces.
149 : : class ModuleLoaderPrincipals final : public JSPrincipals {
150 : : static constexpr uint32_t STRUCTURED_CLONE_TAG = JS_SCTAG_USER_MIN;
151 : :
152 : 0 : bool write(JSContext* cx [[maybe_unused]],
153 : : JSStructuredCloneWriter* writer) override {
154 : : g_assert_not_reached();
155 : : return JS_WriteUint32Pair(writer, STRUCTURED_CLONE_TAG, 1);
156 : : }
157 : :
158 : 0 : bool isSystemOrAddonPrincipal() override { return true; }
159 : :
160 : : public:
161 : 131129 : static bool subsumes(JSPrincipals* first, JSPrincipals* second) {
162 [ + - + + ]: 131129 : return first == &the_principals || second != &the_principals;
163 : : }
164 : :
165 : 254 : static void destroy(JSPrincipals* principals [[maybe_unused]]) {
166 : 254 : g_assert(principals == &the_principals &&
167 : : "Should not create other instances of ModuleLoaderPrinciples");
168 : 254 : g_assert(principals->refcount == 0 &&
169 : : "Mismatched JS_HoldPrincipals/JS_DropPrincipals");
170 : 254 : }
171 : :
172 : : // Singleton
173 : : static ModuleLoaderPrincipals the_principals;
174 : : };
175 : :
176 : : ModuleLoaderPrincipals ModuleLoaderPrincipals::the_principals{};
177 : :
178 : 256 : JSPrincipals* get_internal_principals() {
179 : 256 : return &ModuleLoaderPrincipals::the_principals;
180 : : }
181 : :
182 : : static const JSSecurityCallbacks security_callbacks = {
183 : : /* contentSecurityPolicyAllows = */ nullptr,
184 : : /* codeForEvalGets = */ nullptr,
185 : : &ModuleLoaderPrincipals::subsumes,
186 : : };
187 : :
188 : 4 : static bool instance_class_is_error(const JSClass* klass) {
189 : 4 : return klass == &ErrorBase::klass;
190 : : }
191 : :
192 : : static const js::DOMCallbacks dom_callbacks = {
193 : : /* instanceClassHasProtoAtDepth = */ nullptr,
194 : : &instance_class_is_error,
195 : : };
196 : :
197 : 256 : JSContext* gjs_create_js_context(GjsContextPrivate* uninitialized_gjs) {
198 : 256 : g_assert(gjs_is_inited);
199 : 256 : JSContext* cx = JS_NewContext(/* max bytes = */ 32 * 1024 * 1024);
200 [ - + ]: 256 : if (!cx)
201 : 0 : return nullptr;
202 : :
203 [ - + ]: 256 : if (!JS::InitSelfHostedCode(cx)) {
204 : 0 : JS_DestroyContext(cx);
205 : 0 : return nullptr;
206 : : }
207 : :
208 : : // For additional context on these options, see
209 : : // https://searchfox.org/mozilla-esr91/rev/c49725508e97c1e2e2bb3bf9ed0ba14b2016abac/js/public/GCAPI.h#53
210 : 256 : JS_SetNativeStackQuota(cx, 1024UL * 1024UL);
211 : 256 : JS_SetGCParameter(cx, JSGC_MAX_BYTES, -1);
212 : 256 : JS_SetGCParameter(cx, JSGC_INCREMENTAL_GC_ENABLED, 1);
213 : 256 : JS_SetGCParameter(cx, JSGC_SLICE_TIME_BUDGET_MS, 10);
214 : :
215 : : // set ourselves as the private data
216 : 256 : JS_SetContextPrivate(cx, uninitialized_gjs);
217 : :
218 : 256 : JS_SetSecurityCallbacks(cx, &security_callbacks);
219 : 256 : JS_InitDestroyPrincipalsCallback(cx, &ModuleLoaderPrincipals::destroy);
220 : 256 : JS_AddFinalizeCallback(cx, gjs_finalize_callback, uninitialized_gjs);
221 : 256 : JS::SetWarningReporter(cx, gjs_warning_reporter);
222 : 256 : JS::SetJobQueue(cx, dynamic_cast<JS::JobQueue*>(uninitialized_gjs));
223 : 256 : JS::SetPromiseRejectionTrackerCallback(cx, on_promise_unhandled_rejection,
224 : : uninitialized_gjs);
225 : 256 : JS::SetHostCleanupFinalizationRegistryCallback(
226 : : cx, on_cleanup_finalization_registry, uninitialized_gjs);
227 : 256 : js::SetDOMCallbacks(cx, &dom_callbacks);
228 : :
229 : : // We use this to handle "lazy sources" that SpiderMonkey doesn't need to
230 : : // keep in memory. Most sources should be kept in memory, but we can skip
231 : : // doing that for the realm bootstrap code, as it is already in memory in
232 : : // the form of a GResource. Instead we use the "source hook" to retrieve it.
233 : 256 : auto hook = mozilla::MakeUnique<GjsSourceHook>();
234 : 256 : js::SetSourceHook(cx, std::move(hook));
235 : :
236 [ - + ]: 256 : if (g_getenv("GJS_DISABLE_EXTRA_WARNINGS")) {
237 : 0 : g_warning(
238 : : "GJS_DISABLE_EXTRA_WARNINGS has been removed, GJS no longer logs "
239 : : "extra warnings.");
240 : : }
241 : :
242 : 256 : bool enable_jit = !(g_getenv("GJS_DISABLE_JIT"));
243 [ + - ]: 256 : if (enable_jit) {
244 : 256 : gjs_debug(GJS_DEBUG_CONTEXT, "Enabling JIT");
245 : : }
246 : 256 : JS::ContextOptionsRef(cx).setAsmJS(enable_jit);
247 : :
248 [ + - ]: 256 : uint32_t value = enable_jit ? 1 : 0;
249 : :
250 : 256 : JS_SetGlobalJitCompilerOption(
251 : : cx, JSJitCompilerOption::JSJITCOMPILER_ION_ENABLE, value);
252 : 256 : JS_SetGlobalJitCompilerOption(
253 : : cx, JSJitCompilerOption::JSJITCOMPILER_BASELINE_ENABLE, value);
254 : 256 : JS_SetGlobalJitCompilerOption(
255 : : cx, JSJitCompilerOption::JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
256 : : value);
257 : :
258 : 256 : return cx;
259 : 256 : }
|