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