Coverage Report

Created: 2018-09-25 14:53

/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