1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7 /* Per JSContext object */
8
9 #include "mozilla/MemoryReporting.h"
10 #include "mozilla/UniquePtr.h"
11
12 #include "xpcprivate.h"
13 #include "xpcpublic.h"
14 #include "XPCWrapper.h"
15 #include "XPCJSMemoryReporter.h"
16 #include "WrapperFactory.h"
17 #include "mozJSComponentLoader.h"
18 #include "nsNetUtil.h"
19 #include "nsThreadUtils.h"
20
21 #include "nsIObserverService.h"
22 #include "nsIDebug2.h"
23 #include "nsPIDOMWindow.h"
24 #include "nsPrintfCString.h"
25 #include "mozilla/Preferences.h"
26 #include "mozilla/Telemetry.h"
27 #include "mozilla/Services.h"
28 #ifdef FUZZING
29 # include "mozilla/StaticPrefs_fuzzing.h"
30 #endif
31 #include "mozilla/StaticPrefs_browser.h"
32 #include "mozilla/StaticPrefs_javascript.h"
33 #include "mozilla/dom/ScriptSettings.h"
34
35 #include "nsContentUtils.h"
36 #include "nsCCUncollectableMarker.h"
37 #include "nsCycleCollectionNoteRootCallback.h"
38 #include "nsCycleCollector.h"
39 #include "jsapi.h"
40 #include "js/ContextOptions.h"
41 #include "js/MemoryMetrics.h"
42 #include "mozilla/dom/BindingUtils.h"
43 #include "mozilla/dom/Element.h"
44 #include "mozilla/dom/ScriptLoader.h"
45 #include "mozilla/dom/WindowBinding.h"
46 #include "mozilla/extensions/WebExtensionPolicy.h"
47 #include "mozilla/Atomics.h"
48 #include "mozilla/Attributes.h"
49 #include "mozilla/ProcessHangMonitor.h"
50 #include "mozilla/Sprintf.h"
51 #include "mozilla/SystemPrincipal.h"
52 #include "mozilla/ThreadLocal.h"
53 #include "mozilla/UniquePtrExtensions.h"
54 #include "mozilla/Unused.h"
55 #include "AccessCheck.h"
56 #include "nsGlobalWindow.h"
57 #include "nsAboutProtocolUtils.h"
58
59 #include "GeckoProfiler.h"
60 #include "nsIXULRuntime.h"
61 #include "nsJSPrincipals.h"
62 #include "ExpandedPrincipal.h"
63 #ifdef MOZ_GECKO_PROFILER
64 # include "ProfilerMarkerPayload.h"
65 #endif
66
67 #if defined(XP_LINUX) && !defined(ANDROID)
68 // For getrlimit and min/max.
69 # include <algorithm>
70 # include <sys/resource.h>
71 #endif
72
73 #ifdef XP_WIN
74 // For min.
75 # include <algorithm>
76 # include <windows.h>
77 #endif
78
79 using namespace mozilla;
80 using namespace xpc;
81 using namespace JS;
82 using mozilla::dom::AutoEntryScript;
83
84 // The watchdog thread loop is pretty trivial, and should not require much stack
85 // space to do its job. So only give it 32KiB or the platform minimum.
86 #if !defined(PTHREAD_STACK_MIN)
87 # define PTHREAD_STACK_MIN 0
88 #endif
89 static constexpr size_t kWatchdogStackSize =
90 PTHREAD_STACK_MIN < 32 * 1024 ? 32 * 1024 : PTHREAD_STACK_MIN;
91
92 static void WatchdogMain(void* arg);
93 class Watchdog;
94 class WatchdogManager;
95 class MOZ_RAII AutoLockWatchdog final {
96 MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
97 Watchdog* const mWatchdog;
98
99 public:
100 explicit AutoLockWatchdog(
101 Watchdog* aWatchdog MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
102 ~AutoLockWatchdog();
103 };
104
105 class Watchdog {
106 public:
Watchdog(WatchdogManager * aManager)107 explicit Watchdog(WatchdogManager* aManager)
108 : mManager(aManager),
109 mLock(nullptr),
110 mWakeup(nullptr),
111 mThread(nullptr),
112 mHibernating(false),
113 mInitialized(false),
114 mShuttingDown(false),
115 mMinScriptRunTimeSeconds(1) {}
~Watchdog()116 ~Watchdog() { MOZ_ASSERT(!Initialized()); }
117
Manager()118 WatchdogManager* Manager() { return mManager; }
Initialized()119 bool Initialized() { return mInitialized; }
ShuttingDown()120 bool ShuttingDown() { return mShuttingDown; }
GetLock()121 PRLock* GetLock() { return mLock; }
Hibernating()122 bool Hibernating() { return mHibernating; }
WakeUp()123 void WakeUp() {
124 MOZ_ASSERT(Initialized());
125 MOZ_ASSERT(Hibernating());
126 mHibernating = false;
127 PR_NotifyCondVar(mWakeup);
128 }
129
130 //
131 // Invoked by the main thread only.
132 //
133
Init()134 void Init() {
135 MOZ_ASSERT(NS_IsMainThread());
136 mLock = PR_NewLock();
137 if (!mLock) {
138 MOZ_CRASH("PR_NewLock failed.");
139 }
140
141 mWakeup = PR_NewCondVar(mLock);
142 if (!mWakeup) {
143 MOZ_CRASH("PR_NewCondVar failed.");
144 }
145
146 {
147 // Make sure the debug service is instantiated before we create the
148 // watchdog thread, since we intentionally try to keep the thread's stack
149 // segment as small as possible. It isn't always large enough to
150 // instantiate a new service, and even when it is, we don't want fault in
151 // extra pages if we can avoid it.
152 nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
153 Unused << dbg;
154 }
155
156 {
157 AutoLockWatchdog lock(this);
158
159 // Gecko uses thread private for accounting and has to clean up at thread
160 // exit. Therefore, even though we don't have a return value from the
161 // watchdog, we need to join it on shutdown.
162 mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
163 PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
164 PR_JOINABLE_THREAD, kWatchdogStackSize);
165 if (!mThread) {
166 MOZ_CRASH("PR_CreateThread failed!");
167 }
168
169 // WatchdogMain acquires the lock and then asserts mInitialized. So
170 // make sure to set mInitialized before releasing the lock here so
171 // that it's atomic with the creation of the thread.
172 mInitialized = true;
173 }
174 }
175
Shutdown()176 void Shutdown() {
177 MOZ_ASSERT(NS_IsMainThread());
178 MOZ_ASSERT(Initialized());
179 { // Scoped lock.
180 AutoLockWatchdog lock(this);
181
182 // Signal to the watchdog thread that it's time to shut down.
183 mShuttingDown = true;
184
185 // Wake up the watchdog, and wait for it to call us back.
186 PR_NotifyCondVar(mWakeup);
187 }
188
189 PR_JoinThread(mThread);
190
191 // The thread sets mShuttingDown to false as it exits.
192 MOZ_ASSERT(!mShuttingDown);
193
194 // Destroy state.
195 mThread = nullptr;
196 PR_DestroyCondVar(mWakeup);
197 mWakeup = nullptr;
198 PR_DestroyLock(mLock);
199 mLock = nullptr;
200
201 // All done.
202 mInitialized = false;
203 }
204
SetMinScriptRunTimeSeconds(int32_t seconds)205 void SetMinScriptRunTimeSeconds(int32_t seconds) {
206 // This variable is atomic, and is set from the main thread without
207 // locking.
208 MOZ_ASSERT(seconds > 0);
209 mMinScriptRunTimeSeconds = seconds;
210 }
211
212 //
213 // Invoked by the watchdog thread only.
214 //
215
Hibernate()216 void Hibernate() {
217 MOZ_ASSERT(!NS_IsMainThread());
218 mHibernating = true;
219 Sleep(PR_INTERVAL_NO_TIMEOUT);
220 }
Sleep(PRIntervalTime timeout)221 void Sleep(PRIntervalTime timeout) {
222 MOZ_ASSERT(!NS_IsMainThread());
223 MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS);
224 }
Finished()225 void Finished() {
226 MOZ_ASSERT(!NS_IsMainThread());
227 mShuttingDown = false;
228 }
229
MinScriptRunTimeSeconds()230 int32_t MinScriptRunTimeSeconds() { return mMinScriptRunTimeSeconds; }
231
232 private:
233 WatchdogManager* mManager;
234
235 PRLock* mLock;
236 PRCondVar* mWakeup;
237 PRThread* mThread;
238 bool mHibernating;
239 bool mInitialized;
240 bool mShuttingDown;
241 mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
242 };
243
244 #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
245 #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
246 #define PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT \
247 "dom.max_ext_content_script_run_time"
248
249 static const char* gCallbackPrefs[] = {
250 "dom.use_watchdog",
251 PREF_MAX_SCRIPT_RUN_TIME_CONTENT,
252 PREF_MAX_SCRIPT_RUN_TIME_CHROME,
253 PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT,
254 nullptr,
255 };
256
257 class WatchdogManager {
258 public:
WatchdogManager()259 explicit WatchdogManager() {
260 // All the timestamps start at zero.
261 PodArrayZero(mTimestamps);
262
263 // Register ourselves as an observer to get updates on the pref.
264 Preferences::RegisterCallbacks(PrefsChanged, gCallbackPrefs, this);
265 }
266
~WatchdogManager()267 virtual ~WatchdogManager() {
268 // Shutting down the watchdog requires context-switching to the watchdog
269 // thread, which isn't great to do in a destructor. So we require
270 // consumers to shut it down manually before releasing it.
271 MOZ_ASSERT(!mWatchdog);
272 }
273
274 private:
PrefsChanged(const char * aPref,void * aSelf)275 static void PrefsChanged(const char* aPref, void* aSelf) {
276 static_cast<WatchdogManager*>(aSelf)->RefreshWatchdog();
277 }
278
279 public:
Shutdown()280 void Shutdown() {
281 Preferences::UnregisterCallbacks(PrefsChanged, gCallbackPrefs, this);
282 }
283
RegisterContext(XPCJSContext * aContext)284 void RegisterContext(XPCJSContext* aContext) {
285 MOZ_ASSERT(NS_IsMainThread());
286 AutoLockWatchdog lock(mWatchdog.get());
287
288 if (aContext->mActive == XPCJSContext::CONTEXT_ACTIVE) {
289 mActiveContexts.insertBack(aContext);
290 } else {
291 mInactiveContexts.insertBack(aContext);
292 }
293
294 // Enable the watchdog, if appropriate.
295 RefreshWatchdog();
296 }
297
UnregisterContext(XPCJSContext * aContext)298 void UnregisterContext(XPCJSContext* aContext) {
299 MOZ_ASSERT(NS_IsMainThread());
300 AutoLockWatchdog lock(mWatchdog.get());
301
302 // aContext must be in one of our two lists, simply remove it.
303 aContext->LinkedListElement<XPCJSContext>::remove();
304
305 #ifdef DEBUG
306 // If this was the last context, we should have already shut down
307 // the watchdog.
308 if (mActiveContexts.isEmpty() && mInactiveContexts.isEmpty()) {
309 MOZ_ASSERT(!mWatchdog);
310 }
311 #endif
312 }
313
314 // Context statistics. These live on the watchdog manager, are written
315 // from the main thread, and are read from the watchdog thread (holding
316 // the lock in each case).
RecordContextActivity(XPCJSContext * aContext,bool active)317 void RecordContextActivity(XPCJSContext* aContext, bool active) {
318 // The watchdog reads this state, so acquire the lock before writing it.
319 MOZ_ASSERT(NS_IsMainThread());
320 AutoLockWatchdog lock(mWatchdog.get());
321
322 // Write state.
323 aContext->mLastStateChange = PR_Now();
324 aContext->mActive =
325 active ? XPCJSContext::CONTEXT_ACTIVE : XPCJSContext::CONTEXT_INACTIVE;
326 UpdateContextLists(aContext);
327
328 // The watchdog may be hibernating, waiting for the context to go
329 // active. Wake it up if necessary.
330 if (active && mWatchdog && mWatchdog->Hibernating()) {
331 mWatchdog->WakeUp();
332 }
333 }
334
IsAnyContextActive()335 bool IsAnyContextActive() { return !mActiveContexts.isEmpty(); }
TimeSinceLastActiveContext()336 PRTime TimeSinceLastActiveContext() {
337 // Must be called on the watchdog thread with the lock held.
338 MOZ_ASSERT(!NS_IsMainThread());
339 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock());
340 MOZ_ASSERT(mActiveContexts.isEmpty());
341 MOZ_ASSERT(!mInactiveContexts.isEmpty());
342
343 // We store inactive contexts with the most recently added inactive
344 // context at the end of the list.
345 return PR_Now() - mInactiveContexts.getLast()->mLastStateChange;
346 }
347
RecordTimestamp(WatchdogTimestampCategory aCategory)348 void RecordTimestamp(WatchdogTimestampCategory aCategory) {
349 // Must be called on the watchdog thread with the lock held.
350 MOZ_ASSERT(!NS_IsMainThread());
351 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock());
352 MOZ_ASSERT(aCategory != TimestampContextStateChange,
353 "Use RecordContextActivity to update this");
354
355 mTimestamps[aCategory] = PR_Now();
356 }
357
GetContextTimestamp(XPCJSContext * aContext,const AutoLockWatchdog & aProofOfLock)358 PRTime GetContextTimestamp(XPCJSContext* aContext,
359 const AutoLockWatchdog& aProofOfLock) {
360 return aContext->mLastStateChange;
361 }
362
GetTimestamp(WatchdogTimestampCategory aCategory,const AutoLockWatchdog & aProofOfLock)363 PRTime GetTimestamp(WatchdogTimestampCategory aCategory,
364 const AutoLockWatchdog& aProofOfLock) {
365 MOZ_ASSERT(aCategory != TimestampContextStateChange,
366 "Use GetContextTimestamp to retrieve this");
367 return mTimestamps[aCategory];
368 }
369
GetWatchdog()370 Watchdog* GetWatchdog() { return mWatchdog.get(); }
371
RefreshWatchdog()372 void RefreshWatchdog() {
373 bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true);
374 if (wantWatchdog != !!mWatchdog) {
375 if (wantWatchdog) {
376 StartWatchdog();
377 } else {
378 StopWatchdog();
379 }
380 }
381
382 if (mWatchdog) {
383 int32_t contentTime =
384 Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
385 if (contentTime <= 0) {
386 contentTime = INT32_MAX;
387 }
388 int32_t chromeTime =
389 Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
390 if (chromeTime <= 0) {
391 chromeTime = INT32_MAX;
392 }
393 int32_t extTime =
394 Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT, 5);
395 if (extTime <= 0) {
396 extTime = INT32_MAX;
397 }
398 mWatchdog->SetMinScriptRunTimeSeconds(
399 std::min({contentTime, chromeTime, extTime}));
400 }
401 }
402
StartWatchdog()403 void StartWatchdog() {
404 MOZ_ASSERT(!mWatchdog);
405 mWatchdog = mozilla::MakeUnique<Watchdog>(this);
406 mWatchdog->Init();
407 }
408
StopWatchdog()409 void StopWatchdog() {
410 MOZ_ASSERT(mWatchdog);
411 mWatchdog->Shutdown();
412 mWatchdog = nullptr;
413 }
414
415 template <class Callback>
ForAllActiveContexts(Callback && aCallback)416 void ForAllActiveContexts(Callback&& aCallback) {
417 // This function must be called on the watchdog thread with the lock held.
418 MOZ_ASSERT(!NS_IsMainThread());
419 PR_ASSERT_CURRENT_THREAD_OWNS_LOCK(mWatchdog->GetLock());
420
421 for (auto* context = mActiveContexts.getFirst(); context;
422 context = context->LinkedListElement<XPCJSContext>::getNext()) {
423 if (!aCallback(context)) {
424 return;
425 }
426 }
427 }
428
429 private:
UpdateContextLists(XPCJSContext * aContext)430 void UpdateContextLists(XPCJSContext* aContext) {
431 // Given aContext whose activity state or timestamp has just changed,
432 // put it back in the proper position in the proper list.
433 aContext->LinkedListElement<XPCJSContext>::remove();
434 auto& list = aContext->mActive == XPCJSContext::CONTEXT_ACTIVE
435 ? mActiveContexts
436 : mInactiveContexts;
437
438 // Either the new list is empty or aContext must be more recent than
439 // the existing last element.
440 MOZ_ASSERT_IF(!list.isEmpty(), list.getLast()->mLastStateChange <
441 aContext->mLastStateChange);
442 list.insertBack(aContext);
443 }
444
445 LinkedList<XPCJSContext> mActiveContexts;
446 LinkedList<XPCJSContext> mInactiveContexts;
447 mozilla::UniquePtr<Watchdog> mWatchdog;
448
449 // We store ContextStateChange on the contexts themselves.
450 PRTime mTimestamps[kWatchdogTimestampCategoryCount - 1];
451 };
452
AutoLockWatchdog(Watchdog * aWatchdog MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)453 AutoLockWatchdog::AutoLockWatchdog(
454 Watchdog* aWatchdog MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
455 : mWatchdog(aWatchdog) {
456 MOZ_GUARD_OBJECT_NOTIFIER_INIT;
457 if (mWatchdog) {
458 PR_Lock(mWatchdog->GetLock());
459 }
460 }
461
~AutoLockWatchdog()462 AutoLockWatchdog::~AutoLockWatchdog() {
463 if (mWatchdog) {
464 PR_Unlock(mWatchdog->GetLock());
465 }
466 }
467
WatchdogMain(void * arg)468 static void WatchdogMain(void* arg) {
469 AUTO_PROFILER_REGISTER_THREAD("JS Watchdog");
470 // Create an nsThread wrapper for the thread and register it with the thread
471 // manager.
472 Unused << NS_GetCurrentThread();
473 NS_SetCurrentThreadName("JS Watchdog");
474
475 Watchdog* self = static_cast<Watchdog*>(arg);
476 WatchdogManager* manager = self->Manager();
477
478 // Lock lasts until we return
479 AutoLockWatchdog lock(self);
480
481 MOZ_ASSERT(self->Initialized());
482 while (!self->ShuttingDown()) {
483 // Sleep only 1 second if recently (or currently) active; otherwise,
484 // hibernate
485 if (manager->IsAnyContextActive() ||
486 manager->TimeSinceLastActiveContext() <= PRTime(2 * PR_USEC_PER_SEC)) {
487 self->Sleep(PR_TicksPerSecond());
488 } else {
489 manager->RecordTimestamp(TimestampWatchdogHibernateStart);
490 self->Hibernate();
491 manager->RecordTimestamp(TimestampWatchdogHibernateStop);
492 }
493
494 // Rise and shine.
495 manager->RecordTimestamp(TimestampWatchdogWakeup);
496
497 // Don't request an interrupt callback unless the current script has
498 // been running long enough that we might show the slow script dialog.
499 // Triggering the callback from off the main thread can be expensive.
500
501 // We want to avoid showing the slow script dialog if the user's laptop
502 // goes to sleep in the middle of running a script. To ensure this, we
503 // invoke the interrupt callback after only half the timeout has
504 // elapsed. The callback simply records the fact that it was called in
505 // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
506 // seconds and invoke the callback again. This time around it sees
507 // mSlowScriptSecondHalf is set and so it shows the slow script
508 // dialog. If the computer is put to sleep during one of the (timeout/2)
509 // periods, the script still has the other (timeout/2) seconds to
510 // finish.
511 if (!self->ShuttingDown() && manager->IsAnyContextActive()) {
512 bool debuggerAttached = false;
513 nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
514 if (dbg) {
515 dbg->GetIsDebuggerAttached(&debuggerAttached);
516 }
517 if (debuggerAttached) {
518 // We won't be interrupting these scripts anyway.
519 continue;
520 }
521
522 PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2;
523 manager->ForAllActiveContexts([usecs, manager,
524 &lock](XPCJSContext* aContext) -> bool {
525 auto timediff = PR_Now() - manager->GetContextTimestamp(aContext, lock);
526 if (timediff > usecs) {
527 JS_RequestInterruptCallback(aContext->Context());
528 return true;
529 }
530 return false;
531 });
532 }
533 }
534
535 // Tell the manager that we've shut down.
536 self->Finished();
537 }
538
GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)539 PRTime XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory) {
540 AutoLockWatchdog lock(mWatchdogManager->GetWatchdog());
541 return aCategory == TimestampContextStateChange
542 ? mWatchdogManager->GetContextTimestamp(this, lock)
543 : mWatchdogManager->GetTimestamp(aCategory, lock);
544 }
545
546 // static
RecordScriptActivity(bool aActive)547 bool XPCJSContext::RecordScriptActivity(bool aActive) {
548 MOZ_ASSERT(NS_IsMainThread());
549
550 XPCJSContext* xpccx = XPCJSContext::Get();
551 if (!xpccx) {
552 // mozilla::SpinEventLoopUntil may use AutoScriptActivity(false) after
553 // we destroyed the XPCJSContext.
554 MOZ_ASSERT(!aActive);
555 return false;
556 }
557
558 bool oldValue = xpccx->SetHasScriptActivity(aActive);
559 if (aActive == oldValue) {
560 // Nothing to do.
561 return oldValue;
562 }
563
564 if (!aActive) {
565 ProcessHangMonitor::ClearHang();
566 }
567 xpccx->mWatchdogManager->RecordContextActivity(xpccx, aActive);
568
569 return oldValue;
570 }
571
AutoScriptActivity(bool aActive)572 AutoScriptActivity::AutoScriptActivity(bool aActive)
573 : mActive(aActive),
574 mOldValue(XPCJSContext::RecordScriptActivity(aActive)) {}
575
~AutoScriptActivity()576 AutoScriptActivity::~AutoScriptActivity() {
577 MOZ_ALWAYS_TRUE(mActive == XPCJSContext::RecordScriptActivity(mOldValue));
578 }
579
580 // static
InterruptCallback(JSContext * cx)581 bool XPCJSContext::InterruptCallback(JSContext* cx) {
582 XPCJSContext* self = XPCJSContext::Get();
583
584 // Now is a good time to turn on profiling if it's pending.
585 PROFILER_JS_INTERRUPT_CALLBACK();
586
587 #ifdef MOZ_GECKO_PROFILER
588 nsDependentCString filename("unknown file");
589 JS::AutoFilename scriptFilename;
590 // Computing the line number can be very expensive (see bug 1330231 for
591 // example), so don't request it here.
592 if (JS::DescribeScriptedCaller(cx, &scriptFilename)) {
593 if (const char* file = scriptFilename.get()) {
594 filename.Assign(file, strlen(file));
595 }
596 PROFILER_ADD_MARKER_WITH_PAYLOAD("JS::InterruptCallback", JS,
597 TextMarkerPayload,
598 (filename, TimeStamp::Now()));
599 }
600 #endif
601
602 // Normally we record mSlowScriptCheckpoint when we start to process an
603 // event. However, we can run JS outside of event handlers. This code takes
604 // care of that case.
605 if (self->mSlowScriptCheckpoint.IsNull()) {
606 self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
607 self->mSlowScriptSecondHalf = false;
608 self->mSlowScriptActualWait = mozilla::TimeDuration();
609 self->mTimeoutAccumulated = false;
610 return true;
611 }
612
613 // Sometimes we get called back during XPConnect initialization, before Gecko
614 // has finished bootstrapping. Avoid crashing in nsContentUtils below.
615 if (!nsContentUtils::IsInitialized()) {
616 return true;
617 }
618
619 // This is at least the second interrupt callback we've received since
620 // returning to the event loop. See how long it's been, and what the limit
621 // is.
622 TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
623 int32_t limit;
624
625 nsString addonId;
626 const char* prefName;
627
628 auto principal = BasePrincipal::Cast(nsContentUtils::SubjectPrincipal(cx));
629 bool chrome = principal->Is<SystemPrincipal>();
630 if (chrome) {
631 prefName = PREF_MAX_SCRIPT_RUN_TIME_CHROME;
632 limit = Preferences::GetInt(prefName, 20);
633 } else if (auto policy = principal->ContentScriptAddonPolicy()) {
634 policy->GetId(addonId);
635 prefName = PREF_MAX_SCRIPT_RUN_TIME_EXT_CONTENT;
636 limit = Preferences::GetInt(prefName, 5);
637 } else {
638 prefName = PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
639 limit = Preferences::GetInt(prefName, 10);
640 }
641
642 // If there's no limit, or we're within the limit, let it go.
643 if (limit == 0 || duration.ToSeconds() < limit / 2.0) {
644 return true;
645 }
646
647 self->mSlowScriptActualWait += duration;
648
649 // In order to guard against time changes or laptops going to sleep, we
650 // don't trigger the slow script warning until (limit/2) seconds have
651 // elapsed twice.
652 if (!self->mSlowScriptSecondHalf) {
653 self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
654 self->mSlowScriptSecondHalf = true;
655 return true;
656 }
657
658 //
659 // This has gone on long enough! Time to take action. ;-)
660 //
661
662 // Get the DOM window associated with the running script. If the script is
663 // running in a non-DOM scope, we have to just let it keep running.
664 RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
665 RefPtr<nsGlobalWindowInner> win = WindowOrNull(global);
666 if (!win && IsSandbox(global)) {
667 // If this is a sandbox associated with a DOMWindow via a
668 // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
669 // and JetPack content scripts.
670 JS::Rooted<JSObject*> proto(cx);
671 if (!JS_GetPrototype(cx, global, &proto)) {
672 return false;
673 }
674 if (proto && xpc::IsSandboxPrototypeProxy(proto) &&
675 (proto = js::CheckedUnwrapDynamic(proto, cx,
676 /* stopAtWindowProxy = */ false))) {
677 win = WindowGlobalOrNull(proto);
678 }
679 }
680
681 if (!win) {
682 NS_WARNING("No active window");
683 return true;
684 }
685
686 if (win->IsDying()) {
687 // The window is being torn down. When that happens we try to prevent
688 // the dispatch of new runnables, so it also makes sense to kill any
689 // long-running script. The user is primarily interested in this page
690 // going away.
691 return false;
692 }
693
694 // Accumulate slow script invokation delay.
695 if (!chrome && !self->mTimeoutAccumulated) {
696 uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() -
697 (limit * 1000.0));
698 Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
699 self->mTimeoutAccumulated = true;
700 }
701
702 // Show the prompt to the user, and kill if requested.
703 nsGlobalWindowInner::SlowScriptResponse response =
704 win->ShowSlowScriptDialog(cx, addonId);
705 if (response == nsGlobalWindowInner::KillSlowScript) {
706 if (Preferences::GetBool("dom.global_stop_script", true)) {
707 xpc::Scriptability::Get(global).Block();
708 }
709 return false;
710 }
711 if (response == nsGlobalWindowInner::KillScriptGlobal) {
712 nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
713
714 if (!IsSandbox(global) || !obs) {
715 return false;
716 }
717
718 // Notify the extensions framework that the sandbox should be killed.
719 nsIXPConnect* xpc = nsContentUtils::XPConnect();
720 JS::RootedObject wrapper(cx, JS_NewPlainObject(cx));
721 nsCOMPtr<nsISupports> supports;
722
723 // Store the sandbox object on the wrappedJSObject property of the
724 // subject so that JS recipients can access the JS value directly.
725 if (!wrapper ||
726 !JS_DefineProperty(cx, wrapper, "wrappedJSObject", global,
727 JSPROP_ENUMERATE) ||
728 NS_FAILED(xpc->WrapJS(cx, wrapper, NS_GET_IID(nsISupports),
729 getter_AddRefs(supports)))) {
730 return false;
731 }
732
733 obs->NotifyObservers(supports, "kill-content-script-sandbox", nullptr);
734 return false;
735 }
736
737 // The user chose to continue the script. Reset the timer, and disable this
738 // machinery with a pref of the user opted out of future slow-script dialogs.
739 if (response != nsGlobalWindowInner::ContinueSlowScriptAndKeepNotifying) {
740 self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
741 }
742
743 if (response == nsGlobalWindowInner::AlwaysContinueSlowScript) {
744 Preferences::SetInt(prefName, 0);
745 }
746
747 return true;
748 }
749
750 #define JS_OPTIONS_DOT_STR "javascript.options."
751
752 static mozilla::Atomic<bool> sDiscardSystemSource(false);
753
ShouldDiscardSystemSource()754 bool xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
755
756 static mozilla::Atomic<bool> sSharedMemoryEnabled(false);
757 static mozilla::Atomic<bool> sStreamsEnabled(false);
758
759 static mozilla::Atomic<bool> sPropertyErrorMessageFixEnabled(false);
760 static mozilla::Atomic<bool> sWeakRefsEnabled(false);
761 static mozilla::Atomic<bool> sIteratorHelpersEnabled(false);
762
SetPrefableRealmOptions(JS::RealmOptions & options)763 void xpc::SetPrefableRealmOptions(JS::RealmOptions& options) {
764 options.creationOptions()
765 .setSharedMemoryAndAtomicsEnabled(sSharedMemoryEnabled)
766 .setCoopAndCoepEnabled(
767 StaticPrefs::browser_tabs_remote_useCrossOriginOpenerPolicy() &&
768 StaticPrefs::browser_tabs_remote_useCrossOriginEmbedderPolicy())
769 .setStreamsEnabled(sStreamsEnabled)
770 .setWritableStreamsEnabled(
771 StaticPrefs::javascript_options_writable_streams())
772 .setPropertyErrorMessageFixEnabled(sPropertyErrorMessageFixEnabled)
773 .setWeakRefsEnabled(sWeakRefsEnabled)
774 .setIteratorHelpersEnabled(sIteratorHelpersEnabled);
775 }
776
LoadStartupJSPrefs(XPCJSContext * xpccx)777 static void LoadStartupJSPrefs(XPCJSContext* xpccx) {
778 // Prefs that require a restart are handled here. This includes the
779 // process-wide JIT options because toggling these at runtime can easily cause
780 // races or get us into an inconsistent state.
781 //
782 // 'Live' prefs are handled by ReloadPrefsCallback below.
783
784 JSContext* cx = xpccx->Context();
785
786 bool useBaselineInterp = Preferences::GetBool(JS_OPTIONS_DOT_STR "blinterp");
787 bool useBaselineJit = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit");
788 bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion");
789 bool useJitForTrustedPrincipals =
790 Preferences::GetBool(JS_OPTIONS_DOT_STR "jit_trustedprincipals");
791 bool useNativeRegExp =
792 Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp");
793
794 bool offthreadIonCompilation =
795 Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.offthread_compilation");
796 bool useBaselineEager = Preferences::GetBool(
797 JS_OPTIONS_DOT_STR "baselinejit.unsafe_eager_compilation");
798 bool useIonEager =
799 Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
800 #ifdef DEBUG
801 bool fullJitDebugChecks =
802 Preferences::GetBool(JS_OPTIONS_DOT_STR "jit.full_debug_checks");
803 #endif
804
805 int32_t baselineInterpThreshold =
806 Preferences::GetInt(JS_OPTIONS_DOT_STR "blinterp.threshold", -1);
807 int32_t baselineThreshold =
808 Preferences::GetInt(JS_OPTIONS_DOT_STR "baselinejit.threshold", -1);
809 int32_t normalIonThreshold =
810 Preferences::GetInt(JS_OPTIONS_DOT_STR "ion.threshold", -1);
811 int32_t fullIonThreshold =
812 Preferences::GetInt(JS_OPTIONS_DOT_STR "ion.full.threshold", -1);
813 int32_t ionFrequentBailoutThreshold = Preferences::GetInt(
814 JS_OPTIONS_DOT_STR "ion.frequent_bailout_threshold", -1);
815
816 bool spectreIndexMasking =
817 Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.index_masking");
818 bool spectreObjectMitigationsBarriers = Preferences::GetBool(
819 JS_OPTIONS_DOT_STR "spectre.object_mitigations.barriers");
820 bool spectreObjectMitigationsMisc = Preferences::GetBool(
821 JS_OPTIONS_DOT_STR "spectre.object_mitigations.misc");
822 bool spectreStringMitigations =
823 Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.string_mitigations");
824 bool spectreValueMasking =
825 Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.value_masking");
826 bool spectreJitToCxxCalls =
827 Preferences::GetBool(JS_OPTIONS_DOT_STR "spectre.jit_to_C++_calls");
828
829 bool disableWasmHugeMemory =
830 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_disable_huge_memory");
831
832 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
833 if (xr) {
834 bool safeMode = false;
835 xr->GetInSafeMode(&safeMode);
836 if (safeMode) {
837 useBaselineInterp = false;
838 useBaselineJit = false;
839 useIon = false;
840 useJitForTrustedPrincipals = false;
841 useNativeRegExp = false;
842 }
843 }
844
845 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_INTERPRETER_ENABLE,
846 useBaselineInterp);
847 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_ENABLE,
848 useBaselineJit);
849 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_ENABLE, useIon);
850 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_JIT_TRUSTEDPRINCIPALS_ENABLE,
851 useJitForTrustedPrincipals);
852 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_NATIVE_REGEXP_ENABLE,
853 useNativeRegExp);
854
855 JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
856
857 JS_SetGlobalJitCompilerOption(
858 cx, JSJITCOMPILER_BASELINE_INTERPRETER_WARMUP_TRIGGER,
859 baselineInterpThreshold);
860 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
861 useBaselineEager ? 0 : baselineThreshold);
862 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_NORMAL_WARMUP_TRIGGER,
863 useIonEager ? 0 : normalIonThreshold);
864 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_FULL_WARMUP_TRIGGER,
865 useIonEager ? 0 : fullIonThreshold);
866 JS_SetGlobalJitCompilerOption(cx,
867 JSJITCOMPILER_ION_FREQUENT_BAILOUT_THRESHOLD,
868 ionFrequentBailoutThreshold);
869
870 #ifdef DEBUG
871 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS,
872 fullJitDebugChecks);
873 #endif
874
875 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_INDEX_MASKING,
876 spectreIndexMasking);
877 JS_SetGlobalJitCompilerOption(
878 cx, JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_BARRIERS,
879 spectreObjectMitigationsBarriers);
880 JS_SetGlobalJitCompilerOption(cx,
881 JSJITCOMPILER_SPECTRE_OBJECT_MITIGATIONS_MISC,
882 spectreObjectMitigationsMisc);
883 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_STRING_MITIGATIONS,
884 spectreStringMitigations);
885 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_VALUE_MASKING,
886 spectreValueMasking);
887 JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_SPECTRE_JIT_TO_CXX_CALLS,
888 spectreJitToCxxCalls);
889 if (disableWasmHugeMemory) {
890 bool disabledHugeMemory = JS::DisableWasmHugeMemory();
891 MOZ_RELEASE_ASSERT(disabledHugeMemory);
892 }
893 }
894
ReloadPrefsCallback(const char * pref,void * aXpccx)895 static void ReloadPrefsCallback(const char* pref, void* aXpccx) {
896 // Note: Prefs that require a restart are handled in LoadStartupJSPrefs above.
897
898 auto xpccx = static_cast<XPCJSContext*>(aXpccx);
899 JSContext* cx = xpccx->Context();
900
901 bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs");
902 bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm");
903 bool useWasmTrustedPrincipals =
904 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_trustedprincipals");
905 bool useWasmIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_ionjit");
906 bool useWasmBaseline =
907 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit");
908 #ifdef ENABLE_WASM_CRANELIFT
909 bool useWasmCranelift =
910 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_cranelift");
911 #endif
912 bool useWasmReftypes =
913 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_reftypes");
914 #ifdef ENABLE_WASM_GC
915 bool useWasmGc = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_gc");
916 #endif
917 #ifdef ENABLE_WASM_MULTI_VALUE
918 bool useWasmMultiValue =
919 Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_multi_value");
920 #endif
921 #ifdef ENABLE_WASM_SIMD
922 bool useWasmSimd = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_simd");
923 #endif
924 bool useWasmVerbose = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_verbose");
925 bool throwOnAsmJSValidationFailure = Preferences::GetBool(
926 JS_OPTIONS_DOT_STR "throw_on_asmjs_validation_failure");
927
928 bool parallelParsing =
929 Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing");
930
931 sDiscardSystemSource =
932 Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
933
934 bool useSourcePragmas =
935 Preferences::GetBool(JS_OPTIONS_DOT_STR "source_pragmas");
936 bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack");
937
938 bool throwOnDebuggeeWouldRun =
939 Preferences::GetBool(JS_OPTIONS_DOT_STR "throw_on_debuggee_would_run");
940
941 bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool(
942 JS_OPTIONS_DOT_STR "dump_stack_on_debuggee_would_run");
943
944 sSharedMemoryEnabled =
945 Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
946 sStreamsEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "streams");
947 sPropertyErrorMessageFixEnabled =
948 Preferences::GetBool(JS_OPTIONS_DOT_STR "property_error_message_fix");
949 #ifdef NIGHTLY_BUILD
950 sWeakRefsEnabled =
951 Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.weakrefs");
952 sIteratorHelpersEnabled =
953 Preferences::GetBool(JS_OPTIONS_DOT_STR "experimental.iterator_helpers");
954 #endif
955
956 #ifdef JS_GC_ZEAL
957 int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1);
958 int32_t zeal_frequency = Preferences::GetInt(
959 JS_OPTIONS_DOT_STR "gczeal.frequency", JS_DEFAULT_ZEAL_FREQ);
960 if (zeal >= 0) {
961 JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency);
962 }
963 #endif // JS_GC_ZEAL
964
965 #ifdef FUZZING
966 bool fuzzingEnabled = StaticPrefs::fuzzing_enabled();
967 #endif
968
969 JS::ContextOptionsRef(cx)
970 .setAsmJS(useAsmJS)
971 #ifdef FUZZING
972 .setFuzzing(fuzzingEnabled)
973 #endif
974 .setWasm(useWasm)
975 .setWasmForTrustedPrinciples(useWasmTrustedPrincipals)
976 .setWasmIon(useWasmIon)
977 .setWasmBaseline(useWasmBaseline)
978 .setWasmReftypes(useWasmReftypes)
979 #ifdef ENABLE_WASM_CRANELIFT
980 .setWasmCranelift(useWasmCranelift)
981 #endif
982 #ifdef ENABLE_WASM_GC
983 .setWasmGc(useWasmGc)
984 #endif
985 #ifdef ENABLE_WASM_MULTI_VALUE
986 .setWasmMultiValue(useWasmMultiValue)
987 #endif
988 #ifdef ENABLE_WASM_SIMD
989 .setWasmSimd(useWasmSimd)
990 #endif
991 .setWasmVerbose(useWasmVerbose)
992 .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
993 .setSourcePragmas(useSourcePragmas)
994 .setAsyncStack(useAsyncStack)
995 .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
996 .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun);
997
998 nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
999 if (xr) {
1000 bool safeMode = false;
1001 xr->GetInSafeMode(&safeMode);
1002 if (safeMode) {
1003 JS::ContextOptionsRef(cx).disableOptionsForSafeMode();
1004 }
1005 }
1006
1007 JS_SetParallelParsingEnabled(cx, parallelParsing);
1008 }
1009
~XPCJSContext()1010 XPCJSContext::~XPCJSContext() {
1011 MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
1012 // Elsewhere we abort immediately if XPCJSContext initialization fails.
1013 // Therefore the context must be non-null.
1014 MOZ_ASSERT(MaybeContext());
1015
1016 Preferences::UnregisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR,
1017 this);
1018
1019 #ifdef FUZZING
1020 Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
1021 #endif
1022
1023 // Clear any pending exception. It might be an XPCWrappedJS, and if we try
1024 // to destroy it later we will crash.
1025 SetPendingException(nullptr);
1026
1027 // If we're the last XPCJSContext around, clean up the watchdog manager.
1028 if (--sInstanceCount == 0) {
1029 if (mWatchdogManager->GetWatchdog()) {
1030 mWatchdogManager->StopWatchdog();
1031 }
1032
1033 mWatchdogManager->UnregisterContext(this);
1034 mWatchdogManager->Shutdown();
1035 sWatchdogInstance = nullptr;
1036 } else {
1037 // Otherwise, simply remove ourselves from the list.
1038 mWatchdogManager->UnregisterContext(this);
1039 }
1040
1041 if (mCallContext) {
1042 mCallContext->SystemIsBeingShutDown();
1043 }
1044
1045 PROFILER_CLEAR_JS_CONTEXT();
1046 }
1047
XPCJSContext()1048 XPCJSContext::XPCJSContext()
1049 : mCallContext(nullptr),
1050 mAutoRoots(nullptr),
1051 mResolveName(JSID_VOID),
1052 mResolvingWrapper(nullptr),
1053 mWatchdogManager(GetWatchdogManager()),
1054 mSlowScriptSecondHalf(false),
1055 mTimeoutAccumulated(false),
1056 mHasScriptActivity(false),
1057 mPendingResult(NS_OK),
1058 mActive(CONTEXT_INACTIVE),
1059 mLastStateChange(PR_Now()) {
1060 MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
1061 MOZ_ASSERT(mWatchdogManager);
1062 ++sInstanceCount;
1063 mWatchdogManager->RegisterContext(this);
1064 }
1065
1066 /* static */
Get()1067 XPCJSContext* XPCJSContext::Get() {
1068 // Do an explicit null check, because this can get called from a process that
1069 // does not run JS.
1070 nsXPConnect* xpc = static_cast<nsXPConnect*>(nsXPConnect::XPConnect());
1071 return xpc ? xpc->GetContext() : nullptr;
1072 }
1073
1074 #ifdef XP_WIN
GetWindowsStackSize()1075 static size_t GetWindowsStackSize() {
1076 // First, get the stack base. Because the stack grows down, this is the top
1077 // of the stack.
1078 const uint8_t* stackTop;
1079 # ifdef _WIN64
1080 PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
1081 stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
1082 # else
1083 PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
1084 stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
1085 # endif
1086
1087 // Now determine the stack bottom. Note that we can't use tib->StackLimit,
1088 // because that's the size of the committed area and we're also interested
1089 // in the reserved pages below that.
1090 MEMORY_BASIC_INFORMATION mbi;
1091 if (!VirtualQuery(&mbi, &mbi, sizeof(mbi))) {
1092 MOZ_CRASH("VirtualQuery failed");
1093 }
1094
1095 const uint8_t* stackBottom =
1096 reinterpret_cast<const uint8_t*>(mbi.AllocationBase);
1097
1098 // Do some sanity checks.
1099 size_t stackSize = size_t(stackTop - stackBottom);
1100 MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024);
1101 MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024);
1102
1103 // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like
1104 // the guard page and large PGO stack frames.
1105 return stackSize - 10 * sizeof(uintptr_t) * 1024;
1106 }
1107 #endif
1108
Runtime() const1109 XPCJSRuntime* XPCJSContext::Runtime() const {
1110 return static_cast<XPCJSRuntime*>(CycleCollectedJSContext::Runtime());
1111 }
1112
CreateRuntime(JSContext * aCx)1113 CycleCollectedJSRuntime* XPCJSContext::CreateRuntime(JSContext* aCx) {
1114 return new XPCJSRuntime(aCx);
1115 }
1116
Initialize()1117 nsresult XPCJSContext::Initialize() {
1118 nsresult rv =
1119 CycleCollectedJSContext::Initialize(nullptr, JS::DefaultHeapMaxBytes);
1120 if (NS_WARN_IF(NS_FAILED(rv))) {
1121 return rv;
1122 }
1123
1124 MOZ_ASSERT(Context());
1125 JSContext* cx = Context();
1126
1127 // The JS engine permits us to set different stack limits for system code,
1128 // trusted script, and untrusted script. We have tests that ensure that
1129 // we can always execute 10 "heavy" (eval+with) stack frames deeper in
1130 // privileged code. Our stack sizes vary greatly in different configurations,
1131 // so satisfying those tests requires some care. Manual measurements of the
1132 // number of heavy stack frames achievable gives us the following rough data,
1133 // ordered by the effective categories in which they are grouped in the
1134 // JS_SetNativeStackQuota call (which predates this analysis).
1135 //
1136 // The following "Stack Frames" numbers come from `chromeLimit` in
1137 // js/xpconnect/tests/chrome/test_bug732665.xul
1138 //
1139 // Platform | Build | Stack Quota | Stack Frames | Stack Frame Size
1140 // ------------+-------+-------------+--------------+------------------
1141 // OSX 64 | Opt | 7MB | 1331 | ~5.4k
1142 // OSX 64 | Debug | 7MB | 1202 | ~6.0k
1143 // ------------+-------+-------------+--------------+------------------
1144 // Linux 32 | Opt | 7.875MB | 2513 | ~3.2k
1145 // Linux 32 | Debug | 7.875MB | 2146 | ~3.8k
1146 // ------------+-------+-------------+--------------+------------------
1147 // Linux 64 | Opt | 7.875MB | 1360 | ~5.9k
1148 // Linux 64 | Debug | 7.875MB | 1180 | ~6.8k
1149 // Linux 64 | ASan | 7.875MB | 473 | ~17.0k
1150 // ------------+-------+-------------+--------------+------------------
1151 // Windows 32 | Opt | 984k | 188 | ~5.2k
1152 // Windows 32 | Debug | 984k | 208 | ~4.7k
1153 // ------------+-------+-------------+--------------+------------------
1154 // Windows 64 | Opt | 1.922MB | 189 | ~10.4k
1155 // Windows 64 | Debug | 1.922MB | 175 | ~11.2k
1156 //
1157 // We tune the trusted/untrusted quotas for each configuration to achieve our
1158 // invariants while attempting to minimize overhead. In contrast, our buffer
1159 // between system code and trusted script is a very unscientific 10k.
1160 const size_t kSystemCodeBuffer = 10 * 1024;
1161
1162 // Our "default" stack is what we use in configurations where we don't have
1163 // a compelling reason to do things differently. This is effectively 512KB
1164 // on 32-bit platforms and 1MB on 64-bit platforms.
1165 const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024;
1166
1167 // Set maximum stack size for different configurations. This value is then
1168 // capped below because huge stacks are not web-compatible.
1169
1170 #if defined(XP_MACOSX) || defined(DARWIN)
1171 // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB,
1172 // and give trusted script 180k extra. The stack is huge on mac anyway.
1173 const size_t kUncappedStackQuota = 7 * 1024 * 1024;
1174 const size_t kTrustedScriptBuffer = 180 * 1024;
1175 #elif defined(XP_LINUX) && !defined(ANDROID)
1176 // Most Linux distributions set default stack size to 8MB. Use it as the
1177 // maximum value.
1178 const size_t kStackQuotaMax = 8 * 1024 * 1024;
1179 # if defined(MOZ_ASAN) || defined(DEBUG)
1180 // Bug 803182: account for the 4x difference in the size of js::Interpret
1181 // between optimized and debug builds. We use 2x since the JIT part
1182 // doesn't increase much.
1183 // See the standalone MOZ_ASAN branch below for the ASan case.
1184 const size_t kStackQuotaMin = 2 * kDefaultStackQuota;
1185 # else
1186 const size_t kStackQuotaMin = kDefaultStackQuota;
1187 # endif
1188 // Allocate 128kB margin for the safe space.
1189 const size_t kStackSafeMargin = 128 * 1024;
1190
1191 struct rlimit rlim;
1192 const size_t kUncappedStackQuota =
1193 getrlimit(RLIMIT_STACK, &rlim) == 0
1194 ? std::max(std::min(size_t(rlim.rlim_cur - kStackSafeMargin),
1195 kStackQuotaMax - kStackSafeMargin),
1196 kStackQuotaMin)
1197 : kStackQuotaMin;
1198 # if defined(MOZ_ASAN)
1199 // See the standalone MOZ_ASAN branch below for the ASan case.
1200 const size_t kTrustedScriptBuffer = 450 * 1024;
1201 # else
1202 const size_t kTrustedScriptBuffer = 180 * 1024;
1203 # endif
1204 #elif defined(XP_WIN)
1205 // 1MB is the default stack size on Windows. We use the -STACK linker flag
1206 // (see WIN32_EXE_LDFLAGS in config/config.mk) to request a larger stack, so
1207 // we determine the stack size at runtime.
1208 const size_t kUncappedStackQuota = GetWindowsStackSize();
1209 # if defined(MOZ_ASAN)
1210 // See the standalone MOZ_ASAN branch below for the ASan case.
1211 const size_t kTrustedScriptBuffer = 450 * 1024;
1212 # else
1213 const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8)
1214 ? 180 * 1024 // win64
1215 : 120 * 1024; // win32
1216 # endif
1217 #elif defined(MOZ_ASAN)
1218 // ASan requires more stack space due to red-zones, so give it double the
1219 // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
1220 // were not taken at the time of this writing, so we hazard a guess that
1221 // ASAN builds have roughly thrice the stack overhead as normal builds.
1222 // On normal builds, the largest stack frame size we might encounter is
1223 // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k.
1224 //
1225 // FIXME: Does this branch make sense for Windows and Android?
1226 // (See bug 1415195)
1227 const size_t kUncappedStackQuota = 2 * kDefaultStackQuota;
1228 const size_t kTrustedScriptBuffer = 450 * 1024;
1229 #elif defined(ANDROID)
1230 // Android appears to have 1MB stacks. Allow the use of 3/4 of that size
1231 // (768KB on 32-bit), since otherwise we can crash with a stack overflow
1232 // when nearing the 1MB limit.
1233 const size_t kUncappedStackQuota =
1234 kDefaultStackQuota + kDefaultStackQuota / 2;
1235 const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
1236 #else
1237 // Catch-all configuration for other environments.
1238 # if defined(DEBUG)
1239 const size_t kUncappedStackQuota = 2 * kDefaultStackQuota;
1240 # else
1241 const size_t kUncappedStackQuota = kDefaultStackQuota;
1242 # endif
1243 // Given the numbers above, we use 50k and 100k trusted buffers on 32-bit
1244 // and 64-bit respectively.
1245 const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
1246 #endif
1247
1248 // Avoid an unused variable warning on platforms where we don't use the
1249 // default.
1250 (void)kDefaultStackQuota;
1251
1252 // Large stacks are not web-compatible so cap to a smaller value.
1253 // See bug 1537609 and bug 1562700.
1254 const size_t kStackQuotaCap =
1255 StaticPrefs::javascript_options_main_thread_stack_quota_cap();
1256 const size_t kStackQuota = std::min(kUncappedStackQuota, kStackQuotaCap);
1257
1258 JS_SetNativeStackQuota(
1259 cx, kStackQuota, kStackQuota - kSystemCodeBuffer,
1260 kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
1261
1262 PROFILER_SET_JS_CONTEXT(cx);
1263
1264 JS_AddInterruptCallback(cx, InterruptCallback);
1265
1266 Runtime()->Initialize(cx);
1267
1268 LoadStartupJSPrefs(this);
1269
1270 // Watch for the JS boolean options.
1271 ReloadPrefsCallback(nullptr, this);
1272 Preferences::RegisterPrefixCallback(ReloadPrefsCallback, JS_OPTIONS_DOT_STR,
1273 this);
1274
1275 #ifdef FUZZING
1276 Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
1277 #endif
1278
1279 if (!JS::InitSelfHostedCode(cx)) {
1280 // Note: If no exception is pending, failure is due to OOM.
1281 if (!JS_IsExceptionPending(cx) || JS_IsThrowingOutOfMemory(cx)) {
1282 NS_ABORT_OOM(0); // Size is unknown.
1283 }
1284
1285 // Failed to execute self-hosted JavaScript! Uh oh.
1286 MOZ_CRASH("InitSelfHostedCode failed");
1287 }
1288
1289 MOZ_RELEASE_ASSERT(Runtime()->InitializeStrings(cx),
1290 "InitializeStrings failed");
1291
1292 return NS_OK;
1293 }
1294
1295 // static
1296 uint32_t XPCJSContext::sInstanceCount;
1297
1298 // static
1299 StaticAutoPtr<WatchdogManager> XPCJSContext::sWatchdogInstance;
1300
1301 // static
GetWatchdogManager()1302 WatchdogManager* XPCJSContext::GetWatchdogManager() {
1303 if (sWatchdogInstance) {
1304 return sWatchdogInstance;
1305 }
1306
1307 MOZ_ASSERT(sInstanceCount == 0);
1308 sWatchdogInstance = new WatchdogManager();
1309 return sWatchdogInstance;
1310 }
1311
1312 // static
NewXPCJSContext()1313 XPCJSContext* XPCJSContext::NewXPCJSContext() {
1314 XPCJSContext* self = new XPCJSContext();
1315 nsresult rv = self->Initialize();
1316 if (NS_FAILED(rv)) {
1317 MOZ_CRASH("new XPCJSContext failed to initialize.");
1318 }
1319
1320 if (self->Context()) {
1321 return self;
1322 }
1323
1324 MOZ_CRASH("new XPCJSContext failed to initialize.");
1325 }
1326
BeforeProcessTask(bool aMightBlock)1327 void XPCJSContext::BeforeProcessTask(bool aMightBlock) {
1328 MOZ_ASSERT(NS_IsMainThread());
1329
1330 // Start the slow script timer.
1331 mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
1332 mSlowScriptSecondHalf = false;
1333 mSlowScriptActualWait = mozilla::TimeDuration();
1334 mTimeoutAccumulated = false;
1335 CycleCollectedJSContext::BeforeProcessTask(aMightBlock);
1336 }
1337
AfterProcessTask(uint32_t aNewRecursionDepth)1338 void XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth) {
1339 // Now that we're back to the event loop, reset the slow script checkpoint.
1340 mSlowScriptCheckpoint = mozilla::TimeStamp();
1341 mSlowScriptSecondHalf = false;
1342
1343 // Call cycle collector occasionally.
1344 MOZ_ASSERT(NS_IsMainThread());
1345 nsJSContext::MaybePokeCC();
1346 CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth);
1347
1348 // This exception might have been set if we called an XPCWrappedJS that threw,
1349 // but now we're returning to the event loop, so nothing is going to look at
1350 // this value again. Clear it to prevent leaks.
1351 SetPendingException(nullptr);
1352 }
1353
IsSystemCaller() const1354 bool XPCJSContext::IsSystemCaller() const {
1355 return nsContentUtils::IsSystemCaller(Context());
1356 }
1357