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