/work/obj-fuzz/dist/include/mozilla/ScriptPreloader.h
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */ |
2 | | /* This Source Code Form is subject to the terms of the Mozilla Public |
3 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
4 | | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
5 | | |
6 | | #ifndef ScriptPreloader_h |
7 | | #define ScriptPreloader_h |
8 | | |
9 | | #include "mozilla/CheckedInt.h" |
10 | | #include "mozilla/EnumSet.h" |
11 | | #include "mozilla/LinkedList.h" |
12 | | #include "mozilla/MemoryReporting.h" |
13 | | #include "mozilla/Maybe.h" |
14 | | #include "mozilla/MaybeOneOf.h" |
15 | | #include "mozilla/Monitor.h" |
16 | | #include "mozilla/Range.h" |
17 | | #include "mozilla/Vector.h" |
18 | | #include "mozilla/Result.h" |
19 | | #include "mozilla/loader/AutoMemMap.h" |
20 | | #include "nsClassHashtable.h" |
21 | | #include "nsIFile.h" |
22 | | #include "nsIMemoryReporter.h" |
23 | | #include "nsIObserver.h" |
24 | | #include "nsIThread.h" |
25 | | #include "nsITimer.h" |
26 | | |
27 | | #include "jsapi.h" |
28 | | #include "js/GCAnnotations.h" |
29 | | |
30 | | #include <prio.h> |
31 | | |
32 | | namespace mozilla { |
33 | | namespace dom { |
34 | | class ContentParent; |
35 | | } |
36 | | namespace ipc { |
37 | | class FileDescriptor; |
38 | | } |
39 | | namespace loader { |
40 | | class InputBuffer; |
41 | | class ScriptCacheChild; |
42 | | |
43 | | enum class ProcessType : uint8_t { |
44 | | Uninitialized, |
45 | | Parent, |
46 | | Web, |
47 | | Extension, |
48 | | Privileged, |
49 | | }; |
50 | | |
51 | | template <typename T> |
52 | | struct Matcher |
53 | | { |
54 | | virtual bool Matches(T) = 0; |
55 | | }; |
56 | | } |
57 | | |
58 | | using namespace mozilla::loader; |
59 | | |
60 | | class ScriptPreloader : public nsIObserver |
61 | | , public nsIMemoryReporter |
62 | | , public nsIRunnable |
63 | | { |
64 | | MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf) |
65 | | |
66 | | friend class mozilla::loader::ScriptCacheChild; |
67 | | |
68 | | public: |
69 | | NS_DECL_THREADSAFE_ISUPPORTS |
70 | | NS_DECL_NSIOBSERVER |
71 | | NS_DECL_NSIMEMORYREPORTER |
72 | | NS_DECL_NSIRUNNABLE |
73 | | |
74 | | static ScriptPreloader& GetSingleton(); |
75 | | static ScriptPreloader& GetChildSingleton(); |
76 | | |
77 | | static ProcessType GetChildProcessType(const nsAString& remoteType); |
78 | | |
79 | | // Retrieves the script with the given cache key from the script cache. |
80 | | // Returns null if the script is not cached. |
81 | | JSScript* GetCachedScript(JSContext* cx, const nsCString& name); |
82 | | |
83 | | // Notes the execution of a script with the given URL and cache key. |
84 | | // Depending on the stage of startup, the script may be serialized and |
85 | | // stored to the startup script cache. |
86 | | // |
87 | | // If isRunOnce is true, this script is expected to run only once per |
88 | | // process per browser session. A cached instance will not be kept alive |
89 | | // for repeated execution. |
90 | | void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script, |
91 | | bool isRunOnce = false); |
92 | | |
93 | | void NoteScript(const nsCString& url, const nsCString& cachePath, |
94 | | ProcessType processType, nsTArray<uint8_t>&& xdrData, |
95 | | TimeStamp loadTime); |
96 | | |
97 | | // Initializes the script cache from the startup script cache file. |
98 | | Result<Ok, nsresult> InitCache(const nsAString& = NS_LITERAL_STRING("scriptCache")); |
99 | | |
100 | | Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild); |
101 | | |
102 | | bool Active() |
103 | 5 | { |
104 | 5 | return mCacheInitialized && !mStartupFinished; |
105 | 5 | } |
106 | | |
107 | | private: |
108 | | Result<Ok, nsresult> InitCacheInternal(JS::HandleObject scope = nullptr); |
109 | | |
110 | | public: |
111 | | void Trace(JSTracer* trc); |
112 | | |
113 | | static ProcessType CurrentProcessType() |
114 | 5 | { |
115 | 5 | MOZ_ASSERT(sProcessType != ProcessType::Uninitialized); |
116 | 5 | return sProcessType; |
117 | 5 | } |
118 | | |
119 | | static void InitContentChild(dom::ContentParent& parent); |
120 | | |
121 | | protected: |
122 | 0 | virtual ~ScriptPreloader() = default; |
123 | | |
124 | | private: |
125 | | enum class ScriptStatus { |
126 | | Restored, |
127 | | Saved, |
128 | | }; |
129 | | |
130 | | // Represents a cached JS script, either initially read from the script |
131 | | // cache file, to be added to the next session's script cache file, or |
132 | | // both. |
133 | | // |
134 | | // A script which was read from the cache file may be in any of the |
135 | | // following states: |
136 | | // |
137 | | // - Read from the cache, and being compiled off thread. In this case, |
138 | | // mReadyToExecute is false, and mToken is null. |
139 | | // - Off-thread compilation has finished, but the script has not yet been |
140 | | // executed. In this case, mReadyToExecute is true, and mToken has a non-null |
141 | | // value. |
142 | | // - Read from the cache, but too small or needed to immediately to be |
143 | | // compiled off-thread. In this case, mReadyToExecute is true, and both mToken |
144 | | // and mScript are null. |
145 | | // - Fully decoded, and ready to be added to the next session's cache |
146 | | // file. In this case, mReadyToExecute is true, and mScript is non-null. |
147 | | // |
148 | | // A script to be added to the next session's cache file always has a |
149 | | // non-null mScript value. If it was read from the last session's cache |
150 | | // file, it also has a non-empty mXDRRange range, which will be stored in |
151 | | // the next session's cache file. If it was compiled in this session, its |
152 | | // mXDRRange will initially be empty, and its mXDRData buffer will be |
153 | | // populated just before it is written to the cache file. |
154 | | class CachedScript : public LinkedListElement<CachedScript> |
155 | | { |
156 | | public: |
157 | | CachedScript(CachedScript&&) = delete; |
158 | | |
159 | | CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath, |
160 | | JSScript* script) |
161 | | : mCache(cache) |
162 | | , mURL(url) |
163 | | , mCachePath(cachePath) |
164 | | , mScript(script) |
165 | | , mReadyToExecute(true) |
166 | | , mIsRunOnce(false) |
167 | 5 | {} |
168 | | |
169 | | inline CachedScript(ScriptPreloader& cache, InputBuffer& buf); |
170 | | |
171 | 0 | ~CachedScript() = default; |
172 | | |
173 | | ScriptStatus Status() const |
174 | 0 | { |
175 | 0 | return mProcessTypes.isEmpty() ? ScriptStatus::Restored : ScriptStatus::Saved; |
176 | 0 | } |
177 | | |
178 | | // For use with nsTArray::Sort. |
179 | | // |
180 | | // Orders scripts by script load time, so that scripts which are needed |
181 | | // earlier are stored earlier, and scripts needed at approximately the |
182 | | // same time are stored approximately contiguously. |
183 | | struct Comparator |
184 | | { |
185 | | bool Equals(const CachedScript* a, const CachedScript* b) const |
186 | 0 | { |
187 | 0 | return a->mLoadTime == b->mLoadTime; |
188 | 0 | } |
189 | | |
190 | | bool LessThan(const CachedScript* a, const CachedScript* b) const |
191 | 0 | { |
192 | 0 | return a->mLoadTime < b->mLoadTime; |
193 | 0 | } |
194 | | }; |
195 | | |
196 | | struct StatusMatcher final : public Matcher<CachedScript*> |
197 | | { |
198 | 0 | explicit StatusMatcher(ScriptStatus status) : mStatus(status) {} |
199 | | |
200 | | virtual bool Matches(CachedScript* script) override |
201 | 0 | { |
202 | 0 | return script->Status() == mStatus; |
203 | 0 | } |
204 | | |
205 | | const ScriptStatus mStatus; |
206 | | }; |
207 | | |
208 | | void FreeData() |
209 | 0 | { |
210 | 0 | // If the script data isn't mmapped, we need to release both it |
211 | 0 | // and the Range that points to it at the same time. |
212 | 0 | if (!mXDRData.empty()) { |
213 | 0 | mXDRRange.reset(); |
214 | 0 | mXDRData.destroy(); |
215 | 0 | } |
216 | 0 | } |
217 | | |
218 | | void UpdateLoadTime(const TimeStamp& loadTime) |
219 | 5 | { |
220 | 5 | if (mLoadTime.IsNull() || loadTime < mLoadTime) { |
221 | 5 | mLoadTime = loadTime; |
222 | 5 | } |
223 | 5 | } |
224 | | |
225 | | // Checks whether the cached JSScript for this entry will be needed |
226 | | // again and, if not, drops it and returns true. This is the case for |
227 | | // run-once scripts that do not still need to be encoded into the |
228 | | // cache. |
229 | | // |
230 | | // If this method returns false, callers may set mScript to a cached |
231 | | // JSScript instance for this entry. If it returns true, they should |
232 | | // not. |
233 | | bool MaybeDropScript() |
234 | 5 | { |
235 | 5 | if (mIsRunOnce && (HasRange() || !mCache.WillWriteScripts())) { |
236 | 0 | mScript = nullptr; |
237 | 0 | return true; |
238 | 0 | } |
239 | 5 | return false; |
240 | 5 | } |
241 | | |
242 | | // Encodes this script into XDR data, and stores the result in mXDRData. |
243 | | // Returns true on success, false on failure. |
244 | | bool XDREncode(JSContext* cx); |
245 | | |
246 | | // Encodes or decodes this script, in the storage format required by the |
247 | | // script cache file. |
248 | | template<typename Buffer> |
249 | | void Code(Buffer& buffer) |
250 | 0 | { |
251 | 0 | buffer.codeString(mURL); |
252 | 0 | buffer.codeString(mCachePath); |
253 | 0 | buffer.codeUint32(mOffset); |
254 | 0 | buffer.codeUint32(mSize); |
255 | 0 | buffer.codeUint8(mProcessTypes); |
256 | 0 | } Unexecuted instantiation: void mozilla::ScriptPreloader::CachedScript::Code<mozilla::loader::InputBuffer>(mozilla::loader::InputBuffer&) Unexecuted instantiation: void mozilla::ScriptPreloader::CachedScript::Code<mozilla::loader::OutputBuffer>(mozilla::loader::OutputBuffer&) |
257 | | |
258 | | // Returns the XDR data generated for this script during this session. See |
259 | | // mXDRData. |
260 | | JS::TranscodeBuffer& Buffer() |
261 | 0 | { |
262 | 0 | MOZ_ASSERT(HasBuffer()); |
263 | 0 | return mXDRData.ref<JS::TranscodeBuffer>(); |
264 | 0 | } |
265 | | |
266 | 0 | bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); } |
267 | | |
268 | | // Returns the read-only XDR data for this script. See mXDRRange. |
269 | | const JS::TranscodeRange& Range() |
270 | 0 | { |
271 | 0 | MOZ_ASSERT(HasRange()); |
272 | 0 | return mXDRRange.ref(); |
273 | 0 | } |
274 | | |
275 | 0 | bool HasRange() { return mXDRRange.isSome(); } |
276 | | |
277 | | nsTArray<uint8_t>& Array() |
278 | 0 | { |
279 | 0 | MOZ_ASSERT(HasArray()); |
280 | 0 | return mXDRData.ref<nsTArray<uint8_t>>(); |
281 | 0 | } |
282 | | |
283 | 0 | bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); } |
284 | | |
285 | | |
286 | | JSScript* GetJSScript(JSContext* cx); |
287 | | |
288 | | size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) |
289 | 0 | { |
290 | 0 | auto size = mallocSizeOf(this); |
291 | 0 |
|
292 | 0 | if (HasArray()) { |
293 | 0 | size += Array().ShallowSizeOfExcludingThis(mallocSizeOf); |
294 | 0 | } else if (HasBuffer()) { |
295 | 0 | size += Buffer().sizeOfExcludingThis(mallocSizeOf); |
296 | 0 | } else { |
297 | 0 | return size; |
298 | 0 | } |
299 | 0 | |
300 | 0 | // Note: mURL and mCachePath use the same string for scripts loaded |
301 | 0 | // by the message manager. The following statement avoids |
302 | 0 | // double-measuring in that case. |
303 | 0 | size += (mURL.SizeOfExcludingThisIfUnshared(mallocSizeOf) + |
304 | 0 | mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf)); |
305 | 0 |
|
306 | 0 | return size; |
307 | 0 | } |
308 | | |
309 | | ScriptPreloader& mCache; |
310 | | |
311 | | // The URL from which this script was initially read and compiled. |
312 | | nsCString mURL; |
313 | | // A unique identifier for this script's filesystem location, used as a |
314 | | // primary cache lookup value. |
315 | | nsCString mCachePath; |
316 | | |
317 | | // The offset of this script in the cache file, from the start of the XDR |
318 | | // data block. |
319 | | uint32_t mOffset = 0; |
320 | | // The size of this script's encoded XDR data. |
321 | | uint32_t mSize = 0; |
322 | | |
323 | | TimeStamp mLoadTime{}; |
324 | | |
325 | | JS::Heap<JSScript*> mScript; |
326 | | |
327 | | // True if this script is ready to be executed. This means that either the |
328 | | // off-thread portion of an off-thread decode has finished, or the script |
329 | | // is too small to be decoded off-thread, and may be immediately decoded |
330 | | // whenever it is first executed. |
331 | | bool mReadyToExecute = false; |
332 | | |
333 | | // True if this script is expected to run once per process. If so, its |
334 | | // JSScript instance will be dropped as soon as the script has |
335 | | // executed and been encoded into the cache. |
336 | | bool mIsRunOnce = false; |
337 | | |
338 | | // The set of processes in which this script has been used. |
339 | | EnumSet<ProcessType> mProcessTypes{}; |
340 | | |
341 | | // The set of processes which the script was loaded into during the |
342 | | // last session, as read from the cache file. |
343 | | EnumSet<ProcessType> mOriginalProcessTypes{}; |
344 | | |
345 | | // The read-only XDR data for this script, which was either read from an |
346 | | // existing cache file, or generated by encoding a script which was |
347 | | // compiled during this session. |
348 | | Maybe<JS::TranscodeRange> mXDRRange; |
349 | | |
350 | | // XDR data which was generated from a script compiled during this |
351 | | // session, and will be written to the cache file. |
352 | | MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData; |
353 | | } JS_HAZ_NON_GC_POINTER; |
354 | | |
355 | | template <ScriptStatus status> |
356 | | static Matcher<CachedScript*>* Match() |
357 | 0 | { |
358 | 0 | static CachedScript::StatusMatcher matcher{status}; |
359 | 0 | return &matcher; |
360 | 0 | } Unexecuted instantiation: mozilla::loader::Matcher<mozilla::ScriptPreloader::CachedScript*>* mozilla::ScriptPreloader::Match<(mozilla::ScriptPreloader::ScriptStatus)1>() Unexecuted instantiation: mozilla::loader::Matcher<mozilla::ScriptPreloader::CachedScript*>* mozilla::ScriptPreloader::Match<(mozilla::ScriptPreloader::ScriptStatus)0>() |
361 | | |
362 | | // There's a significant setup cost for each off-thread decode operation, |
363 | | // so scripts are decoded in chunks to minimize the overhead. There's a |
364 | | // careful balancing act in choosing the size of chunks, to minimize the |
365 | | // number of decode operations, while also minimizing the number of buffer |
366 | | // underruns that require the main thread to wait for a script to finish |
367 | | // decoding. |
368 | | // |
369 | | // For the first chunk, we don't have much time between the start of the |
370 | | // decode operation and the time the first script is needed, so that chunk |
371 | | // needs to be fairly small. After the first chunk is finished, we have |
372 | | // some buffered scripts to fall back on, and a lot more breathing room, |
373 | | // so the chunks can be a bit bigger, but still not too big. |
374 | | static constexpr int OFF_THREAD_FIRST_CHUNK_SIZE = 128 * 1024; |
375 | | static constexpr int OFF_THREAD_CHUNK_SIZE = 512 * 1024; |
376 | | |
377 | | // Ideally, we want every chunk to be smaller than the chunk sizes |
378 | | // specified above. However, if we have some number of small scripts |
379 | | // followed by a huge script that would put us over the normal chunk size, |
380 | | // we're better off processing them as a single chunk. |
381 | | // |
382 | | // In order to guarantee that the JS engine will process a chunk |
383 | | // off-thread, it needs to be at least 100K (which is an implementation |
384 | | // detail that can change at any time), so make sure that we always hit at |
385 | | // least that size, with a bit of breathing room to be safe. |
386 | | static constexpr int SMALL_SCRIPT_CHUNK_THRESHOLD = 128 * 1024; |
387 | | |
388 | | // The maximum size of scripts to re-decode on the main thread if off-thread |
389 | | // decoding hasn't finished yet. In practice, we don't hit this very often, |
390 | | // but when we do, re-decoding some smaller scripts on the main thread gives |
391 | | // the background decoding a chance to catch up without blocking the main |
392 | | // thread for quite as long. |
393 | | static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024; |
394 | | |
395 | | ScriptPreloader(); |
396 | | |
397 | | void ForceWriteCacheFile(); |
398 | | void Cleanup(); |
399 | | |
400 | | void FinishPendingParses(MonitorAutoLock& aMal); |
401 | | void InvalidateCache(); |
402 | | |
403 | | // Opens the cache file for reading. |
404 | | Result<Ok, nsresult> OpenCache(); |
405 | | |
406 | | // Writes a new cache file to disk. Must not be called on the main thread. |
407 | | Result<Ok, nsresult> WriteCache(); |
408 | | |
409 | | // Prepares scripts for writing to the cache, serializing new scripts to |
410 | | // XDR, and calculating their size-based offsets. |
411 | | void PrepareCacheWrite(); |
412 | | |
413 | | void PrepareCacheWriteInternal(); |
414 | | |
415 | | void FinishContentStartup(); |
416 | | |
417 | | // Returns true if scripts added to the cache now will be encoded and |
418 | | // written to the cache. If we've passed the startup script loading |
419 | | // window, or this is a content process which hasn't been asked to return |
420 | | // script bytecode, this will return false. |
421 | | bool WillWriteScripts(); |
422 | | |
423 | | // Returns a file pointer for the cache file with the given name in the |
424 | | // current profile. |
425 | | Result<nsCOMPtr<nsIFile>, nsresult> |
426 | | GetCacheFile(const nsAString& suffix); |
427 | | |
428 | | // Waits for the given cached script to finish compiling off-thread, or |
429 | | // decodes it synchronously on the main thread, as appropriate. |
430 | | JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script); |
431 | | |
432 | | void DecodeNextBatch(size_t chunkSize, JS::HandleObject scope = nullptr); |
433 | | |
434 | | static void OffThreadDecodeCallback(JS::OffThreadToken* token, void* context); |
435 | | void MaybeFinishOffThreadDecode(); |
436 | | void DoFinishOffThreadDecode(); |
437 | | |
438 | | size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf) |
439 | 0 | { |
440 | 0 | return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) + |
441 | 0 | mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get())); |
442 | 0 | } |
443 | | |
444 | | using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>; |
445 | | |
446 | | template<ScriptStatus status> |
447 | | static size_t SizeOfHashEntries(ScriptHash& scripts, mozilla::MallocSizeOf mallocSizeOf) |
448 | 0 | { |
449 | 0 | size_t size = 0; |
450 | 0 | for (auto elem : IterHash(scripts, Match<status>())) { |
451 | 0 | size += elem->HeapSizeOfIncludingThis(mallocSizeOf); |
452 | 0 | } |
453 | 0 | return size; |
454 | 0 | } Unexecuted instantiation: unsigned long mozilla::ScriptPreloader::SizeOfHashEntries<(mozilla::ScriptPreloader::ScriptStatus)1>(nsClassHashtable<nsCStringHashKey, mozilla::ScriptPreloader::CachedScript>&, unsigned long (*)(void const*)) Unexecuted instantiation: unsigned long mozilla::ScriptPreloader::SizeOfHashEntries<(mozilla::ScriptPreloader::ScriptStatus)0>(nsClassHashtable<nsCStringHashKey, mozilla::ScriptPreloader::CachedScript>&, unsigned long (*)(void const*)) |
455 | | |
456 | | ScriptHash mScripts; |
457 | | |
458 | | // True after we've shown the first window, and are no longer adding new |
459 | | // scripts to the cache. |
460 | | bool mStartupFinished = false; |
461 | | |
462 | | bool mCacheInitialized = false; |
463 | | bool mSaveComplete = false; |
464 | | bool mDataPrepared = false; |
465 | | bool mCacheInvalidated = false; |
466 | | bool mBlockedOnSyncDispatch = false; |
467 | | |
468 | | // The list of scripts that we read from the initial startup cache file, |
469 | | // but have yet to initiate a decode task for. |
470 | | LinkedList<CachedScript> mPendingScripts; |
471 | | |
472 | | // The lists of scripts and their sources that make up the chunk currently |
473 | | // being decoded in a background thread. |
474 | | JS::TranscodeSources mParsingSources; |
475 | | Vector<CachedScript*> mParsingScripts; |
476 | | |
477 | | // The token for the completed off-thread decode task. |
478 | | JS::OffThreadToken* mToken = nullptr; |
479 | | |
480 | | // True if a runnable has been dispatched to the main thread to finish an |
481 | | // off-thread decode operation. |
482 | | bool mFinishDecodeRunnablePending = false; |
483 | | |
484 | | // The process type of the current process. |
485 | | static ProcessType sProcessType; |
486 | | |
487 | | // The process types for which remote processes have been initialized, and |
488 | | // are expected to send back script data. |
489 | | EnumSet<ProcessType> mInitializedProcesses{}; |
490 | | |
491 | | RefPtr<ScriptPreloader> mChildCache; |
492 | | ScriptCacheChild* mChildActor = nullptr; |
493 | | |
494 | | nsString mBaseName; |
495 | | nsCString mContentStartupFinishedTopic; |
496 | | |
497 | | nsCOMPtr<nsIFile> mProfD; |
498 | | nsCOMPtr<nsIThread> mSaveThread; |
499 | | nsCOMPtr<nsITimer> mSaveTimer; |
500 | | |
501 | | // The mmapped cache data from this session's cache file. |
502 | | AutoMemMap mCacheData; |
503 | | |
504 | | Monitor mMonitor; |
505 | | Monitor mSaveMonitor; |
506 | | }; |
507 | | |
508 | | } // namespace mozilla |
509 | | |
510 | | #endif // ScriptPreloader_h |