Mypal/js/src/jscompartment.h

1076 lines
39 KiB
C++

/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
* 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/. */
#ifndef jscompartment_h
#define jscompartment_h
#include "mozilla/LinkedList.h"
#include "mozilla/Maybe.h"
#include "mozilla/MemoryReporting.h"
#include "mozilla/Tuple.h"
#include "mozilla/Variant.h"
#include "mozilla/XorShift128PlusRNG.h"
#include "builtin/RegExp.h"
#include "gc/Barrier.h"
#include "gc/NurseryAwareHashMap.h"
#include "gc/Zone.h"
#include "vm/GlobalObject.h"
#include "vm/PIC.h"
#include "vm/SavedStacks.h"
#include "vm/Time.h"
#include "wasm/WasmCompartment.h"
namespace js {
namespace jit {
class JitCompartment;
} // namespace jit
namespace gc {
template <typename Node, typename Derived> class ComponentFinder;
} // namespace gc
class LexicalEnvironmentObject;
class ScriptSourceObject;
struct NativeIterator;
/*
* A single-entry cache for some base-10 double-to-string conversions. This
* helps date-format-xparb.js. It also avoids skewing the results for
* v8-splay.js when measured by the SunSpider harness, where the splay tree
* initialization (which includes many repeated double-to-string conversions)
* is erroneously included in the measurement; see bug 562553.
*/
class DtoaCache {
double d;
int base;
JSFlatString* s; // if s==nullptr, d and base are not valid
public:
DtoaCache() : s(nullptr) {}
void purge() { s = nullptr; }
JSFlatString* lookup(int base, double d) {
return this->s && base == this->base && d == this->d ? this->s : nullptr;
}
void cache(int base, double d, JSFlatString* s) {
this->base = base;
this->d = d;
this->s = s;
}
#ifdef JSGC_HASH_TABLE_CHECKS
void checkCacheAfterMovingGC() { MOZ_ASSERT(!s || !IsForwarded(s)); }
#endif
};
class CrossCompartmentKey
{
public:
enum DebuggerObjectKind : uint8_t { DebuggerSource, DebuggerEnvironment, DebuggerObject,
DebuggerWasmScript, DebuggerWasmSource };
using DebuggerAndObject = mozilla::Tuple<NativeObject*, JSObject*, DebuggerObjectKind>;
using DebuggerAndScript = mozilla::Tuple<NativeObject*, JSScript*>;
using WrappedType = mozilla::Variant<
JSObject*,
JSString*,
DebuggerAndScript,
DebuggerAndObject>;
explicit CrossCompartmentKey(JSObject* obj) : wrapped(obj) { MOZ_RELEASE_ASSERT(obj); }
explicit CrossCompartmentKey(JSString* str) : wrapped(str) { MOZ_RELEASE_ASSERT(str); }
explicit CrossCompartmentKey(const JS::Value& v)
: wrapped(v.isString() ? WrappedType(v.toString()) : WrappedType(&v.toObject()))
{}
explicit CrossCompartmentKey(NativeObject* debugger, JSObject* obj, DebuggerObjectKind kind)
: wrapped(DebuggerAndObject(debugger, obj, kind))
{
MOZ_RELEASE_ASSERT(debugger);
MOZ_RELEASE_ASSERT(obj);
}
explicit CrossCompartmentKey(NativeObject* debugger, JSScript* script)
: wrapped(DebuggerAndScript(debugger, script))
{
MOZ_RELEASE_ASSERT(debugger);
MOZ_RELEASE_ASSERT(script);
}
bool operator==(const CrossCompartmentKey& other) const { return wrapped == other.wrapped; }
bool operator!=(const CrossCompartmentKey& other) const { return wrapped != other.wrapped; }
template <typename T> bool is() const { return wrapped.is<T>(); }
template <typename T> const T& as() const { return wrapped.as<T>(); }
template <typename F>
auto applyToWrapped(F f) -> decltype(f(static_cast<JSObject**>(nullptr))) {
using ReturnType = decltype(f(static_cast<JSObject**>(nullptr)));
struct WrappedMatcher {
F f_;
explicit WrappedMatcher(F f) : f_(f) {}
ReturnType match(JSObject*& obj) { return f_(&obj); }
ReturnType match(JSString*& str) { return f_(&str); }
ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<1>(tpl)); }
ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<1>(tpl)); }
} matcher(f);
return wrapped.match(matcher);
}
template <typename F>
auto applyToDebugger(F f) -> decltype(f(static_cast<NativeObject**>(nullptr))) {
using ReturnType = decltype(f(static_cast<NativeObject**>(nullptr)));
struct DebuggerMatcher {
F f_;
explicit DebuggerMatcher(F f) : f_(f) {}
ReturnType match(JSObject*& obj) { return ReturnType(); }
ReturnType match(JSString*& str) { return ReturnType(); }
ReturnType match(DebuggerAndScript& tpl) { return f_(&mozilla::Get<0>(tpl)); }
ReturnType match(DebuggerAndObject& tpl) { return f_(&mozilla::Get<0>(tpl)); }
} matcher(f);
return wrapped.match(matcher);
}
// Valid for JSObject* and Debugger keys. Crashes immediately if used on a
// JSString* key.
JSCompartment* compartment() {
struct GetCompartmentFunctor {
JSCompartment* operator()(JSObject** tp) const { return (*tp)->compartment(); }
JSCompartment* operator()(JSScript** tp) const { return (*tp)->compartment(); }
JSCompartment* operator()(JSString** tp) const {
MOZ_CRASH("invalid ccw key"); return nullptr;
}
};
return applyToWrapped(GetCompartmentFunctor());
}
struct Hasher : public DefaultHasher<CrossCompartmentKey>
{
struct HashFunctor {
HashNumber match(JSObject* obj) { return DefaultHasher<JSObject*>::hash(obj); }
HashNumber match(JSString* str) { return DefaultHasher<JSString*>::hash(str); }
HashNumber match(const DebuggerAndScript& tpl) {
return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
DefaultHasher<JSScript*>::hash(mozilla::Get<1>(tpl));
}
HashNumber match(const DebuggerAndObject& tpl) {
return DefaultHasher<NativeObject*>::hash(mozilla::Get<0>(tpl)) ^
DefaultHasher<JSObject*>::hash(mozilla::Get<1>(tpl)) ^
(mozilla::Get<2>(tpl) << 5);
}
};
static HashNumber hash(const CrossCompartmentKey& key) {
return key.wrapped.match(HashFunctor());
}
static bool match(const CrossCompartmentKey& l, const CrossCompartmentKey& k) {
return l.wrapped == k.wrapped;
}
};
bool isTenured() const {
struct IsTenuredFunctor {
using ReturnType = bool;
ReturnType operator()(JSObject** tp) { return !IsInsideNursery(*tp); }
ReturnType operator()(JSScript** tp) { return true; }
ReturnType operator()(JSString** tp) { return true; }
};
return const_cast<CrossCompartmentKey*>(this)->applyToWrapped(IsTenuredFunctor());
}
void trace(JSTracer* trc);
bool needsSweep();
private:
CrossCompartmentKey() = delete;
WrappedType wrapped;
};
using WrapperMap = NurseryAwareHashMap<CrossCompartmentKey, JS::Value,
CrossCompartmentKey::Hasher, SystemAllocPolicy>;
// We must ensure that all newly allocated JSObjects get their metadata
// set. However, metadata builders may require the new object be in a sane
// state (eg, have its reserved slots initialized so they can get the
// sizeOfExcludingThis of the object). Therefore, for objects of certain
// JSClasses (those marked with JSCLASS_DELAY_METADATA_BUILDER), it is not safe
// for the allocation paths to call the object metadata builder
// immediately. Instead, the JSClass-specific "constructor" C++ function up the
// stack makes a promise that it will ensure that the new object has its
// metadata set after the object is initialized.
//
// To help those constructor functions keep their promise of setting metadata,
// each compartment is in one of three states at any given time:
//
// * ImmediateMetadata: Allocators should set new object metadata immediately,
// as usual.
//
// * DelayMetadata: Allocators should *not* set new object metadata, it will be
// handled after reserved slots are initialized by custom code
// for the object's JSClass. The newly allocated object's
// JSClass *must* have the JSCLASS_DELAY_METADATA_BUILDER flag
// set.
//
// * PendingMetadata: This object has been allocated and is still pending its
// metadata. This should never be the case when we begin an
// allocation, as a constructor function was supposed to have
// set the metadata of the previous object *before*
// allocating another object.
//
// The js::AutoSetNewObjectMetadata RAII class provides an ergonomic way for
// constructor functions to navigate state transitions, and its instances
// collectively maintain a stack of previous states. The stack is required to
// support the lazy resolution and allocation of global builtin constructors and
// prototype objects. The initial (and intuitively most common) state is
// ImmediateMetadata.
//
// Without the presence of internal errors (such as OOM), transitions between
// the states are as follows:
//
// ImmediateMetadata .----- previous state on stack
// | | ^
// | via constructor | |
// | | | via setting the new
// | via constructor | | object's metadata
// | .-----------------------' |
// | | |
// V V |
// DelayMetadata -------------------------> PendingMetadata
// via allocation
//
// In the presence of internal errors, we do not set the new object's metadata
// (if it was even allocated) and reset to the previous state on the stack.
struct ImmediateMetadata { };
struct DelayMetadata { };
using PendingMetadata = JSObject*;
using NewObjectMetadataState = mozilla::Variant<ImmediateMetadata,
DelayMetadata,
PendingMetadata>;
class MOZ_RAII AutoSetNewObjectMetadata : private JS::CustomAutoRooter
{
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER;
JSContext* cx_;
NewObjectMetadataState prevState_;
AutoSetNewObjectMetadata(const AutoSetNewObjectMetadata& aOther) = delete;
void operator=(const AutoSetNewObjectMetadata& aOther) = delete;
protected:
virtual void trace(JSTracer* trc) override {
if (prevState_.is<PendingMetadata>()) {
TraceRoot(trc,
&prevState_.as<PendingMetadata>(),
"Object pending metadata");
}
}
public:
explicit AutoSetNewObjectMetadata(ExclusiveContext* ecx MOZ_GUARD_OBJECT_NOTIFIER_PARAM);
~AutoSetNewObjectMetadata();
};
} /* namespace js */
namespace js {
class DebugEnvironments;
class ObjectWeakMap;
class WeakMapBase;
} // namespace js
struct JSCompartment
{
const JS::CompartmentCreationOptions creationOptions_;
JS::CompartmentBehaviors behaviors_;
private:
JS::Zone* zone_;
JSRuntime* runtime_;
public:
/*
* The principals associated with this compartment. Note that the
* same several compartments may share the same principals and
* that a compartment may change principals during its lifetime
* (e.g. in case of lazy parsing).
*/
inline JSPrincipals* principals() {
return principals_;
}
inline void setPrincipals(JSPrincipals* principals) {
if (principals_ == principals)
return;
// If we change principals, we need to unlink immediately this
// compartment from its PerformanceGroup. For one thing, the
// performance data we collect should not be improperly associated
// with a group to which we do not belong anymore. For another thing,
// we use `principals()` as part of the key to map compartments
// to a `PerformanceGroup`, so if we do not unlink now, this will
// be too late once we have updated `principals_`.
performanceMonitoring.unlink();
principals_ = principals;
}
inline bool isSystem() const {
return isSystem_;
}
inline void setIsSystem(bool isSystem) {
if (isSystem_ == isSystem)
return;
// If we change `isSystem*(`, we need to unlink immediately this
// compartment from its PerformanceGroup. For one thing, the
// performance data we collect should not be improperly associated
// to a group to which we do not belong anymore. For another thing,
// we use `isSystem()` as part of the key to map compartments
// to a `PerformanceGroup`, so if we do not unlink now, this will
// be too late once we have updated `isSystem_`.
performanceMonitoring.unlink();
isSystem_ = isSystem;
}
bool isAtomsCompartment() const {
return isAtomsCompartment_;
}
void setIsAtomsCompartment() {
isAtomsCompartment_ = true;
}
private:
JSPrincipals* principals_;
bool isSystem_;
bool isAtomsCompartment_;
public:
bool isSelfHosting;
bool marked;
bool warnedAboutExprClosure;
bool warnedAboutForEach;
#ifdef DEBUG
bool firedOnNewGlobalObject;
#endif
void mark() { marked = true; }
private:
friend struct JSRuntime;
friend struct JSContext;
friend class js::ExclusiveContext;
js::ReadBarrieredGlobalObject global_;
unsigned enterCompartmentDepth;
int64_t startInterval;
public:
js::PerformanceGroupHolder performanceMonitoring;
void enter() {
enterCompartmentDepth++;
}
void leave() {
enterCompartmentDepth--;
}
bool hasBeenEntered() { return !!enterCompartmentDepth; }
JS::Zone* zone() { return zone_; }
const JS::Zone* zone() const { return zone_; }
const JS::CompartmentCreationOptions& creationOptions() const { return creationOptions_; }
JS::CompartmentBehaviors& behaviors() { return behaviors_; }
const JS::CompartmentBehaviors& behaviors() const { return behaviors_; }
JSRuntime* runtimeFromMainThread() const {
MOZ_ASSERT(CurrentThreadCanAccessRuntime(runtime_));
return runtime_;
}
// Note: Unrestricted access to the zone's runtime from an arbitrary
// thread can easily lead to races. Use this method very carefully.
JSRuntime* runtimeFromAnyThread() const {
return runtime_;
}
JSContext* contextFromMainThread() const {
return runtime_->contextFromMainThread();
}
/*
* Nb: global_ might be nullptr, if (a) it's the atoms compartment, or
* (b) the compartment's global has been collected. The latter can happen
* if e.g. a string in a compartment is rooted but no object is, and thus
* the global isn't rooted, and thus the global can be finalized while the
* compartment lives on.
*
* In contrast, JSObject::global() is infallible because marking a JSObject
* always marks its global as well.
* TODO: add infallible JSScript::global()
*/
inline js::GlobalObject* maybeGlobal() const;
/* An unbarriered getter for use while tracing. */
inline js::GlobalObject* unsafeUnbarrieredMaybeGlobal() const;
inline void initGlobal(js::GlobalObject& global);
public:
void* data;
private:
const js::AllocationMetadataBuilder *allocationMetadataBuilder;
js::SavedStacks savedStacks_;
js::WrapperMap crossCompartmentWrappers;
using CCKeyVector = mozilla::Vector<js::CrossCompartmentKey, 0, js::SystemAllocPolicy>;
CCKeyVector nurseryCCKeys;
// The global environment record's [[VarNames]] list that contains all
// names declared using FunctionDeclaration, GeneratorDeclaration, and
// VariableDeclaration declarations in global code in this compartment.
// Names are only removed from this list by a |delete IdentifierReference|
// that successfully removes that global property.
JS::GCHashSet<JSAtom*,
js::DefaultHasher<JSAtom*>,
js::SystemAllocPolicy> varNames_;
public:
/* Last time at which an animation was played for a global in this compartment. */
int64_t lastAnimationTime;
js::RegExpCompartment regExps;
/*
* For generational GC, record whether a write barrier has added this
* compartment's global to the store buffer since the last minor GC.
*
* This is used to avoid calling into the VM every time a nursery object is
* written to a property of the global.
*/
uint32_t globalWriteBarriered;
// Non-zero if the storage underlying any typed object in this compartment
// might be detached.
int32_t detachedTypedObjects;
private:
friend class js::AutoSetNewObjectMetadata;
js::NewObjectMetadataState objectMetadataState;
public:
// Recompute the probability with which this compartment should record
// profiling data (stack traces, allocations log, etc.) about each
// allocation. We consult the probabilities requested by the Debugger
// instances observing us, if any.
void chooseAllocationSamplingProbability() { savedStacks_.chooseSamplingProbability(this); }
bool hasObjectPendingMetadata() const { return objectMetadataState.is<js::PendingMetadata>(); }
void setObjectPendingMetadata(JSContext* cx, JSObject* obj) {
MOZ_ASSERT(objectMetadataState.is<js::DelayMetadata>());
objectMetadataState = js::NewObjectMetadataState(js::PendingMetadata(obj));
}
void setObjectPendingMetadata(js::ExclusiveContext* ecx, JSObject* obj) {
if (JSContext* cx = ecx->maybeJSContext())
setObjectPendingMetadata(cx, obj);
}
public:
void addSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf,
size_t* tiAllocationSiteTables,
size_t* tiArrayTypeTables,
size_t* tiObjectTypeTables,
size_t* compartmentObject,
size_t* compartmentTables,
size_t* innerViews,
size_t* lazyArrayBuffers,
size_t* objectMetadataTables,
size_t* crossCompartmentWrappers,
size_t* regexpCompartment,
size_t* savedStacksSet,
size_t* varNamesSet,
size_t* nonSyntacticLexicalScopes,
size_t* jitCompartment,
size_t* privateData);
// Object group tables and other state in the compartment.
js::ObjectGroupCompartment objectGroups;
#ifdef JSGC_HASH_TABLE_CHECKS
void checkWrapperMapAfterMovingGC();
void checkScriptMapsAfterMovingGC();
#endif
/*
* Lazily initialized script source object to use for scripts cloned
* from the self-hosting global.
*/
js::ReadBarrieredScriptSourceObject selfHostingScriptSource;
// Keep track of the metadata objects which can be associated with each JS
// object. Both keys and values are in this compartment.
js::ObjectWeakMap* objectMetadataTable;
// Map from array buffers to views sharing that storage.
JS::WeakCache<js::InnerViewTable> innerViews;
// Inline transparent typed objects do not initially have an array buffer,
// but can have that buffer created lazily if it is accessed later. This
// table manages references from such typed objects to their buffers.
js::ObjectWeakMap* lazyArrayBuffers;
// All unboxed layouts in the compartment.
mozilla::LinkedList<js::UnboxedLayout> unboxedLayouts;
// WebAssembly state for the compartment.
js::wasm::Compartment wasm;
private:
// All non-syntactic lexical environments in the compartment. These are kept in
// a map because when loading scripts into a non-syntactic environment, we need
// to use the same lexical environment to persist lexical bindings.
js::ObjectWeakMap* nonSyntacticLexicalEnvironments_;
public:
/* During GC, stores the index of this compartment in rt->compartments. */
unsigned gcIndex;
/*
* During GC, stores the head of a list of incoming pointers from gray cells.
*
* The objects in the list are either cross-compartment wrappers, or
* debugger wrapper objects. The list link is either in the second extra
* slot for the former, or a special slot for the latter.
*/
JSObject* gcIncomingGrayPointers;
private:
enum {
IsDebuggee = 1 << 0,
DebuggerObservesAllExecution = 1 << 1,
DebuggerObservesAsmJS = 1 << 2,
DebuggerObservesCoverage = 1 << 3,
DebuggerNeedsDelazification = 1 << 4
};
unsigned debugModeBits;
friend class AutoRestoreCompartmentDebugMode;
static const unsigned DebuggerObservesMask = IsDebuggee |
DebuggerObservesAllExecution |
DebuggerObservesCoverage |
DebuggerObservesAsmJS;
void updateDebuggerObservesFlag(unsigned flag);
bool getNonWrapperObjectForCurrentCompartment(JSContext* cx, js::MutableHandleObject obj);
bool getOrCreateWrapper(JSContext* cx, js::HandleObject existing, js::MutableHandleObject obj);
public:
JSCompartment(JS::Zone* zone, const JS::CompartmentOptions& options);
~JSCompartment();
MOZ_MUST_USE bool init(JSContext* maybecx);
MOZ_MUST_USE inline bool wrap(JSContext* cx, JS::MutableHandleValue vp);
MOZ_MUST_USE bool wrap(JSContext* cx, js::MutableHandleString strp);
MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandleObject obj);
MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<js::PropertyDescriptor> desc);
MOZ_MUST_USE bool wrap(JSContext* cx, JS::MutableHandle<JS::GCVector<JS::Value>> vec);
MOZ_MUST_USE bool rewrap(JSContext* cx, JS::MutableHandleObject obj, JS::HandleObject existing);
MOZ_MUST_USE bool putWrapper(JSContext* cx, const js::CrossCompartmentKey& wrapped,
const js::Value& wrapper);
js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) const {
return crossCompartmentWrappers.lookup(js::CrossCompartmentKey(wrapped));
}
void removeWrapper(js::WrapperMap::Ptr p) {
crossCompartmentWrappers.remove(p);
}
struct WrapperEnum : public js::WrapperMap::Enum {
explicit WrapperEnum(JSCompartment* c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {}
};
js::LexicalEnvironmentObject*
getOrCreateNonSyntacticLexicalEnvironment(JSContext* cx, js::HandleObject enclosing);
js::LexicalEnvironmentObject* getNonSyntacticLexicalEnvironment(JSObject* enclosing) const;
/*
* This method traces data that is live iff we know that this compartment's
* global is still live.
*/
void trace(JSTracer* trc);
/*
* This method traces JSCompartment-owned GC roots that are considered live
* regardless of whether the JSCompartment itself is still live.
*/
void traceRoots(JSTracer* trc, js::gc::GCRuntime::TraceOrMarkRuntime traceOrMark);
/*
* This method clears out tables of roots in preparation for the final GC.
*/
void finishRoots();
/*
* These methods mark pointers that cross compartment boundaries. They are
* called in per-zone GCs to prevent the wrappers' outgoing edges from
* dangling (full GCs naturally follow pointers across compartments) and
* when compacting to update cross-compartment pointers.
*/
void traceOutgoingCrossCompartmentWrappers(JSTracer* trc);
static void traceIncomingCrossCompartmentEdgesForZoneGC(JSTracer* trc);
/* Whether to preserve JIT code on non-shrinking GCs. */
bool preserveJitCode() { return creationOptions_.preserveJitCode(); }
void sweepAfterMinorGC(JSTracer* trc);
void sweepCrossCompartmentWrappers();
void sweepSavedStacks();
void sweepGlobalObject(js::FreeOp* fop);
void sweepSelfHostingScriptSource();
void sweepJitCompartment(js::FreeOp* fop);
void sweepRegExps();
void sweepDebugEnvironments();
void sweepNativeIterators();
void sweepTemplateObjects();
void sweepVarNames();
void purge();
void clearTables();
static void fixupCrossCompartmentWrappersAfterMovingGC(JSTracer* trc);
void fixupAfterMovingGC();
void fixupGlobal();
void fixupScriptMapsAfterMovingGC();
bool hasAllocationMetadataBuilder() const { return allocationMetadataBuilder; }
const js::AllocationMetadataBuilder* getAllocationMetadataBuilder() const {
return allocationMetadataBuilder;
}
void setAllocationMetadataBuilder(const js::AllocationMetadataBuilder* builder);
void forgetAllocationMetadataBuilder() {
allocationMetadataBuilder = nullptr;
}
void setNewObjectMetadata(JSContext* cx, JS::HandleObject obj);
void clearObjectMetadata();
const void* addressOfMetadataBuilder() const {
return &allocationMetadataBuilder;
}
js::SavedStacks& savedStacks() { return savedStacks_; }
// Add a name to [[VarNames]]. Reports OOM on failure.
MOZ_MUST_USE bool addToVarNames(JSContext* cx, JS::Handle<JSAtom*> name);
void removeFromVarNames(JS::Handle<JSAtom*> name) {
varNames_.remove(name);
}
// Whether the given name is in [[VarNames]].
bool isInVarNames(JS::Handle<JSAtom*> name) {
return varNames_.has(name);
}
void findOutgoingEdges(js::gc::ZoneComponentFinder& finder);
js::DtoaCache dtoaCache;
// Random number generator for Math.random().
mozilla::Maybe<mozilla::non_crypto::XorShift128PlusRNG> randomNumberGenerator;
// Initialize randomNumberGenerator if needed.
void ensureRandomNumberGenerator();
private:
mozilla::non_crypto::XorShift128PlusRNG randomKeyGenerator_;
public:
js::HashNumber randomHashCode();
mozilla::HashCodeScrambler randomHashCodeScrambler();
static size_t offsetOfRegExps() {
return offsetof(JSCompartment, regExps);
}
private:
JSCompartment* thisForCtor() { return this; }
public:
//
// The Debugger observes execution on a frame-by-frame basis. The
// invariants of JSCompartment's debug mode bits, JSScript::isDebuggee,
// InterpreterFrame::isDebuggee, and BaselineFrame::isDebuggee are
// enumerated below.
//
// 1. When a compartment's isDebuggee() == true, relazification and lazy
// parsing are disabled.
//
// Whether AOT wasm is disabled is togglable by the Debugger API. By
// default it is disabled. See debuggerObservesAsmJS below.
//
// 2. When a compartment's debuggerObservesAllExecution() == true, all of
// the compartment's scripts are considered debuggee scripts.
//
// 3. A script is considered a debuggee script either when, per above, its
// compartment is observing all execution, or if it has breakpoints set.
//
// 4. A debuggee script always pushes a debuggee frame.
//
// 5. A debuggee frame calls all slow path Debugger hooks in the
// Interpreter and Baseline. A debuggee frame implies that its script's
// BaselineScript, if extant, has been compiled with debug hook calls.
//
// 6. A debuggee script or a debuggee frame (i.e., during OSR) ensures
// that the compiled BaselineScript is compiled with debug hook calls
// when attempting to enter Baseline.
//
// 7. A debuggee script or a debuggee frame (i.e., during OSR) does not
// attempt to enter Ion.
//
// Note that a debuggee frame may exist without its script being a
// debuggee script. e.g., Debugger.Frame.prototype.eval only marks the
// frame in which it is evaluating as a debuggee frame.
//
// True if this compartment's global is a debuggee of some Debugger
// object.
bool isDebuggee() const { return !!(debugModeBits & IsDebuggee); }
void setIsDebuggee() { debugModeBits |= IsDebuggee; }
void unsetIsDebuggee();
// True if this compartment's global is a debuggee of some Debugger
// object with a live hook that observes all execution; e.g.,
// onEnterFrame.
bool debuggerObservesAllExecution() const {
static const unsigned Mask = IsDebuggee | DebuggerObservesAllExecution;
return (debugModeBits & Mask) == Mask;
}
void updateDebuggerObservesAllExecution() {
updateDebuggerObservesFlag(DebuggerObservesAllExecution);
}
// True if this compartment's global is a debuggee of some Debugger object
// whose allowUnobservedAsmJS flag is false.
//
// Note that since AOT wasm functions cannot bail out, this flag really
// means "observe wasm from this point forward". We cannot make
// already-compiled wasm code observable to Debugger.
bool debuggerObservesAsmJS() const {
static const unsigned Mask = IsDebuggee | DebuggerObservesAsmJS;
return (debugModeBits & Mask) == Mask;
}
void updateDebuggerObservesAsmJS() {
updateDebuggerObservesFlag(DebuggerObservesAsmJS);
}
// True if this compartment's global is a debuggee of some Debugger object
// whose collectCoverageInfo flag is true.
bool debuggerObservesCoverage() const {
static const unsigned Mask = DebuggerObservesCoverage;
return (debugModeBits & Mask) == Mask;
}
void updateDebuggerObservesCoverage();
// The code coverage can be enabled either for each compartment, with the
// Debugger API, or for the entire runtime.
bool collectCoverage() const;
bool collectCoverageForDebug() const;
bool collectCoverageForPGO() const;
void clearScriptCounts();
bool needsDelazificationForDebugger() const {
return debugModeBits & DebuggerNeedsDelazification;
}
/*
* Schedule the compartment to be delazified. Called from
* LazyScript::Create.
*/
void scheduleDelazificationForDebugger() { debugModeBits |= DebuggerNeedsDelazification; }
/*
* If we scheduled delazification for turning on debug mode, delazify all
* scripts.
*/
bool ensureDelazifyScriptsForDebugger(JSContext* cx);
void clearBreakpointsIn(js::FreeOp* fop, js::Debugger* dbg, JS::HandleObject handler);
private:
void sweepBreakpoints(js::FreeOp* fop);
public:
js::ScriptCountsMap* scriptCountsMap;
js::DebugScriptMap* debugScriptMap;
/* Bookkeeping information for debug scope objects. */
js::DebugEnvironments* debugEnvs;
/*
* List of potentially active iterators that may need deleted property
* suppression.
*/
js::NativeIterator* enumerators;
/* Native iterator most recently started. */
js::PropertyIteratorObject* lastCachedNativeIterator;
private:
/* Used by memory reporters and invalid otherwise. */
JS::CompartmentStats* compartmentStats_;
public:
// This should only be called when it is non-null, i.e. during memory
// reporting.
JS::CompartmentStats& compartmentStats() {
// We use MOZ_RELEASE_ASSERT here because in bug 1132502 there was some
// (inconclusive) evidence that compartmentStats_ can be nullptr
// unexpectedly.
MOZ_RELEASE_ASSERT(compartmentStats_);
return *compartmentStats_;
}
void nullCompartmentStats() {
MOZ_ASSERT(compartmentStats_);
compartmentStats_ = nullptr;
}
void setCompartmentStats(JS::CompartmentStats* newStats) {
MOZ_ASSERT(!compartmentStats_ && newStats);
compartmentStats_ = newStats;
}
// These flags help us to discover if a compartment that shouldn't be alive
// manages to outlive a GC.
bool scheduledForDestruction;
bool maybeAlive;
private:
js::jit::JitCompartment* jitCompartment_;
js::ReadBarriered<js::ArgumentsObject*> mappedArgumentsTemplate_;
js::ReadBarriered<js::ArgumentsObject*> unmappedArgumentsTemplate_;
public:
bool ensureJitCompartmentExists(JSContext* cx);
js::jit::JitCompartment* jitCompartment() {
return jitCompartment_;
}
js::ArgumentsObject* getOrCreateArgumentsTemplateObject(JSContext* cx, bool mapped);
js::ArgumentsObject* maybeArgumentsTemplateObject(bool mapped) const;
public:
// Aggregated output used to collect JSScript hit counts when code coverage
// is enabled.
js::coverage::LCovCompartment lcovOutput;
};
inline bool
JSRuntime::isAtomsZone(const JS::Zone* zone) const
{
return zone == atomsCompartment_->zone();
}
namespace js {
// We only set the maybeAlive flag for objects and scripts. It's assumed that,
// if a compartment is alive, then it will have at least some live object or
// script it in. Even if we get this wrong, the worst that will happen is that
// scheduledForDestruction will be set on the compartment, which will cause
// some extra GC activity to try to free the compartment.
template<typename T> inline void SetMaybeAliveFlag(T* thing) {}
template<> inline void SetMaybeAliveFlag(JSObject* thing) {thing->compartment()->maybeAlive = true;}
template<> inline void SetMaybeAliveFlag(JSScript* thing) {thing->compartment()->maybeAlive = true;}
inline js::Handle<js::GlobalObject*>
ExclusiveContext::global() const
{
/*
* It's safe to use |unsafeGet()| here because any compartment that is
* on-stack will be marked automatically, so there's no need for a read
* barrier on it. Once the compartment is popped, the handle is no longer
* safe to use.
*/
MOZ_ASSERT(compartment_, "Caller needs to enter a compartment first");
return Handle<GlobalObject*>::fromMarkedLocation(compartment_->global_.unsafeGet());
}
class MOZ_RAII AssertCompartmentUnchanged
{
public:
explicit AssertCompartmentUnchanged(JSContext* cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: cx(cx), oldCompartment(cx->compartment())
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
~AssertCompartmentUnchanged() {
MOZ_ASSERT(cx->compartment() == oldCompartment);
}
protected:
JSContext * const cx;
JSCompartment * const oldCompartment;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class AutoCompartment
{
ExclusiveContext * const cx_;
JSCompartment * const origin_;
const js::AutoLockForExclusiveAccess* maybeLock_;
public:
inline AutoCompartment(ExclusiveContext* cx, JSObject* target,
js::AutoLockForExclusiveAccess* maybeLock = nullptr);
inline AutoCompartment(ExclusiveContext* cx, JSCompartment* target,
js::AutoLockForExclusiveAccess* maybeLock = nullptr);
inline ~AutoCompartment();
ExclusiveContext* context() const { return cx_; }
JSCompartment* origin() const { return origin_; }
private:
AutoCompartment(const AutoCompartment&) = delete;
AutoCompartment & operator=(const AutoCompartment&) = delete;
};
/*
* Use this to change the behavior of an AutoCompartment slightly on error. If
* the exception happens to be an Error object, copy it to the origin compartment
* instead of wrapping it.
*/
class ErrorCopier
{
mozilla::Maybe<AutoCompartment>& ac;
public:
explicit ErrorCopier(mozilla::Maybe<AutoCompartment>& ac)
: ac(ac) {}
~ErrorCopier();
};
/*
* AutoWrapperVector and AutoWrapperRooter can be used to store wrappers that
* are obtained from the cross-compartment map. However, these classes should
* not be used if the wrapper will escape. For example, it should not be stored
* in the heap.
*
* The AutoWrapper rooters are different from other autorooters because their
* wrappers are marked on every GC slice rather than just the first one. If
* there's some wrapper that we want to use temporarily without causing it to be
* marked, we can use these AutoWrapper classes. If we get unlucky and a GC
* slice runs during the code using the wrapper, the GC will mark the wrapper so
* that it doesn't get swept out from under us. Otherwise, the wrapper needn't
* be marked. This is useful in functions like JS_TransplantObject that
* manipulate wrappers in compartments that may no longer be alive.
*/
/*
* This class stores the data for AutoWrapperVector and AutoWrapperRooter. It
* should not be used in any other situations.
*/
struct WrapperValue
{
/*
* We use unsafeGet() in the constructors to avoid invoking a read barrier
* on the wrapper, which may be dead (see the comment about bug 803376 in
* jsgc.cpp regarding this). If there is an incremental GC while the wrapper
* is in use, the AutoWrapper rooter will ensure the wrapper gets marked.
*/
explicit WrapperValue(const WrapperMap::Ptr& ptr)
: value(*ptr->value().unsafeGet())
{}
explicit WrapperValue(const WrapperMap::Enum& e)
: value(*e.front().value().unsafeGet())
{}
Value& get() { return value; }
Value get() const { return value; }
operator const Value&() const { return value; }
JSObject& toObject() const { return value.toObject(); }
private:
Value value;
};
class MOZ_RAII AutoWrapperVector : public JS::AutoVectorRooterBase<WrapperValue>
{
public:
explicit AutoWrapperVector(JSContext* cx
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: AutoVectorRooterBase<WrapperValue>(cx, WRAPVECTOR)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class MOZ_RAII AutoWrapperRooter : private JS::AutoGCRooter {
public:
AutoWrapperRooter(JSContext* cx, const WrapperValue& v
MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
: JS::AutoGCRooter(cx, WRAPPER), value(v)
{
MOZ_GUARD_OBJECT_NOTIFIER_INIT;
}
operator JSObject*() const {
return value.get().toObjectOrNull();
}
friend void JS::AutoGCRooter::trace(JSTracer* trc);
private:
WrapperValue value;
MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
};
class MOZ_RAII AutoSuppressAllocationMetadataBuilder {
JS::Zone* zone;
bool saved;
public:
explicit AutoSuppressAllocationMetadataBuilder(ExclusiveContext* cx)
: AutoSuppressAllocationMetadataBuilder(cx->compartment()->zone())
{ }
explicit AutoSuppressAllocationMetadataBuilder(JS::Zone* zone)
: zone(zone),
saved(zone->suppressAllocationMetadataBuilder)
{
zone->suppressAllocationMetadataBuilder = true;
}
~AutoSuppressAllocationMetadataBuilder() {
zone->suppressAllocationMetadataBuilder = saved;
}
};
} /* namespace js */
namespace JS {
template <>
struct GCPolicy<js::CrossCompartmentKey> : public StructGCPolicy<js::CrossCompartmentKey> {
static bool isTenured(const js::CrossCompartmentKey& key) { return key.isTenured(); }
};
} // namespace JS
#endif /* jscompartment_h */