Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 : /* vim: set ts=8 sts=4 et sw=4 tw=99: */
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 "nsAutoPtr.h"
19 : #include "nsNetUtil.h"
20 : #include "nsThreadUtils.h"
21 :
22 : #include "nsIMemoryInfoDumper.h"
23 : #include "nsIMemoryReporter.h"
24 : #include "nsIObserverService.h"
25 : #include "nsIDebug2.h"
26 : #include "nsIDocShell.h"
27 : #include "nsIRunnable.h"
28 : #include "amIAddonManager.h"
29 : #include "nsPIDOMWindow.h"
30 : #include "nsPrintfCString.h"
31 : #include "mozilla/Preferences.h"
32 : #include "mozilla/Telemetry.h"
33 : #include "mozilla/Services.h"
34 : #include "mozilla/dom/ScriptSettings.h"
35 :
36 : #include "nsContentUtils.h"
37 : #include "nsCCUncollectableMarker.h"
38 : #include "nsCycleCollectionNoteRootCallback.h"
39 : #include "nsCycleCollector.h"
40 : #include "jsapi.h"
41 : #include "jsprf.h"
42 : #include "js/MemoryMetrics.h"
43 : #include "mozilla/dom/GeneratedAtomList.h"
44 : #include "mozilla/dom/BindingUtils.h"
45 : #include "mozilla/dom/Element.h"
46 : #include "mozilla/dom/ScriptLoader.h"
47 : #include "mozilla/dom/WindowBinding.h"
48 : #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
49 : #include "mozilla/Atomics.h"
50 : #include "mozilla/Attributes.h"
51 : #include "mozilla/ProcessHangMonitor.h"
52 : #include "mozilla/Sprintf.h"
53 : #include "mozilla/ThreadLocal.h"
54 : #include "mozilla/UniquePtrExtensions.h"
55 : #include "mozilla/Unused.h"
56 : #include "AccessCheck.h"
57 : #include "nsGlobalWindow.h"
58 : #include "nsAboutProtocolUtils.h"
59 :
60 : #include "GeckoProfiler.h"
61 : #include "nsIInputStream.h"
62 : #include "nsIXULRuntime.h"
63 : #include "nsJSPrincipals.h"
64 :
65 : #ifdef MOZ_CRASHREPORTER
66 : #include "nsExceptionHandler.h"
67 : #endif
68 :
69 : #ifdef XP_WIN
70 : #include <windows.h>
71 : #endif
72 :
73 : static MOZ_THREAD_LOCAL(XPCJSContext*) gTlsContext;
74 :
75 : using namespace mozilla;
76 : using namespace xpc;
77 : using namespace JS;
78 : using mozilla::dom::PerThreadAtomCache;
79 : using mozilla::dom::AutoEntryScript;
80 :
81 : static void WatchdogMain(void* arg);
82 : class Watchdog;
83 : class WatchdogManager;
84 : class AutoLockWatchdog {
85 : Watchdog* const mWatchdog;
86 : public:
87 : explicit AutoLockWatchdog(Watchdog* aWatchdog);
88 : ~AutoLockWatchdog();
89 : };
90 :
91 : class Watchdog
92 : {
93 : public:
94 3 : explicit Watchdog(WatchdogManager* aManager)
95 3 : : mManager(aManager)
96 : , mLock(nullptr)
97 : , mWakeup(nullptr)
98 : , mThread(nullptr)
99 : , mHibernating(false)
100 : , mInitialized(false)
101 : , mShuttingDown(false)
102 3 : , mMinScriptRunTimeSeconds(1)
103 3 : {}
104 0 : ~Watchdog() { MOZ_ASSERT(!Initialized()); }
105 :
106 3 : WatchdogManager* Manager() { return mManager; }
107 3 : bool Initialized() { return mInitialized; }
108 35 : bool ShuttingDown() { return mShuttingDown; }
109 6859 : PRLock* GetLock() { return mLock; }
110 1713 : bool Hibernating() { return mHibernating; }
111 0 : void WakeUp()
112 : {
113 0 : MOZ_ASSERT(Initialized());
114 0 : MOZ_ASSERT(Hibernating());
115 0 : mHibernating = false;
116 0 : PR_NotifyCondVar(mWakeup);
117 0 : }
118 :
119 : //
120 : // Invoked by the main thread only.
121 : //
122 :
123 3 : void Init()
124 : {
125 3 : MOZ_ASSERT(NS_IsMainThread());
126 3 : mLock = PR_NewLock();
127 3 : if (!mLock)
128 0 : NS_RUNTIMEABORT("PR_NewLock failed.");
129 3 : mWakeup = PR_NewCondVar(mLock);
130 3 : if (!mWakeup)
131 0 : NS_RUNTIMEABORT("PR_NewCondVar failed.");
132 :
133 : {
134 6 : AutoLockWatchdog lock(this);
135 :
136 : // Gecko uses thread private for accounting and has to clean up at thread exit.
137 : // Therefore, even though we don't have a return value from the watchdog, we need to
138 : // join it on shutdown.
139 3 : mThread = PR_CreateThread(PR_USER_THREAD, WatchdogMain, this,
140 : PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD,
141 : PR_JOINABLE_THREAD, 0);
142 3 : if (!mThread)
143 0 : NS_RUNTIMEABORT("PR_CreateThread failed!");
144 :
145 : // WatchdogMain acquires the lock and then asserts mInitialized. So
146 : // make sure to set mInitialized before releasing the lock here so
147 : // that it's atomic with the creation of the thread.
148 3 : mInitialized = true;
149 : }
150 3 : }
151 :
152 0 : void Shutdown()
153 : {
154 0 : MOZ_ASSERT(NS_IsMainThread());
155 0 : MOZ_ASSERT(Initialized());
156 : { // Scoped lock.
157 0 : AutoLockWatchdog lock(this);
158 :
159 : // Signal to the watchdog thread that it's time to shut down.
160 0 : mShuttingDown = true;
161 :
162 : // Wake up the watchdog, and wait for it to call us back.
163 0 : PR_NotifyCondVar(mWakeup);
164 : }
165 :
166 0 : PR_JoinThread(mThread);
167 :
168 : // The thread sets mShuttingDown to false as it exits.
169 0 : MOZ_ASSERT(!mShuttingDown);
170 :
171 : // Destroy state.
172 0 : mThread = nullptr;
173 0 : PR_DestroyCondVar(mWakeup);
174 0 : mWakeup = nullptr;
175 0 : PR_DestroyLock(mLock);
176 0 : mLock = nullptr;
177 :
178 : // All done.
179 0 : mInitialized = false;
180 0 : }
181 :
182 13 : void SetMinScriptRunTimeSeconds(int32_t seconds)
183 : {
184 : // This variable is atomic, and is set from the main thread without
185 : // locking.
186 13 : MOZ_ASSERT(seconds > 0);
187 13 : mMinScriptRunTimeSeconds = seconds;
188 13 : }
189 :
190 : //
191 : // Invoked by the watchdog thread only.
192 : //
193 :
194 0 : void Hibernate()
195 : {
196 0 : MOZ_ASSERT(!NS_IsMainThread());
197 0 : mHibernating = true;
198 0 : Sleep(PR_INTERVAL_NO_TIMEOUT);
199 0 : }
200 32 : void Sleep(PRIntervalTime timeout)
201 : {
202 32 : MOZ_ASSERT(!NS_IsMainThread());
203 32 : MOZ_ALWAYS_TRUE(PR_WaitCondVar(mWakeup, timeout) == PR_SUCCESS);
204 29 : }
205 0 : void Finished()
206 : {
207 0 : MOZ_ASSERT(!NS_IsMainThread());
208 0 : mShuttingDown = false;
209 0 : }
210 :
211 29 : int32_t MinScriptRunTimeSeconds()
212 : {
213 29 : return mMinScriptRunTimeSeconds;
214 : }
215 :
216 : private:
217 : WatchdogManager* mManager;
218 :
219 : PRLock* mLock;
220 : PRCondVar* mWakeup;
221 : PRThread* mThread;
222 : bool mHibernating;
223 : bool mInitialized;
224 : bool mShuttingDown;
225 : mozilla::Atomic<int32_t> mMinScriptRunTimeSeconds;
226 : };
227 :
228 : #define PREF_MAX_SCRIPT_RUN_TIME_CONTENT "dom.max_script_run_time"
229 : #define PREF_MAX_SCRIPT_RUN_TIME_CHROME "dom.max_chrome_script_run_time"
230 :
231 : class WatchdogManager : public nsIObserver
232 : {
233 : public:
234 :
235 : NS_DECL_ISUPPORTS
236 3 : explicit WatchdogManager(XPCJSContext* aContext) : mContext(aContext)
237 3 : , mContextState(CONTEXT_INACTIVE)
238 : {
239 : // All the timestamps start at zero except for context state change.
240 3 : PodArrayZero(mTimestamps);
241 3 : mTimestamps[TimestampContextStateChange] = PR_Now();
242 :
243 : // Enable the watchdog, if appropriate.
244 3 : RefreshWatchdog();
245 :
246 : // Register ourselves as an observer to get updates on the pref.
247 3 : mozilla::Preferences::AddStrongObserver(this, "dom.use_watchdog");
248 3 : mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
249 3 : mozilla::Preferences::AddStrongObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
250 3 : }
251 :
252 : protected:
253 :
254 0 : virtual ~WatchdogManager()
255 0 : {
256 : // Shutting down the watchdog requires context-switching to the watchdog
257 : // thread, which isn't great to do in a destructor. So we require
258 : // consumers to shut it down manually before releasing it.
259 0 : MOZ_ASSERT(!mWatchdog);
260 0 : }
261 :
262 : public:
263 :
264 0 : void Shutdown()
265 : {
266 0 : mozilla::Preferences::RemoveObserver(this, "dom.use_watchdog");
267 0 : mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CONTENT);
268 0 : mozilla::Preferences::RemoveObserver(this, PREF_MAX_SCRIPT_RUN_TIME_CHROME);
269 0 : }
270 :
271 10 : NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic,
272 : const char16_t* aData) override
273 : {
274 10 : RefreshWatchdog();
275 10 : return NS_OK;
276 : }
277 :
278 : // Context statistics. These live on the watchdog manager, are written
279 : // from the main thread, and are read from the watchdog thread (holding
280 : // the lock in each case).
281 : void
282 3425 : RecordContextActivity(bool active)
283 : {
284 : // The watchdog reads this state, so acquire the lock before writing it.
285 3425 : MOZ_ASSERT(NS_IsMainThread());
286 6850 : Maybe<AutoLockWatchdog> lock;
287 3425 : if (mWatchdog)
288 3425 : lock.emplace(mWatchdog);
289 :
290 : // Write state.
291 3425 : mTimestamps[TimestampContextStateChange] = PR_Now();
292 3425 : mContextState = active ? CONTEXT_ACTIVE : CONTEXT_INACTIVE;
293 :
294 : // The watchdog may be hibernating, waiting for the context to go
295 : // active. Wake it up if necessary.
296 3425 : if (active && mWatchdog && mWatchdog->Hibernating())
297 0 : mWatchdog->WakeUp();
298 3425 : }
299 61 : bool IsContextActive() { return mContextState == CONTEXT_ACTIVE; }
300 32 : PRTime TimeSinceLastContextStateChange()
301 : {
302 32 : return PR_Now() - GetTimestamp(TimestampContextStateChange);
303 : }
304 :
305 : // Note - Because of the context activity timestamp, these are read and
306 : // written from both threads.
307 29 : void RecordTimestamp(WatchdogTimestampCategory aCategory)
308 : {
309 : // The watchdog thread always holds the lock when it runs.
310 58 : Maybe<AutoLockWatchdog> maybeLock;
311 29 : if (NS_IsMainThread() && mWatchdog)
312 0 : maybeLock.emplace(mWatchdog);
313 29 : mTimestamps[aCategory] = PR_Now();
314 29 : }
315 32 : PRTime GetTimestamp(WatchdogTimestampCategory aCategory)
316 : {
317 : // The watchdog thread always holds the lock when it runs.
318 64 : Maybe<AutoLockWatchdog> maybeLock;
319 32 : if (NS_IsMainThread() && mWatchdog)
320 0 : maybeLock.emplace(mWatchdog);
321 64 : return mTimestamps[aCategory];
322 : }
323 :
324 0 : XPCJSContext* Context() { return mContext; }
325 0 : Watchdog* GetWatchdog() { return mWatchdog; }
326 :
327 13 : void RefreshWatchdog()
328 : {
329 13 : bool wantWatchdog = Preferences::GetBool("dom.use_watchdog", true);
330 13 : if (wantWatchdog != !!mWatchdog) {
331 3 : if (wantWatchdog)
332 3 : StartWatchdog();
333 : else
334 0 : StopWatchdog();
335 : }
336 :
337 13 : if (mWatchdog) {
338 13 : int32_t contentTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CONTENT, 10);
339 13 : if (contentTime <= 0)
340 9 : contentTime = INT32_MAX;
341 13 : int32_t chromeTime = Preferences::GetInt(PREF_MAX_SCRIPT_RUN_TIME_CHROME, 20);
342 13 : if (chromeTime <= 0)
343 10 : chromeTime = INT32_MAX;
344 13 : mWatchdog->SetMinScriptRunTimeSeconds(std::min(contentTime, chromeTime));
345 : }
346 13 : }
347 :
348 3 : void StartWatchdog()
349 : {
350 3 : MOZ_ASSERT(!mWatchdog);
351 3 : mWatchdog = new Watchdog(this);
352 3 : mWatchdog->Init();
353 3 : }
354 :
355 0 : void StopWatchdog()
356 : {
357 0 : MOZ_ASSERT(mWatchdog);
358 0 : mWatchdog->Shutdown();
359 0 : mWatchdog = nullptr;
360 0 : }
361 :
362 : private:
363 : XPCJSContext* mContext;
364 : nsAutoPtr<Watchdog> mWatchdog;
365 :
366 : enum { CONTEXT_ACTIVE, CONTEXT_INACTIVE } mContextState;
367 : PRTime mTimestamps[TimestampCount];
368 : };
369 :
370 125 : NS_IMPL_ISUPPORTS(WatchdogManager, nsIObserver)
371 :
372 3431 : AutoLockWatchdog::AutoLockWatchdog(Watchdog* aWatchdog) : mWatchdog(aWatchdog)
373 : {
374 3431 : PR_Lock(mWatchdog->GetLock());
375 3431 : }
376 :
377 6856 : AutoLockWatchdog::~AutoLockWatchdog()
378 : {
379 3428 : PR_Unlock(mWatchdog->GetLock());
380 3428 : }
381 :
382 : static void
383 3 : WatchdogMain(void* arg)
384 : {
385 3 : mozilla::AutoProfilerRegisterThread registerThread("JS Watchdog");
386 3 : NS_SetCurrentThreadName("JS Watchdog");
387 :
388 3 : Watchdog* self = static_cast<Watchdog*>(arg);
389 3 : WatchdogManager* manager = self->Manager();
390 :
391 : // Lock lasts until we return
392 3 : AutoLockWatchdog lock(self);
393 :
394 3 : MOZ_ASSERT(self->Initialized());
395 3 : MOZ_ASSERT(!self->ShuttingDown());
396 61 : while (!self->ShuttingDown()) {
397 : // Sleep only 1 second if recently (or currently) active; otherwise, hibernate
398 43 : if (manager->IsContextActive() ||
399 11 : manager->TimeSinceLastContextStateChange() <= PRTime(2*PR_USEC_PER_SEC))
400 : {
401 32 : self->Sleep(PR_TicksPerSecond());
402 : } else {
403 0 : manager->RecordTimestamp(TimestampWatchdogHibernateStart);
404 0 : self->Hibernate();
405 0 : manager->RecordTimestamp(TimestampWatchdogHibernateStop);
406 : }
407 :
408 : // Rise and shine.
409 29 : manager->RecordTimestamp(TimestampWatchdogWakeup);
410 :
411 : // Don't request an interrupt callback unless the current script has
412 : // been running long enough that we might show the slow script dialog.
413 : // Triggering the callback from off the main thread can be expensive.
414 :
415 : // We want to avoid showing the slow script dialog if the user's laptop
416 : // goes to sleep in the middle of running a script. To ensure this, we
417 : // invoke the interrupt callback after only half the timeout has
418 : // elapsed. The callback simply records the fact that it was called in
419 : // the mSlowScriptSecondHalf flag. Then we wait another (timeout/2)
420 : // seconds and invoke the callback again. This time around it sees
421 : // mSlowScriptSecondHalf is set and so it shows the slow script
422 : // dialog. If the computer is put to sleep during one of the (timeout/2)
423 : // periods, the script still has the other (timeout/2) seconds to
424 : // finish.
425 29 : PRTime usecs = self->MinScriptRunTimeSeconds() * PR_USEC_PER_SEC / 2;
426 50 : if (manager->IsContextActive() &&
427 21 : manager->TimeSinceLastContextStateChange() >= usecs)
428 : {
429 0 : bool debuggerAttached = false;
430 0 : nsCOMPtr<nsIDebug2> dbg = do_GetService("@mozilla.org/xpcom/debug;1");
431 0 : if (dbg)
432 0 : dbg->GetIsDebuggerAttached(&debuggerAttached);
433 0 : if (!debuggerAttached)
434 0 : JS_RequestInterruptCallback(manager->Context()->Context());
435 : }
436 : }
437 :
438 : // Tell the manager that we've shut down.
439 0 : self->Finished();
440 0 : }
441 :
442 : PRTime
443 0 : XPCJSContext::GetWatchdogTimestamp(WatchdogTimestampCategory aCategory)
444 : {
445 0 : return mWatchdogManager->GetTimestamp(aCategory);
446 : }
447 :
448 : void
449 0 : xpc::SimulateActivityCallback(bool aActive)
450 : {
451 0 : XPCJSContext::ActivityCallback(XPCJSContext::Get(), aActive);
452 0 : }
453 :
454 : // static
455 : void
456 3425 : XPCJSContext::ActivityCallback(void* arg, bool active)
457 : {
458 3425 : if (!active) {
459 1712 : ProcessHangMonitor::ClearHang();
460 : }
461 :
462 3425 : XPCJSContext* self = static_cast<XPCJSContext*>(arg);
463 3425 : self->mWatchdogManager->RecordContextActivity(active);
464 3425 : }
465 :
466 : // static
467 : bool
468 27 : XPCJSContext::InterruptCallback(JSContext* cx)
469 : {
470 27 : XPCJSContext* self = XPCJSContext::Get();
471 :
472 : // Now is a good time to turn on profiling if it's pending.
473 27 : profiler_js_interrupt_callback();
474 :
475 : // Normally we record mSlowScriptCheckpoint when we start to process an
476 : // event. However, we can run JS outside of event handlers. This code takes
477 : // care of that case.
478 27 : if (self->mSlowScriptCheckpoint.IsNull()) {
479 4 : self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
480 4 : self->mSlowScriptSecondHalf = false;
481 4 : self->mSlowScriptActualWait = mozilla::TimeDuration();
482 4 : self->mTimeoutAccumulated = false;
483 4 : return true;
484 : }
485 :
486 : // Sometimes we get called back during XPConnect initialization, before Gecko
487 : // has finished bootstrapping. Avoid crashing in nsContentUtils below.
488 23 : if (!nsContentUtils::IsInitialized())
489 0 : return true;
490 :
491 : // This is at least the second interrupt callback we've received since
492 : // returning to the event loop. See how long it's been, and what the limit
493 : // is.
494 23 : TimeDuration duration = TimeStamp::NowLoRes() - self->mSlowScriptCheckpoint;
495 23 : bool chrome = nsContentUtils::IsSystemCaller(cx);
496 23 : const char* prefName = chrome ? PREF_MAX_SCRIPT_RUN_TIME_CHROME
497 23 : : PREF_MAX_SCRIPT_RUN_TIME_CONTENT;
498 23 : int32_t limit = Preferences::GetInt(prefName, chrome ? 20 : 10);
499 :
500 : // If there's no limit, or we're within the limit, let it go.
501 23 : if (limit == 0 || duration.ToSeconds() < limit / 2.0)
502 23 : return true;
503 :
504 0 : self->mSlowScriptActualWait += duration;
505 :
506 : // In order to guard against time changes or laptops going to sleep, we
507 : // don't trigger the slow script warning until (limit/2) seconds have
508 : // elapsed twice.
509 0 : if (!self->mSlowScriptSecondHalf) {
510 0 : self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
511 0 : self->mSlowScriptSecondHalf = true;
512 0 : return true;
513 : }
514 :
515 : //
516 : // This has gone on long enough! Time to take action. ;-)
517 : //
518 :
519 : // Get the DOM window associated with the running script. If the script is
520 : // running in a non-DOM scope, we have to just let it keep running.
521 0 : RootedObject global(cx, JS::CurrentGlobalOrNull(cx));
522 0 : RefPtr<nsGlobalWindow> win = WindowOrNull(global);
523 0 : if (!win && IsSandbox(global)) {
524 : // If this is a sandbox associated with a DOMWindow via a
525 : // sandboxPrototype, use that DOMWindow. This supports GreaseMonkey
526 : // and JetPack content scripts.
527 0 : JS::Rooted<JSObject*> proto(cx);
528 0 : if (!JS_GetPrototype(cx, global, &proto))
529 0 : return false;
530 0 : if (proto && IsSandboxPrototypeProxy(proto) &&
531 0 : (proto = js::CheckedUnwrap(proto, /* stopAtWindowProxy = */ false)))
532 : {
533 0 : win = WindowGlobalOrNull(proto);
534 : }
535 : }
536 :
537 0 : if (!win) {
538 0 : NS_WARNING("No active window");
539 0 : return true;
540 : }
541 :
542 0 : if (win->IsDying()) {
543 : // The window is being torn down. When that happens we try to prevent
544 : // the dispatch of new runnables, so it also makes sense to kill any
545 : // long-running script. The user is primarily interested in this page
546 : // going away.
547 0 : return false;
548 : }
549 :
550 0 : if (win->GetIsPrerendered()) {
551 : // We cannot display a dialog if the page is being prerendered, so
552 : // just kill the page.
553 0 : mozilla::dom::HandlePrerenderingViolation(win->AsInner());
554 0 : return false;
555 : }
556 :
557 : // Accumulate slow script invokation delay.
558 0 : if (!chrome && !self->mTimeoutAccumulated) {
559 0 : uint32_t delay = uint32_t(self->mSlowScriptActualWait.ToMilliseconds() - (limit * 1000.0));
560 0 : Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTIFY_DELAY, delay);
561 0 : self->mTimeoutAccumulated = true;
562 : }
563 :
564 : // Show the prompt to the user, and kill if requested.
565 0 : nsGlobalWindow::SlowScriptResponse response = win->ShowSlowScriptDialog();
566 0 : if (response == nsGlobalWindow::KillSlowScript) {
567 0 : if (Preferences::GetBool("dom.global_stop_script", true))
568 0 : xpc::Scriptability::Get(global).Block();
569 0 : return false;
570 : }
571 :
572 : // The user chose to continue the script. Reset the timer, and disable this
573 : // machinery with a pref of the user opted out of future slow-script dialogs.
574 0 : if (response != nsGlobalWindow::ContinueSlowScriptAndKeepNotifying)
575 0 : self->mSlowScriptCheckpoint = TimeStamp::NowLoRes();
576 :
577 0 : if (response == nsGlobalWindow::AlwaysContinueSlowScript)
578 0 : Preferences::SetInt(prefName, 0);
579 :
580 0 : return true;
581 : }
582 :
583 : #define JS_OPTIONS_DOT_STR "javascript.options."
584 :
585 : static mozilla::Atomic<bool> sDiscardSystemSource(false);
586 :
587 : bool
588 271 : xpc::ShouldDiscardSystemSource() { return sDiscardSystemSource; }
589 :
590 : #ifdef DEBUG
591 : static mozilla::Atomic<bool> sExtraWarningsForSystemJS(false);
592 268 : bool xpc::ExtraWarningsForSystemJS() { return sExtraWarningsForSystemJS; }
593 : #else
594 : bool xpc::ExtraWarningsForSystemJS() { return false; }
595 : #endif
596 :
597 : static mozilla::Atomic<bool> sSharedMemoryEnabled(false);
598 :
599 : bool
600 554 : xpc::SharedMemoryEnabled() { return sSharedMemoryEnabled; }
601 :
602 : static void
603 3 : ReloadPrefsCallback(const char* pref, void* data)
604 : {
605 3 : XPCJSContext* xpccx = static_cast<XPCJSContext*>(data);
606 3 : JSContext* cx = xpccx->Context();
607 :
608 3 : bool safeMode = false;
609 6 : nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
610 3 : if (xr) {
611 3 : xr->GetInSafeMode(&safeMode);
612 : }
613 :
614 3 : bool useBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "baselinejit") && !safeMode;
615 3 : bool useIon = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion") && !safeMode;
616 3 : bool useAsmJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "asmjs") && !safeMode;
617 3 : bool useWasm = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm") && !safeMode;
618 3 : bool useWasmBaseline = Preferences::GetBool(JS_OPTIONS_DOT_STR "wasm_baselinejit") && !safeMode;
619 : bool throwOnAsmJSValidationFailure = Preferences::GetBool(JS_OPTIONS_DOT_STR
620 3 : "throw_on_asmjs_validation_failure");
621 3 : bool useNativeRegExp = Preferences::GetBool(JS_OPTIONS_DOT_STR "native_regexp") && !safeMode;
622 :
623 3 : bool parallelParsing = Preferences::GetBool(JS_OPTIONS_DOT_STR "parallel_parsing");
624 : bool offthreadIonCompilation = Preferences::GetBool(JS_OPTIONS_DOT_STR
625 3 : "ion.offthread_compilation");
626 : bool useBaselineEager = Preferences::GetBool(JS_OPTIONS_DOT_STR
627 3 : "baselinejit.unsafe_eager_compilation");
628 3 : bool useIonEager = Preferences::GetBool(JS_OPTIONS_DOT_STR "ion.unsafe_eager_compilation");
629 : #ifdef DEBUG
630 3 : bool fullJitDebugChecks = Preferences::GetBool(JS_OPTIONS_DOT_STR "jit.full_debug_checks");
631 : #endif
632 :
633 3 : int32_t baselineThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR "baselinejit.threshold", -1);
634 3 : int32_t ionThreshold = Preferences::GetInt(JS_OPTIONS_DOT_STR "ion.threshold", -1);
635 :
636 3 : sDiscardSystemSource = Preferences::GetBool(JS_OPTIONS_DOT_STR "discardSystemSource");
637 :
638 3 : bool useAsyncStack = Preferences::GetBool(JS_OPTIONS_DOT_STR "asyncstack");
639 :
640 : bool throwOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
641 3 : "throw_on_debuggee_would_run");
642 :
643 : bool dumpStackOnDebuggeeWouldRun = Preferences::GetBool(JS_OPTIONS_DOT_STR
644 3 : "dump_stack_on_debuggee_would_run");
645 :
646 3 : bool werror = Preferences::GetBool(JS_OPTIONS_DOT_STR "werror");
647 :
648 3 : bool extraWarnings = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict");
649 :
650 3 : sSharedMemoryEnabled = Preferences::GetBool(JS_OPTIONS_DOT_STR "shared_memory");
651 :
652 : #ifdef DEBUG
653 3 : sExtraWarningsForSystemJS = Preferences::GetBool(JS_OPTIONS_DOT_STR "strict.debug");
654 : #endif
655 :
656 : #ifdef JS_GC_ZEAL
657 3 : int32_t zeal = Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal", -1);
658 : int32_t zeal_frequency =
659 : Preferences::GetInt(JS_OPTIONS_DOT_STR "gczeal.frequency",
660 3 : JS_DEFAULT_ZEAL_FREQ);
661 3 : if (zeal >= 0) {
662 0 : JS_SetGCZeal(cx, (uint8_t)zeal, zeal_frequency);
663 : }
664 : #endif // JS_GC_ZEAL
665 :
666 : #ifdef FUZZING
667 : bool fuzzingEnabled = Preferences::GetBool("fuzzing.enabled");
668 : #endif
669 :
670 3 : JS::ContextOptionsRef(cx).setBaseline(useBaseline)
671 6 : .setIon(useIon)
672 6 : .setAsmJS(useAsmJS)
673 6 : .setWasm(useWasm)
674 6 : .setWasmAlwaysBaseline(useWasmBaseline)
675 6 : .setThrowOnAsmJSValidationFailure(throwOnAsmJSValidationFailure)
676 6 : .setNativeRegExp(useNativeRegExp)
677 6 : .setAsyncStack(useAsyncStack)
678 6 : .setThrowOnDebuggeeWouldRun(throwOnDebuggeeWouldRun)
679 6 : .setDumpStackOnDebuggeeWouldRun(dumpStackOnDebuggeeWouldRun)
680 6 : .setWerror(werror)
681 : #ifdef FUZZING
682 : .setFuzzing(fuzzingEnabled)
683 : #endif
684 6 : .setExtraWarnings(extraWarnings);
685 :
686 3 : JS_SetParallelParsingEnabled(cx, parallelParsing);
687 3 : JS_SetOffthreadIonCompilationEnabled(cx, offthreadIonCompilation);
688 3 : JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_BASELINE_WARMUP_TRIGGER,
689 3 : useBaselineEager ? 0 : baselineThreshold);
690 3 : JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_ION_WARMUP_TRIGGER,
691 3 : useIonEager ? 0 : ionThreshold);
692 : #ifdef DEBUG
693 3 : JS_SetGlobalJitCompilerOption(cx, JSJITCOMPILER_FULL_DEBUG_CHECKS, fullJitDebugChecks);
694 : #endif
695 3 : }
696 :
697 0 : XPCJSContext::~XPCJSContext()
698 : {
699 0 : MOZ_COUNT_DTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
700 : // Elsewhere we abort immediately if XPCJSContext initialization fails.
701 : // Therefore the context must be non-null.
702 0 : MOZ_ASSERT(MaybeContext());
703 :
704 : Preferences::UnregisterPrefixCallback(ReloadPrefsCallback,
705 : JS_OPTIONS_DOT_STR,
706 0 : this);
707 :
708 : #ifdef FUZZING
709 : Preferences::UnregisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
710 : #endif
711 :
712 0 : js::SetActivityCallback(Context(), nullptr, nullptr);
713 :
714 : // Clear any pending exception. It might be an XPCWrappedJS, and if we try
715 : // to destroy it later we will crash.
716 0 : SetPendingException(nullptr);
717 :
718 0 : xpc_DelocalizeContext(Context());
719 :
720 0 : if (mWatchdogManager->GetWatchdog())
721 0 : mWatchdogManager->StopWatchdog();
722 0 : mWatchdogManager->Shutdown();
723 :
724 0 : if (mCallContext)
725 0 : mCallContext->SystemIsBeingShutDown();
726 :
727 0 : auto rtPrivate = static_cast<PerThreadAtomCache*>(JS_GetContextPrivate(Context()));
728 : delete rtPrivate;
729 0 : JS_SetContextPrivate(Context(), nullptr);
730 :
731 0 : profiler_clear_js_context();
732 :
733 0 : gTlsContext.set(nullptr);
734 0 : }
735 :
736 3 : XPCJSContext::XPCJSContext()
737 : : mCallContext(nullptr),
738 : mAutoRoots(nullptr),
739 : mResolveName(JSID_VOID),
740 : mResolvingWrapper(nullptr),
741 3 : mWatchdogManager(new WatchdogManager(this)),
742 : mSlowScriptSecondHalf(false),
743 : mTimeoutAccumulated(false),
744 6 : mPendingResult(NS_OK)
745 : {
746 3 : MOZ_COUNT_CTOR_INHERITED(XPCJSContext, CycleCollectedJSContext);
747 3 : MOZ_RELEASE_ASSERT(!gTlsContext.get());
748 3 : gTlsContext.set(this);
749 3 : }
750 :
751 : /* static */ XPCJSContext*
752 75163 : XPCJSContext::Get()
753 : {
754 75163 : return gTlsContext.get();
755 : }
756 :
757 : #ifdef XP_WIN
758 : static size_t
759 : GetWindowsStackSize()
760 : {
761 : // First, get the stack base. Because the stack grows down, this is the top
762 : // of the stack.
763 : const uint8_t* stackTop;
764 : #ifdef _WIN64
765 : PNT_TIB64 pTib = reinterpret_cast<PNT_TIB64>(NtCurrentTeb());
766 : stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
767 : #else
768 : PNT_TIB pTib = reinterpret_cast<PNT_TIB>(NtCurrentTeb());
769 : stackTop = reinterpret_cast<const uint8_t*>(pTib->StackBase);
770 : #endif
771 :
772 : // Now determine the stack bottom. Note that we can't use tib->StackLimit,
773 : // because that's the size of the committed area and we're also interested
774 : // in the reserved pages below that.
775 : MEMORY_BASIC_INFORMATION mbi;
776 : if (!VirtualQuery(&mbi, &mbi, sizeof(mbi)))
777 : MOZ_CRASH("VirtualQuery failed");
778 :
779 : const uint8_t* stackBottom = reinterpret_cast<const uint8_t*>(mbi.AllocationBase);
780 :
781 : // Do some sanity checks.
782 : size_t stackSize = size_t(stackTop - stackBottom);
783 : MOZ_RELEASE_ASSERT(stackSize >= 1 * 1024 * 1024);
784 : MOZ_RELEASE_ASSERT(stackSize <= 32 * 1024 * 1024);
785 :
786 : // Subtract 40 KB (Win32) or 80 KB (Win64) to account for things like
787 : // the guard page and large PGO stack frames.
788 : return stackSize - 10 * sizeof(uintptr_t) * 1024;
789 : }
790 : #endif
791 :
792 : XPCJSRuntime*
793 24262 : XPCJSContext::Runtime() const
794 : {
795 24262 : return static_cast<XPCJSRuntime*>(CycleCollectedJSContext::Runtime());
796 : }
797 :
798 : CycleCollectedJSRuntime*
799 3 : XPCJSContext::CreateRuntime(JSContext* aCx)
800 : {
801 3 : return new XPCJSRuntime(aCx);
802 : }
803 :
804 : nsresult
805 3 : XPCJSContext::Initialize(XPCJSContext* aPrimaryContext)
806 : {
807 : nsresult rv;
808 3 : if (aPrimaryContext) {
809 0 : rv = CycleCollectedJSContext::InitializeNonPrimary(aPrimaryContext);
810 : } else {
811 3 : rv = CycleCollectedJSContext::Initialize(nullptr,
812 : JS::DefaultHeapMaxBytes,
813 3 : JS::DefaultNurseryBytes);
814 : }
815 3 : if (NS_WARN_IF(NS_FAILED(rv))) {
816 0 : return rv;
817 : }
818 :
819 3 : MOZ_ASSERT(Context());
820 3 : JSContext* cx = Context();
821 :
822 3 : auto cxPrivate = new PerThreadAtomCache();
823 3 : memset(cxPrivate, 0, sizeof(PerThreadAtomCache));
824 3 : JS_SetContextPrivate(cx, cxPrivate);
825 :
826 : // The JS engine permits us to set different stack limits for system code,
827 : // trusted script, and untrusted script. We have tests that ensure that
828 : // we can always execute 10 "heavy" (eval+with) stack frames deeper in
829 : // privileged code. Our stack sizes vary greatly in different configurations,
830 : // so satisfying those tests requires some care. Manual measurements of the
831 : // number of heavy stack frames achievable gives us the following rough data,
832 : // ordered by the effective categories in which they are grouped in the
833 : // JS_SetNativeStackQuota call (which predates this analysis).
834 : //
835 : // (NB: These numbers may have drifted recently - see bug 938429)
836 : // OSX 64-bit Debug: 7MB stack, 636 stack frames => ~11.3k per stack frame
837 : // OSX64 Opt: 7MB stack, 2440 stack frames => ~3k per stack frame
838 : //
839 : // Linux 32-bit Debug: 2MB stack, 426 stack frames => ~4.8k per stack frame
840 : // Linux 64-bit Debug: 4MB stack, 455 stack frames => ~9.0k per stack frame
841 : //
842 : // Windows (Opt+Debug): 900K stack, 235 stack frames => ~3.4k per stack frame
843 : //
844 : // Linux 32-bit Opt: 1MB stack, 272 stack frames => ~3.8k per stack frame
845 : // Linux 64-bit Opt: 2MB stack, 316 stack frames => ~6.5k per stack frame
846 : //
847 : // We tune the trusted/untrusted quotas for each configuration to achieve our
848 : // invariants while attempting to minimize overhead. In contrast, our buffer
849 : // between system code and trusted script is a very unscientific 10k.
850 3 : const size_t kSystemCodeBuffer = 10 * 1024;
851 :
852 : // Our "default" stack is what we use in configurations where we don't have
853 : // a compelling reason to do things differently. This is effectively 512KB
854 : // on 32-bit platforms and 1MB on 64-bit platforms.
855 3 : const size_t kDefaultStackQuota = 128 * sizeof(size_t) * 1024;
856 :
857 : // Set stack sizes for different configurations. It's probably not great for
858 : // the web to base this decision primarily on the default stack size that the
859 : // underlying platform makes available, but that seems to be what we do. :-(
860 :
861 : #if defined(XP_MACOSX) || defined(DARWIN)
862 : // MacOS has a gargantuan default stack size of 8MB. Go wild with 7MB,
863 : // and give trusted script 180k extra. The stack is huge on mac anyway.
864 : const size_t kStackQuota = 7 * 1024 * 1024;
865 : const size_t kTrustedScriptBuffer = 180 * 1024;
866 : #elif defined(MOZ_ASAN)
867 : // ASan requires more stack space due to red-zones, so give it double the
868 : // default (1MB on 32-bit, 2MB on 64-bit). ASAN stack frame measurements
869 : // were not taken at the time of this writing, so we hazard a guess that
870 : // ASAN builds have roughly thrice the stack overhead as normal builds.
871 : // On normal builds, the largest stack frame size we might encounter is
872 : // 9.0k (see above), so let's use a buffer of 9.0 * 5 * 10 = 450k.
873 : const size_t kStackQuota = 2 * kDefaultStackQuota;
874 : const size_t kTrustedScriptBuffer = 450 * 1024;
875 : #elif defined(XP_WIN)
876 : // 1MB is the default stack size on Windows. We use the /STACK linker flag
877 : // to request a larger stack, so we determine the stack size at runtime.
878 : const size_t kStackQuota = GetWindowsStackSize();
879 : const size_t kTrustedScriptBuffer = (sizeof(size_t) == 8) ? 180 * 1024 //win64
880 : : 120 * 1024; //win32
881 : // The following two configurations are linux-only. Given the numbers above,
882 : // we use 50k and 100k trusted buffers on 32-bit and 64-bit respectively.
883 : #elif defined(ANDROID)
884 : // Android appears to have 1MB stacks. Allow the use of 3/4 of that size
885 : // (768KB on 32-bit), since otherwise we can crash with a stack overflow
886 : // when nearing the 1MB limit.
887 : const size_t kStackQuota = kDefaultStackQuota + kDefaultStackQuota / 2;
888 : const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
889 : #elif defined(DEBUG)
890 : // Bug 803182: account for the 4x difference in the size of js::Interpret
891 : // between optimized and debug builds.
892 : // XXXbholley - Then why do we only account for 2x of difference?
893 3 : const size_t kStackQuota = 2 * kDefaultStackQuota;
894 3 : const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
895 : #else
896 : const size_t kStackQuota = kDefaultStackQuota;
897 : const size_t kTrustedScriptBuffer = sizeof(size_t) * 12800;
898 : #endif
899 :
900 : // Avoid an unused variable warning on platforms where we don't use the
901 : // default.
902 : (void) kDefaultStackQuota;
903 :
904 : JS_SetNativeStackQuota(cx,
905 : kStackQuota,
906 : kStackQuota - kSystemCodeBuffer,
907 3 : kStackQuota - kSystemCodeBuffer - kTrustedScriptBuffer);
908 :
909 3 : profiler_set_js_context(cx);
910 :
911 3 : js::SetActivityCallback(cx, ActivityCallback, this);
912 3 : JS_AddInterruptCallback(cx, InterruptCallback);
913 :
914 : // Set up locale information and callbacks for the newly-created context so
915 : // that the various toLocaleString() methods, localeCompare(), and other
916 : // internationalization APIs work as desired.
917 3 : if (!xpc_LocalizeContext(cx))
918 0 : NS_RUNTIMEABORT("xpc_LocalizeContext failed.");
919 :
920 3 : if (!aPrimaryContext) {
921 3 : Runtime()->Initialize(cx);
922 : }
923 :
924 : // Watch for the JS boolean options.
925 3 : ReloadPrefsCallback(nullptr, this);
926 : Preferences::RegisterPrefixCallback(ReloadPrefsCallback,
927 : JS_OPTIONS_DOT_STR,
928 3 : this);
929 :
930 : #ifdef FUZZING
931 : Preferences::RegisterCallback(ReloadPrefsCallback, "fuzzing.enabled", this);
932 : #endif
933 :
934 3 : return NS_OK;
935 : }
936 :
937 : // static
938 : void
939 3 : XPCJSContext::InitTLS()
940 : {
941 3 : MOZ_RELEASE_ASSERT(gTlsContext.init());
942 3 : }
943 :
944 : // static
945 : XPCJSContext*
946 3 : XPCJSContext::NewXPCJSContext(XPCJSContext* aPrimaryContext)
947 : {
948 3 : XPCJSContext* self = new XPCJSContext();
949 3 : nsresult rv = self->Initialize(aPrimaryContext);
950 3 : if (NS_FAILED(rv)) {
951 0 : NS_RUNTIMEABORT("new XPCJSContext failed to initialize.");
952 0 : delete self;
953 0 : return nullptr;
954 : }
955 :
956 3 : if (self->Context())
957 3 : return self;
958 :
959 0 : NS_RUNTIMEABORT("new XPCJSContext failed to initialize.");
960 0 : return nullptr;
961 : }
962 :
963 : void
964 1230 : XPCJSContext::BeforeProcessTask(bool aMightBlock)
965 : {
966 1230 : MOZ_ASSERT(NS_IsMainThread());
967 :
968 : // If ProcessNextEvent was called during a Promise "then" callback, we
969 : // must process any pending microtasks before blocking in the event loop,
970 : // otherwise we may deadlock until an event enters the queue later.
971 1230 : if (aMightBlock) {
972 89 : if (Promise::PerformMicroTaskCheckpoint()) {
973 : // If any microtask was processed, we post a dummy event in order to
974 : // force the ProcessNextEvent call not to block. This is required
975 : // to support nested event loops implemented using a pattern like
976 : // "while (condition) thread.processNextEvent(true)", in case the
977 : // condition is triggered here by a Promise "then" callback.
978 :
979 0 : NS_DispatchToMainThread(new Runnable("Empty_microtask_runnable"));
980 : }
981 : }
982 :
983 : // Start the slow script timer.
984 1230 : mSlowScriptCheckpoint = mozilla::TimeStamp::NowLoRes();
985 1230 : mSlowScriptSecondHalf = false;
986 1230 : mSlowScriptActualWait = mozilla::TimeDuration();
987 1230 : mTimeoutAccumulated = false;
988 :
989 : // As we may be entering a nested event loop, we need to
990 : // cancel any ongoing performance measurement.
991 1230 : js::ResetPerformanceMonitoring(Context());
992 :
993 1230 : CycleCollectedJSContext::BeforeProcessTask(aMightBlock);
994 1230 : }
995 :
996 : void
997 1227 : XPCJSContext::AfterProcessTask(uint32_t aNewRecursionDepth)
998 : {
999 : // Now that we're back to the event loop, reset the slow script checkpoint.
1000 1227 : mSlowScriptCheckpoint = mozilla::TimeStamp();
1001 1227 : mSlowScriptSecondHalf = false;
1002 :
1003 : // Call cycle collector occasionally.
1004 1227 : MOZ_ASSERT(NS_IsMainThread());
1005 1227 : nsJSContext::MaybePokeCC();
1006 :
1007 1227 : CycleCollectedJSContext::AfterProcessTask(aNewRecursionDepth);
1008 :
1009 : // Now that we are certain that the event is complete,
1010 : // we can flush any ongoing performance measurement.
1011 1227 : js::FlushPerformanceMonitoring(Context());
1012 :
1013 1227 : mozilla::jsipc::AfterProcessTask();
1014 1227 : }
|