Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* vim: set ts=8 sts=2 et sw=2 tw=80:
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* JS script descriptor. */
#ifndef vm_JSScript_h
#define vm_JSScript_h
#include "mozilla/Atomics.h"
#include "mozilla/Maybe.h"
#include "mozilla/MaybeOneOf.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Span.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Utf8.h"
#include "mozilla/Variant.h"
#include <type_traits> // std::is_same
#include <utility> // std::move
#include "jstypes.h"
#include "frontend/ScriptIndex.h" // ScriptIndex
#include "gc/Barrier.h"
#include "js/ColumnNumber.h" // JS::LimitedColumnNumberOneOrigin, JS::LimitedColumnNumberOneOrigin
#include "js/CompileOptions.h"
#include "js/Transcoding.h"
#include "js/UbiNode.h"
#include "js/UniquePtr.h"
#include "js/Utility.h"
#include "util/TrailingArray.h"
#include "vm/BytecodeIterator.h"
#include "vm/BytecodeLocation.h"
#include "vm/BytecodeUtil.h"
#include "vm/MutexIDs.h" // mutexid
#include "vm/NativeObject.h"
#include "vm/SharedImmutableStringsCache.h"
#include "vm/SharedStencil.h" // js::GCThingIndex, js::SourceExtent, js::SharedImmutableScriptData, MemberInitializers
#include "vm/StencilEnums.h" // SourceRetrievable
namespace JS {
struct ScriptSourceInfo;
template <typename UnitT>
class SourceText;
} // namespace JS
namespace js {
class FrontendContext;
class ScriptSource;
class VarScope;
class LexicalScope;
class JS_PUBLIC_API Sprinter;
namespace coverage {
class LCovSource;
} // namespace coverage
namespace gc {
class AllocSite;
} // namespace gc
namespace jit {
class AutoKeepJitScripts;
class BaselineScript;
class IonScript;
struct IonScriptCounts;
class JitScript;
} // namespace jit
class ModuleObject;
class RegExpObject;
class SourceCompressionTask;
class Shape;
class SrcNote;
class DebugScript;
namespace frontend {
struct CompilationStencil;
struct ExtensibleCompilationStencil;
struct CompilationGCOutput;
struct InitialStencilAndDelazifications;
struct CompilationStencilMerger;
class StencilXDR;
} // namespace frontend
class ScriptCounts {
public:
using PCCountsVector = mozilla::Vector<PCCounts, 0, SystemAllocPolicy>;
inline ScriptCounts();
inline explicit ScriptCounts(PCCountsVector&& jumpTargets);
inline ScriptCounts(ScriptCounts&& src);
inline ~ScriptCounts();
inline ScriptCounts& operator=(ScriptCounts&& src);
// Return the counter used to count the number of visits. Returns null if
// the element is not found.
PCCounts* maybeGetPCCounts(size_t offset);
const PCCounts* maybeGetPCCounts(size_t offset) const;
// PCCounts are stored at jump-target offsets. This function looks for the
// previous PCCount which is in the same basic block as the current offset.
PCCounts* getImmediatePrecedingPCCounts(size_t offset);
// Return the counter used to count the number of throws. Returns null if
// the element is not found.
const PCCounts* maybeGetThrowCounts(size_t offset) const;
// Throw counts are stored at the location of each throwing
// instruction. This function looks for the previous throw count.
//
// Note: if the offset of the returned count is higher than the offset of
// the immediate preceding PCCount, then this throw happened in the same
// basic block.
const PCCounts* getImmediatePrecedingThrowCounts(size_t offset) const;
// Return the counter used to count the number of throws. Allocate it if
// none exists yet. Returns null if the allocation failed.
PCCounts* getThrowCounts(size_t offset);
size_t sizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf);
bool traceWeak(JSTracer* trc) { return true; }
private:
friend class ::JSScript;
friend struct ScriptAndCounts;
// This sorted array is used to map an offset to the number of times a
// branch got visited.
PCCountsVector pcCounts_;
// This sorted vector is used to map an offset to the number of times an
// instruction throw.
PCCountsVector throwCounts_;
// Information about any Ion compilations for the script.
jit::IonScriptCounts* ionCounts_;
};
// The key of these side-table hash maps are intentionally not traced GC
// references to JSScript. Instead, we use bare pointers and manually fix up
// when objects could have moved (see Zone::fixupScriptMapsAfterMovingGC) and
// remove when the realm is destroyed (see Zone::clearScriptCounts and
// Zone::clearScriptNames). They essentially behave as weak references, except
// that the references are not cleared early by the GC. They must be non-strong
// references because the tables are kept at the Zone level and otherwise the
// table keys would keep scripts alive, thus keeping Realms alive, beyond their
// expected lifetimes. However, We do not use actual weak references (e.g. as
// used by WeakMap tables provided in gc/WeakMap.h) because they would be
// collected before the calls to the JSScript::finalize function which are used
// to aggregate code coverage results on the realm.
//
// Note carefully, however, that there is an exceptional case for which we *do*
// want the JSScripts to be strong references (and thus traced): when the
// --dump-bytecode command line option or the PCCount JSFriend API is used,
// then the scripts for all counts must remain alive. See
// Zone::traceScriptTableRoots() for more details.
//
// TODO: Clean this up by either aggregating coverage results in some other
// way, or by tweaking sweep ordering.
using UniqueScriptCounts = js::UniquePtr<ScriptCounts>;
using ScriptCountsMap =
GCRekeyableHashMap<HeapPtr<BaseScript*>, UniqueScriptCounts,
DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
// The 'const char*' for the function name is a pointer within the LCovSource's
// LifoAlloc and will be discarded at the same time.
using ScriptLCovEntry = std::tuple<coverage::LCovSource*, const char*>;
using ScriptLCovMap =
GCRekeyableHashMap<HeapPtr<BaseScript*>, ScriptLCovEntry,
DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
#ifdef MOZ_VTUNE
using ScriptVTuneIdMap =
GCRekeyableHashMap<HeapPtr<BaseScript*>, uint32_t,
DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
#endif
#ifdef JS_CACHEIR_SPEW
using ScriptFinalWarmUpCountEntry = std::tuple<uint32_t, SharedImmutableString>;
using ScriptFinalWarmUpCountMap =
GCRekeyableHashMap<HeapPtr<BaseScript*>, ScriptFinalWarmUpCountEntry,
DefaultHasher<HeapPtr<BaseScript*>>, SystemAllocPolicy>;
#endif
struct ScriptSourceChunk {
ScriptSource* ss = nullptr;
uint32_t chunk = 0;
ScriptSourceChunk() = default;
ScriptSourceChunk(ScriptSource* ss, uint32_t chunk) : ss(ss), chunk(chunk) {
MOZ_ASSERT(valid());
}
bool valid() const { return ss != nullptr; }
bool operator==(const ScriptSourceChunk& other) const {
return ss == other.ss && chunk == other.chunk;
}
};
struct ScriptSourceChunkHasher {
using Lookup = ScriptSourceChunk;
static HashNumber hash(const ScriptSourceChunk& ssc) {
return mozilla::AddToHash(DefaultHasher<ScriptSource*>::hash(ssc.ss),
ssc.chunk);
}
static bool match(const ScriptSourceChunk& c1, const ScriptSourceChunk& c2) {
return c1 == c2;
}
};
template <typename Unit>
using EntryUnits = mozilla::UniquePtr<Unit[], JS::FreePolicy>;
// The uncompressed source cache contains *either* UTF-8 source data *or*
// UTF-16 source data. ScriptSourceChunk implies a ScriptSource that
// contains either UTF-8 data or UTF-16 data, so the nature of the key to
// Map below indicates how each SourceData ought to be interpreted.
using SourceData = mozilla::UniquePtr<void, JS::FreePolicy>;
template <typename Unit>
inline SourceData ToSourceData(EntryUnits<Unit> chars) {
static_assert(std::is_same_v<SourceData::DeleterType,
typename EntryUnits<Unit>::DeleterType>,
"EntryUnits and SourceData must share the same deleter "
"type, that need not know the type of the data being freed, "
"for the upcast below to be safe");
return SourceData(chars.release());
}
class UncompressedSourceCache {
using Map = HashMap<ScriptSourceChunk, SourceData, ScriptSourceChunkHasher,
SystemAllocPolicy>;
public:
// Hold an entry in the source data cache and prevent it from being purged on
// GC.
class AutoHoldEntry {
UncompressedSourceCache* cache_ = nullptr;
ScriptSourceChunk sourceChunk_ = {};
SourceData data_ = nullptr;
public:
explicit AutoHoldEntry() = default;
~AutoHoldEntry() {
if (cache_) {
MOZ_ASSERT(sourceChunk_.valid());
cache_->releaseEntry(*this);
}
}
template <typename Unit>
void holdUnits(EntryUnits<Unit> units) {
MOZ_ASSERT(!cache_);
MOZ_ASSERT(!sourceChunk_.valid());
MOZ_ASSERT(!data_);
data_ = ToSourceData(std::move(units));
}
private:
void holdEntry(UncompressedSourceCache* cache,
const ScriptSourceChunk& sourceChunk) {
// Initialise the holder for a specific cache and script source.
// This will hold on to the cached source chars in the event that
// the cache is purged.
MOZ_ASSERT(!cache_);
MOZ_ASSERT(!sourceChunk_.valid());
MOZ_ASSERT(!data_);
cache_ = cache;
sourceChunk_ = sourceChunk;
}
void deferDelete(SourceData data) {
// Take ownership of source chars now the cache is being purged. Remove
// our reference to the ScriptSource which might soon be destroyed.
MOZ_ASSERT(cache_);
MOZ_ASSERT(sourceChunk_.valid());
MOZ_ASSERT(!data_);
cache_ = nullptr;
sourceChunk_ = ScriptSourceChunk();
data_ = std::move(data);
}
const ScriptSourceChunk& sourceChunk() const { return sourceChunk_; }
friend class UncompressedSourceCache;
};
private:
UniquePtr<Map> map_ = nullptr;
AutoHoldEntry* holder_ = nullptr;
public:
UncompressedSourceCache() = default;
template <typename Unit>
const Unit* lookup(const ScriptSourceChunk& ssc, AutoHoldEntry& asp);
bool put(const ScriptSourceChunk& ssc, SourceData data, AutoHoldEntry& asp);
void purge();
size_t sizeOfExcludingThis(mozilla::MallocSizeOf mallocSizeOf);
private:
void holdEntry(AutoHoldEntry& holder, const ScriptSourceChunk& ssc);
void releaseEntry(AutoHoldEntry& holder);
};
template <typename Unit>
struct SourceTypeTraits;
template <>
struct SourceTypeTraits<mozilla::Utf8Unit> {
using CharT = char;
using SharedImmutableString = js::SharedImmutableString;
static const mozilla::Utf8Unit* units(const SharedImmutableString& string) {
// Casting |char| data to |Utf8Unit| is safe because |Utf8Unit|
// contains a |char|. See the long comment in |Utf8Unit|'s definition.
return reinterpret_cast<const mozilla::Utf8Unit*>(string.chars());
}
static char* toString(const mozilla::Utf8Unit* units) {
auto asUnsigned =
const_cast<unsigned char*>(mozilla::Utf8AsUnsignedChars(units));
return reinterpret_cast<char*>(asUnsigned);
}
static UniqueChars toCacheable(EntryUnits<mozilla::Utf8Unit> str) {
// The cache only stores strings of |char| or |char16_t|, and right now
// it seems best not to gunk up the cache with |Utf8Unit| too. So
// cache |Utf8Unit| strings by interpreting them as |char| strings.
char* chars = toString(str.release());
return UniqueChars(chars);
}
};
template <>
struct SourceTypeTraits<char16_t> {
using CharT = char16_t;
using SharedImmutableString = js::SharedImmutableTwoByteString;
static const char16_t* units(const SharedImmutableString& string) {
return string.chars();
}
static char16_t* toString(const char16_t* units) {
return const_cast<char16_t*>(units);
}
static UniqueTwoByteChars toCacheable(EntryUnits<char16_t> str) {
return UniqueTwoByteChars(std::move(str));
}
};
// Synchronously compress the source of |script|, for testing purposes.
[[nodiscard]] extern bool SynchronouslyCompressSource(
JSContext* cx, JS::Handle<BaseScript*> script);
// [SMDOC] ScriptSource
//
// This class abstracts over the source we used to compile from. The current
// representation may transition to different modes in order to save memory.
// Abstractly the source may be one of UTF-8 or UTF-16. The data itself may be
// unavailable, retrieveable-using-source-hook, compressed, or uncompressed. If
// source is retrieved or decompressed for use, we may update the ScriptSource
// to hold the result.
class ScriptSource {
// NOTE: While ScriptSources may be compressed off thread, they are only
// modified by the main thread, and all members are always safe to access
// on the main thread.
friend class SourceCompressionTask;
friend bool SynchronouslyCompressSource(JSContext* cx,
JS::Handle<BaseScript*> script);
friend class frontend::StencilXDR;
private:
// Common base class of the templated variants of PinnedUnits<T>.
class PinnedUnitsBase {
protected:
ScriptSource* source_;
explicit PinnedUnitsBase(ScriptSource* source) : source_(source) {}
void addReader();
template <typename Unit>
void removeReader();
};
public:
// Any users that wish to manipulate the char buffer of the ScriptSource
// needs to do so via PinnedUnits for GC safety. A GC may compress
// ScriptSources. If the source were initially uncompressed, then any raw
// pointers to the char buffer would now point to the freed, uncompressed
// chars. This is analogous to Rooted.
template <typename Unit>
class PinnedUnits : public PinnedUnitsBase {
const Unit* units_;
public:
PinnedUnits(JSContext* cx, ScriptSource* source,
UncompressedSourceCache::AutoHoldEntry& holder, size_t begin,
size_t len);
~PinnedUnits();
const Unit* get() const { return units_; }
const typename SourceTypeTraits<Unit>::CharT* asChars() const {
return SourceTypeTraits<Unit>::toString(get());
}
};
template <typename Unit>
class PinnedUnitsIfUncompressed : public PinnedUnitsBase {
const Unit* units_;
public:
PinnedUnitsIfUncompressed(ScriptSource* source, size_t begin, size_t len);
~PinnedUnitsIfUncompressed();
const Unit* get() const { return units_; }
const typename SourceTypeTraits<Unit>::CharT* asChars() const {
return SourceTypeTraits<Unit>::toString(get());
}
};
private:
// Missing source text that isn't retrievable using the source hook. (All
// ScriptSources initially begin in this state. Users that are compiling
// source text will overwrite |data| to store a different state.)
struct Missing {};
// Source that can be retrieved using the registered source hook. |Unit|
// records the source type so that source-text coordinates in functions and
// scripts that depend on this |ScriptSource| are correct.
template <typename Unit>
struct Retrievable {
// The source hook and script URL required to retrieve source are stored
// elsewhere, so nothing is needed here. It'd be better hygiene to store
// something source-hook-like in each |ScriptSource| that needs it, but that
// requires reimagining a source-hook API that currently depends on source
// hooks being uniquely-owned pointers...
};
// Uncompressed source text. Templates distinguish if we are interconvertable
// to |Retrievable| or not.
template <typename Unit>
class UncompressedData {
typename SourceTypeTraits<Unit>::SharedImmutableString string_;
public:
explicit UncompressedData(
typename SourceTypeTraits<Unit>::SharedImmutableString str)
: string_(std::move(str)) {}
const Unit* units() const { return SourceTypeTraits<Unit>::units(string_); }
size_t length() const { return string_.length(); }
};
template <typename Unit, SourceRetrievable CanRetrieve>
class Uncompressed : public UncompressedData<Unit> {
using Base = UncompressedData<Unit>;
public:
using Base::Base;
};
// Compressed source text. Templates distinguish if we are interconvertable
// to |Retrievable| or not.
template <typename Unit>
struct CompressedData {
// Single-byte compressed text, regardless whether the original text
// was single-byte or two-byte.
SharedImmutableString raw;
size_t uncompressedLength;
CompressedData(SharedImmutableString raw, size_t uncompressedLength)
: raw(std::move(raw)), uncompressedLength(uncompressedLength) {}
};
template <typename Unit, SourceRetrievable CanRetrieve>
struct Compressed : public CompressedData<Unit> {
using Base = CompressedData<Unit>;
public:
using Base::Base;
};
// The set of currently allowed encoding modes.
using SourceType =
mozilla::Variant<Compressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
Uncompressed<mozilla::Utf8Unit, SourceRetrievable::Yes>,
Compressed<mozilla::Utf8Unit, SourceRetrievable::No>,
Uncompressed<mozilla::Utf8Unit, SourceRetrievable::No>,
Compressed<char16_t, SourceRetrievable::Yes>,
Uncompressed<char16_t, SourceRetrievable::Yes>,
Compressed<char16_t, SourceRetrievable::No>,
Uncompressed<char16_t, SourceRetrievable::No>,
Retrievable<mozilla::Utf8Unit>, Retrievable<char16_t>,
Missing>;
//
// Start of fields.
//
mozilla::Atomic<uint32_t, mozilla::ReleaseAcquire> refs = {};
// An id for this source that is unique across the process. This can be used
// to refer to this source from places that don't want to hold a strong
// reference on the source itself.
//
// This is a 32 bit ID and could overflow, in which case the ID will not be
// unique anymore.
uint32_t id_ = 0;
// Source data (as a mozilla::Variant).
SourceType data = SourceType(Missing());
// If the GC calls triggerConvertToCompressedSource with PinnedUnits present,
// the last PinnedUnits instance will install the compressed chars upon
// destruction.
//
// Retrievability isn't part of the type here because uncompressed->compressed
// transitions must preserve existing retrievability.
struct ReaderInstances {
size_t count = 0;
mozilla::MaybeOneOf<CompressedData<mozilla::Utf8Unit>,
CompressedData<char16_t>>
pendingCompressed;
};
ExclusiveData<ReaderInstances> readers_;
// The UTF-8 encoded filename of this script.
SharedImmutableString filename_;
// Hash of the script filename;
HashNumber filenameHash_ = 0;
// If this ScriptSource was generated by a code-introduction mechanism such
// as |eval| or |new Function|, the debugger needs access to the "raw"
// filename of the top-level script that contains the eval-ing code. To
// keep track of this, we must preserve the original outermost filename (of
// the original introducer script), so that instead of a filename of
// "foo.js line 30 > eval line 10 > Function", we can obtain the original
// raw filename of "foo.js".
//
// In the case described above, this field will be set to to the original raw
// UTF-8 encoded filename from above, otherwise it will be mozilla::Nothing.
SharedImmutableString introducerFilename_;
SharedImmutableTwoByteString displayURL_;
SharedImmutableTwoByteString sourceMapURL_;
// A string indicating how this source code was introduced into the system.
// This is a constant, statically allocated C string, so does not need memory
// management.
//
// TODO: Document the various additional introduction type constants.
const char* introductionType_ = nullptr;
// Bytecode offset in caller script that generated this code. This is
// present for eval-ed code, as well as "new Function(...)"-introduced
// scripts.
mozilla::Maybe<uint32_t> introductionOffset_;
// If this source is for Function constructor, the position of ")" after
// parameter list in the source. This is used to get function body.
// 0 for other cases.
uint32_t parameterListEnd_ = 0;
// Line number within the file where this source starts (1-origin).
uint32_t startLine_ = 0;
// Column number within the file where this source starts,
// in UTF-16 code units.
JS::LimitedColumnNumberOneOrigin startColumn_;
// See: CompileOptions::mutedErrors.
bool mutedErrors_ = false;
// Carry the delazification mode per source.
JS::DelazificationOption delazificationMode_ =
JS::DelazificationOption::OnDemandOnly;
// True if an associated SourceCompressionTask was ever created.
bool hadCompressionTask_ = false;
//
// End of fields.
//
// How many ids have been handed out to sources.
static mozilla::Atomic<uint32_t, mozilla::SequentiallyConsistent> idCount_;
template <typename Unit>
const Unit* chunkUnits(JSContext* cx,
UncompressedSourceCache::AutoHoldEntry& holder,
size_t chunk);
// Return a string containing the chars starting at |begin| and ending at
// |begin + len|.
//
// Warning: this is *not* GC-safe! Any chars to be handed out must use
// PinnedUnits. See comment below.
template <typename Unit>
const Unit* units(JSContext* cx, UncompressedSourceCache::AutoHoldEntry& asp,
size_t begin, size_t len);
template <typename Unit>
const Unit* uncompressedUnits(size_t begin, size_t len);
public:
// When creating a JSString* from TwoByte source characters, we don't try to
// to deflate to Latin1 for longer strings, because this can be slow.
static const size_t SourceDeflateLimit = 100;
explicit ScriptSource()
: id_(++idCount_), readers_(js::mutexid::SourceCompression) {}
~ScriptSource() { MOZ_ASSERT(refs == 0); }
void AddRef() { refs++; }
void Release() {
MOZ_ASSERT(refs != 0);
if (--refs == 0) {
js_delete(this);
}
}
[[nodiscard]] bool initFromOptions(FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options);
/**
* The minimum script length (in code units) necessary for a script to be
* eligible to be compressed.
*/
static constexpr size_t MinimumCompressibleLength = 256;
SharedImmutableString getOrCreateStringZ(FrontendContext* fc,
UniqueChars&& str);
SharedImmutableTwoByteString getOrCreateStringZ(FrontendContext* fc,
UniqueTwoByteChars&& str);
private:
class LoadSourceMatcher;
public:
// Attempt to load usable source for |ss| -- source text on which substring
// operations and the like can be performed. On success return true and set
// |*loaded| to indicate whether usable source could be loaded; otherwise
// return false.
static bool loadSource(JSContext* cx, ScriptSource* ss, bool* loaded);
// Assign source data from |srcBuf| to this recently-created |ScriptSource|.
template <typename Unit>
[[nodiscard]] bool assignSource(FrontendContext* fc,
const JS::ReadOnlyCompileOptions& options,
JS::SourceText<Unit>& srcBuf);
bool hasSourceText() const {
return hasUncompressedSource() || hasCompressedSource();
}
private:
template <typename Unit>
struct UncompressedDataMatcher {
template <SourceRetrievable CanRetrieve>
const UncompressedData<Unit>* operator()(
const Uncompressed<Unit, CanRetrieve>& u) {
return &u;
}
template <typename T>
const UncompressedData<Unit>* operator()(const T&) {
MOZ_CRASH(
"attempting to access uncompressed data in a ScriptSource not "
"containing it");
return nullptr;
}
};
public:
template <typename Unit>
const UncompressedData<Unit>* uncompressedData() {
return data.match(UncompressedDataMatcher<Unit>());
}
private:
template <typename Unit>
struct CompressedDataMatcher {
template <SourceRetrievable CanRetrieve>
const CompressedData<Unit>* operator()(
const Compressed<Unit, CanRetrieve>& c) {
return &c;
}
template <typename T>
const CompressedData<Unit>* operator()(const T&) {
MOZ_CRASH(
"attempting to access compressed data in a ScriptSource not "
"containing it");
return nullptr;
}
};
public:
template <typename Unit>
const CompressedData<Unit>* compressedData() {
return data.match(CompressedDataMatcher<Unit>());
}
private:
struct HasUncompressedSource {
template <typename Unit, SourceRetrievable CanRetrieve>
bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
return true;
}
template <typename Unit, SourceRetrievable CanRetrieve>
bool operator()(const Compressed<Unit, CanRetrieve>&) {
return false;
}
template <typename Unit>
bool operator()(const Retrievable<Unit>&) {
return false;
}
bool operator()(const Missing&) { return false; }
};
public:
bool hasUncompressedSource() const {
return data.match(HasUncompressedSource());
}
private:
template <typename Unit>
struct IsUncompressed {
template <SourceRetrievable CanRetrieve>
bool operator()(const Uncompressed<Unit, CanRetrieve>&) {
return true;
}
template <typename T>
bool operator()(const T&) {
return false;
}
};
public:
template <typename Unit>
bool isUncompressed() const {
return data.match(IsUncompressed<Unit>());
}
private:
struct HasCompressedSource {
template <typename Unit, SourceRetrievable CanRetrieve>
bool operator()(const Compressed<Unit, CanRetrieve>&) {
return true;
}
template <typename T>
bool operator()(const T&) {
return false;
}
};
public:
bool hasCompressedSource() const { return data.match(HasCompressedSource()); }
private:
template <typename Unit>
struct IsCompressed {
template <SourceRetrievable CanRetrieve>
bool operator()(const Compressed<Unit, CanRetrieve>&) {
return true;
}
template <typename T>
bool operator()(const T&) {
return false;
}
};
public:
template <typename Unit>
bool isCompressed() const {
return data.match(IsCompressed<Unit>());
}
private:
template <typename Unit>
struct SourceTypeMatcher {
template <template <typename C, SourceRetrievable R> class Data,
SourceRetrievable CanRetrieve>
bool operator()(const Data<Unit, CanRetrieve>&) {
return true;
}
template <template <typename C, SourceRetrievable R> class Data,
typename NotUnit, SourceRetrievable CanRetrieve>
bool operator()(const Data<NotUnit, CanRetrieve>&) {
return false;
}
bool operator()(const Retrievable<Unit>&) {
MOZ_CRASH("source type only applies where actual text is available");
return false;
}
template <typename NotUnit>
bool operator()(const Retrievable<NotUnit>&) {
return false;
}
bool operator()(const Missing&) {
MOZ_CRASH("doesn't make sense to ask source type when missing");
return false;
}
};
public:
template <typename Unit>
bool hasSourceType() const {
return data.match(SourceTypeMatcher<Unit>());
}
private:
struct UncompressedLengthMatcher {
template <typename Unit, SourceRetrievable CanRetrieve>
size_t operator()(const Uncompressed<Unit, CanRetrieve>& u) {
return u.length();
}
template <typename Unit, SourceRetrievable CanRetrieve>
size_t operator()(const Compressed<Unit, CanRetrieve>& u) {
return u.uncompressedLength;
}
template <typename Unit>
size_t operator()(const Retrievable<Unit>&) {
MOZ_CRASH("ScriptSource::length on a missing-but-retrievable source");
return 0;
}
size_t operator()(const Missing& m) {
MOZ_CRASH("ScriptSource::length on a missing source");
return 0;
}
};
public:
size_t length() const {
MOZ_ASSERT(hasSourceText());
return data.match(UncompressedLengthMatcher());
}
JSLinearString* substring(JSContext* cx, size_t start, size_t stop);
JSLinearString* substringDontDeflate(JSContext* cx, size_t start,
size_t stop);
[[nodiscard]] bool appendSubstring(JSContext* cx, js::StringBuilder& buf,
size_t start, size_t stop);
void setParameterListEnd(uint32_t parameterListEnd) {
parameterListEnd_ = parameterListEnd;
}
bool isFunctionBody() { return parameterListEnd_ != 0; }
JSLinearString* functionBodyString(JSContext* cx);
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
JS::ScriptSourceInfo* info) const;
private:
// Overwrites |data| with the uncompressed data from |source|.
//
// This function asserts nothing about |data|. Users should use assertions to
// double-check their own understandings of the |data| state transition being
// performed.
template <typename ContextT, typename Unit>
[[nodiscard]] bool setUncompressedSourceHelper(ContextT* cx,
EntryUnits<Unit>&& source,
size_t length,
SourceRetrievable retrievable);
public:
// Initialize a fresh |ScriptSource| with unretrievable, uncompressed source.
template <typename Unit>
[[nodiscard]] bool initializeUnretrievableUncompressedSource(
FrontendContext* fc, EntryUnits<Unit>&& source, size_t length);
// Set the retrieved source for a |ScriptSource| whose source was recorded as
// missing but retrievable.
template <typename Unit>
[[nodiscard]] bool setRetrievedSource(JSContext* cx,
EntryUnits<Unit>&& source,
size_t length);
[[nodiscard]] bool tryCompressOffThread(JSContext* cx);
// Called by the SourceCompressionTask constructor to indicate such a task was
// ever created.
void noteSourceCompressionTask() { hadCompressionTask_ = true; }
// *Trigger* the conversion of this ScriptSource from containing uncompressed
// |Unit|-encoded source to containing compressed source. Conversion may not
// be complete when this function returns: it'll be delayed if there's ongoing
// use of the uncompressed source via |PinnedUnits|, in which case conversion
// won't occur until the outermost |PinnedUnits| is destroyed.
//
// Compressed source is in bytes, no matter that |Unit| might be |char16_t|.
// |sourceLength| is the length in code units (not bytes) of the uncompressed
// source.
template <typename Unit>
void triggerConvertToCompressedSource(SharedImmutableString compressed,
size_t sourceLength);
// Initialize a fresh ScriptSource as containing unretrievable compressed
// source of the indicated original encoding.
template <typename Unit>
[[nodiscard]] bool initializeWithUnretrievableCompressedSource(
FrontendContext* fc, UniqueChars&& raw, size_t rawLength,
size_t sourceLength);
private:
void performTaskWork(SourceCompressionTask* task);
struct TriggerConvertToCompressedSourceFromTask {
ScriptSource* const source_;
SharedImmutableString& compressed_;
TriggerConvertToCompressedSourceFromTask(ScriptSource* source,
SharedImmutableString& compressed)
: source_(source), compressed_(compressed) {}
template <typename Unit, SourceRetrievable CanRetrieve>
void operator()(const Uncompressed<Unit, CanRetrieve>&) {
source_->triggerConvertToCompressedSource<Unit>(std::move(compressed_),
source_->length());
}
template <typename Unit, SourceRetrievable CanRetrieve>
void operator()(const Compressed<Unit, CanRetrieve>&) {
MOZ_CRASH(
"can't set compressed source when source is already compressed -- "
"ScriptSource::tryCompressOffThread shouldn't have queued up this "
"task?");
}
template <typename Unit>
void operator()(const Retrievable<Unit>&) {
MOZ_CRASH("shouldn't compressing unloaded-but-retrievable source");
}
void operator()(const Missing&) {
MOZ_CRASH(
"doesn't make sense to set compressed source for missing source -- "
"ScriptSource::tryCompressOffThread shouldn't have queued up this "
"task?");
}
};
template <typename Unit>
void convertToCompressedSource(SharedImmutableString compressed,
size_t uncompressedLength);
template <typename Unit>
void performDelayedConvertToCompressedSource(
ExclusiveData<ReaderInstances>::Guard& g);
void triggerConvertToCompressedSourceFromTask(
SharedImmutableString compressed);
public:
HashNumber filenameHash() const { return filenameHash_; }
const char* filename() const {
return filename_ ? filename_.chars() : nullptr;
}
[[nodiscard]] bool setFilename(FrontendContext* fc, const char* filename);
[[nodiscard]] bool setFilename(FrontendContext* fc, UniqueChars&& filename);
bool hasIntroducerFilename() const {
return introducerFilename_ ? true : false;
}
const char* introducerFilename() const {
return introducerFilename_ ? introducerFilename_.chars() : filename();
}
[[nodiscard]] bool setIntroducerFilename(FrontendContext* fc,
const char* filename);
[[nodiscard]] bool setIntroducerFilename(FrontendContext* fc,
UniqueChars&& filename);
bool hasIntroductionType() const { return introductionType_; }
const char* introductionType() const {
MOZ_ASSERT(hasIntroductionType());
return introductionType_;
}
uint32_t id() const { return id_; }
// Display URLs
[[nodiscard]] bool setDisplayURL(FrontendContext* fc, const char16_t* url);
[[nodiscard]] bool setDisplayURL(FrontendContext* fc,
UniqueTwoByteChars&& url);
bool hasDisplayURL() const { return bool(displayURL_); }
const char16_t* displayURL() { return displayURL_.chars(); }
// Source maps
[[nodiscard]] bool setSourceMapURL(FrontendContext* fc, const char16_t* url);
[[nodiscard]] bool setSourceMapURL(FrontendContext* fc,
UniqueTwoByteChars&& url);
bool hasSourceMapURL() const { return bool(sourceMapURL_); }
const char16_t* sourceMapURL() { return sourceMapURL_.chars(); }
bool mutedErrors() const { return mutedErrors_; }
uint32_t startLine() const { return startLine_; }
JS::LimitedColumnNumberOneOrigin startColumn() const { return startColumn_; }
JS::DelazificationOption delazificationMode() const {
return delazificationMode_;
}
bool hasIntroductionOffset() const { return introductionOffset_.isSome(); }
uint32_t introductionOffset() const { return introductionOffset_.value(); }
void setIntroductionOffset(uint32_t offset) {
MOZ_ASSERT(!hasIntroductionOffset());
MOZ_ASSERT(offset <= (uint32_t)INT32_MAX);
introductionOffset_.emplace(offset);
}
};
// [SMDOC] ScriptSourceObject
//
// ScriptSourceObject stores the ScriptSource and GC pointers related to it.
class ScriptSourceObject : public NativeObject {
static const JSClassOps classOps_;
public:
static const JSClass class_;
static void finalize(JS::GCContext* gcx, JSObject* obj);
static ScriptSourceObject* create(JSContext* cx, ScriptSource* source);
// Initialize those properties of this ScriptSourceObject whose values
// are provided by |options|, re-wrapping as necessary.
static bool initFromOptions(JSContext* cx,
JS::Handle<ScriptSourceObject*> source,
const JS::InstantiateOptions& options);
static bool initElementProperties(JSContext* cx,
JS::Handle<ScriptSourceObject*> source,
HandleString elementAttrName);
bool hasSource() const { return !getReservedSlot(SOURCE_SLOT).isUndefined(); }
ScriptSource* source() const {
return static_cast<ScriptSource*>(getReservedSlot(SOURCE_SLOT).toPrivate());
}
JSObject* unwrappedElement(JSContext* cx) const;
const Value& unwrappedElementAttributeName() const {
MOZ_ASSERT(isInitialized());
const Value& v = getReservedSlot(ELEMENT_PROPERTY_SLOT);
MOZ_ASSERT(!v.isMagic());
return v;
}
BaseScript* unwrappedIntroductionScript() const {
MOZ_ASSERT(isInitialized());
Value value = getReservedSlot(INTRODUCTION_SCRIPT_SLOT);
if (value.isUndefined()) {
return nullptr;
}
return value.toGCThing()->as<BaseScript>();
}
void setPrivate(JSRuntime* rt, const Value& value);
void clearPrivate(JSRuntime* rt);
void setIntroductionScript(const Value& introductionScript) {
setReservedSlot(INTRODUCTION_SCRIPT_SLOT, introductionScript);
}
Value getPrivate() const {
MOZ_ASSERT(isInitialized());
Value value = getReservedSlot(PRIVATE_SLOT);
return value;
}
private:
#ifdef DEBUG
bool isInitialized() const {
Value element = getReservedSlot(ELEMENT_PROPERTY_SLOT);
if (element.isMagic(JS_GENERIC_MAGIC)) {
return false;
}
return !getReservedSlot(INTRODUCTION_SCRIPT_SLOT).isMagic(JS_GENERIC_MAGIC);
}
#endif
enum {
SOURCE_SLOT = 0,
ELEMENT_PROPERTY_SLOT,
INTRODUCTION_SCRIPT_SLOT,
PRIVATE_SLOT,
STENCILS_SLOT,
RESERVED_SLOTS
};
// Delazification stencils can be aggregated in
// InitialStencilAndDelazification, this structure might be used for
// different purposes.
// - Collecting: The goal is to aggregate all delazified functions in order
// to aggregate them for serialization.
// - Sharing: The goal is to use the InitialStencilAndDelazification as a way
// to share multiple threads efforts towards parsing a Script Source
// content.
//
// See setCollectingDelazifications and setSharingDelazifications for details.
static constexpr uintptr_t STENCILS_COLLECTING_DELAZIFICATIONS_FLAG = 0x1;
static constexpr uintptr_t STENCILS_SHARING_DELAZIFICATIONS_FLAG = 0x2;
static constexpr uintptr_t STENCILS_MASK = 0x3;
void clearStencils();
template <uintptr_t flag>
void setStencilsFlag();
template <uintptr_t flag>
void unsetStencilsFlag();
template <uintptr_t flag>
bool isStencilsFlagSet() const;
public:
// Associate stencils to this ScriptSourceObject.
// The consumer should call setCollectingDelazifications or
// setSharingDelazifications after this.
void setStencils(
already_AddRefed<frontend::InitialStencilAndDelazifications> stencils);
// Start collecting delazifications.
// This is a temporary state until unsetCollectingDelazifications is called,
// and this expects a pair of set/unset call.
//
// The caller should check isCollectingDelazifications before calling this.
void setCollectingDelazifications();
// Clear the flag for collecting delazifications.
//
// If setSharingDelazifications wasn't called, this clears the association
// with the stencils.
void unsetCollectingDelazifications();
// Returns true if setCollectingDelazifications was called and
// unsetCollectingDelazifications is not yet called.
bool isCollectingDelazifications() const;
// Start sharing delazifications with others.
// This is a permanent state.
//
// The flag is orthogonal to setCollectingDelazifications.
void setSharingDelazifications();
// Returns true if setSharingDelazifications was called.
bool isSharingDelazifications() const;
// Return the associated stencils if any.
// Returns nullptr if stencils is not associated
frontend::InitialStencilAndDelazifications* maybeGetStencils();
};
// ScriptWarmUpData represents a pointer-sized field in BaseScript that stores
// one of the following using low-bit tags:
//
// * The enclosing BaseScript. This is only used while this script is lazy and
// its containing script is also lazy. This outer script must be compiled
// before the current script can in order to correctly build the scope chain.
//
// * The enclosing Scope. This is only used while this script is lazy and its
// containing script is compiled. This is the outer scope chain that will be
// used to compile this scipt.
//
// * The script's warm-up count. This is only used until the script has a
// JitScript. The Baseline Interpreter and JITs use the warm-up count stored
// in JitScript.
//
// * A pointer to the JitScript, when the script is warm enough for the Baseline
// Interpreter.
//
class ScriptWarmUpData {
uintptr_t data_ = ResetState();
private:
static constexpr uintptr_t NumTagBits = 2;
static constexpr uint32_t MaxWarmUpCount = UINT32_MAX >> NumTagBits;
public:
// Public only for the JITs.
static constexpr uintptr_t TagMask = (1 << NumTagBits) - 1;
static constexpr uintptr_t JitScriptTag = 0;
static constexpr uintptr_t EnclosingScriptTag = 1;
static constexpr uintptr_t EnclosingScopeTag = 2;
static constexpr uintptr_t WarmUpCountTag = 3;
private:
// A gc-safe value to clear to.
constexpr uintptr_t ResetState() { return 0 | WarmUpCountTag; }
template <uintptr_t Tag>
inline void setTaggedPtr(void* ptr) {
static_assert(Tag <= TagMask, "Tag must fit in TagMask");
MOZ_ASSERT((uintptr_t(ptr) & TagMask) == 0);
data_ = uintptr_t(ptr) | Tag;
}
template <typename T, uintptr_t Tag>
inline T getTaggedPtr() const {
static_assert(Tag <= TagMask, "Tag must fit in TagMask");
MOZ_ASSERT((data_ & TagMask) == Tag);
return reinterpret_cast<T>(data_ & ~TagMask);
}
void setWarmUpCount(uint32_t count) {
if (count > MaxWarmUpCount) {
count = MaxWarmUpCount;
}
data_ = (uintptr_t(count) << NumTagBits) | WarmUpCountTag;
}
public:
void trace(JSTracer* trc);
bool isEnclosingScript() const {
return (data_ & TagMask) == EnclosingScriptTag;
}
bool isEnclosingScope() const {
return (data_ & TagMask) == EnclosingScopeTag;
}
bool isWarmUpCount() const { return (data_ & TagMask) == WarmUpCountTag; }
bool isJitScript() const { return (data_ & TagMask) == JitScriptTag; }
// NOTE: To change type safely, 'clear' the old tagged value and then 'init'
// the new one. This will notify the GC appropriately.
BaseScript* toEnclosingScript() const {
return getTaggedPtr<BaseScript*, EnclosingScriptTag>();
}
inline void initEnclosingScript(BaseScript* enclosingScript);
inline void clearEnclosingScript();
Scope* toEnclosingScope() const {
return getTaggedPtr<Scope*, EnclosingScopeTag>();
}
inline void initEnclosingScope(Scope* enclosingScope);
inline void clearEnclosingScope();
uint32_t toWarmUpCount() const {