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