/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- * vim: set ts=8 sts=4 et sw=4 tw=99: * 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/Util.h" #include "jscntxt.h" #include "jsgc.h" #include "jsobj.h" #include "gc/Zone.h" #include "vm/GlobalObject.h" #include "vm/RegExpObject.h" #include "vm/Shape.h" namespace js { namespace jit { class IonCompartment; } 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==NULL, d and base are not valid public: DtoaCache() : s(NULL) {} void purge() { s = NULL; } JSFlatString *lookup(int base, double d) { return this->s && base == this->base && d == this->d ? this->s : NULL; } void cache(int base, double d, JSFlatString *s) { this->base = base; this->d = d; this->s = s; } }; /* If HashNumber grows, need to change WrapperHasher. */ JS_STATIC_ASSERT(sizeof(HashNumber) == 4); struct CrossCompartmentKey { enum Kind { ObjectWrapper, StringWrapper, DebuggerScript, DebuggerSource, DebuggerObject, DebuggerEnvironment }; Kind kind; JSObject *debugger; js::gc::Cell *wrapped; CrossCompartmentKey() : kind(ObjectWrapper), debugger(NULL), wrapped(NULL) {} CrossCompartmentKey(JSObject *wrapped) : kind(ObjectWrapper), debugger(NULL), wrapped(wrapped) {} CrossCompartmentKey(JSString *wrapped) : kind(StringWrapper), debugger(NULL), wrapped(wrapped) {} CrossCompartmentKey(Value wrapped) : kind(wrapped.isString() ? StringWrapper : ObjectWrapper), debugger(NULL), wrapped((js::gc::Cell *)wrapped.toGCThing()) {} CrossCompartmentKey(const RootedValue &wrapped) : kind(wrapped.get().isString() ? StringWrapper : ObjectWrapper), debugger(NULL), wrapped((js::gc::Cell *)wrapped.get().toGCThing()) {} CrossCompartmentKey(Kind kind, JSObject *dbg, js::gc::Cell *wrapped) : kind(kind), debugger(dbg), wrapped(wrapped) {} }; struct WrapperHasher { typedef CrossCompartmentKey Lookup; static HashNumber hash(const CrossCompartmentKey &key) { JS_ASSERT(!IsPoisonedPtr(key.wrapped)); return uint32_t(uintptr_t(key.wrapped)) | uint32_t(key.kind); } static bool match(const CrossCompartmentKey &l, const CrossCompartmentKey &k) { return l.kind == k.kind && l.debugger == k.debugger && l.wrapped == k.wrapped; } }; typedef HashMap WrapperMap; } /* namespace js */ namespace JS { struct TypeInferenceSizes; } namespace js { class AutoDebugModeGC; class DebugScopes; } struct JSCompartment { JS::Zone *zone_; JS::CompartmentOptions options_; JSRuntime *rt; JSPrincipals *principals; bool isSystem; bool marked; void mark() { marked = true; } private: friend struct JSRuntime; friend struct JSContext; js::ReadBarriered global_; unsigned enterCompartmentDepth; public: void enter() { enterCompartmentDepth++; } void leave() { enterCompartmentDepth--; } bool hasBeenEntered() { return !!enterCompartmentDepth; } JS::Zone *zone() { return zone_; } const JS::Zone *zone() const { return zone_; } JS::CompartmentOptions &options() { return options_; } const JS::CompartmentOptions &options() const { return options_; } /* * Nb: global_ might be NULL, 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; inline void initGlobal(js::GlobalObject &global); public: /* * Moves all data from the allocator |workerAllocator|, which was * in use by a parallel worker, into the compartment's main * allocator. This is used at the end of a parallel section. */ void adoptWorkerAllocator(js::Allocator *workerAllocator); int64_t lastCodeRelease; /* Pools for analysis and type information in this compartment. */ static const size_t ANALYSIS_LIFO_ALLOC_PRIMARY_CHUNK_SIZE = 32 * 1024; js::LifoAlloc analysisLifoAlloc; bool activeAnalysis; /* Type information about the scripts and objects in this compartment. */ js::types::TypeCompartment types; void *data; js::ObjectMetadataCallback objectMetadataCallback; private: js::WrapperMap crossCompartmentWrappers; public: /* Last time at which an animation was played for a global in this compartment. */ int64_t lastAnimationTime; js::RegExpCompartment regExps; private: void sizeOfTypeInferenceData(JS::TypeInferenceSizes *stats, JSMallocSizeOfFun mallocSizeOf); public: void sizeOfIncludingThis(JSMallocSizeOfFun mallocSizeOf, size_t *compartmentObject, JS::TypeInferenceSizes *tiSizes, size_t *shapesCompartmentTables, size_t *crossCompartmentWrappers, size_t *regexpCompartment, size_t *debuggeesSet, size_t *baselineStubsOptimized); /* * Shared scope property tree, and arena-pool for allocating its nodes. */ js::PropertyTree propertyTree; /* Set of all unowned base shapes in the compartment. */ js::BaseShapeSet baseShapes; void sweepBaseShapeTable(); /* Set of initial shapes in the compartment. */ js::InitialShapeSet initialShapes; void sweepInitialShapeTable(); void markAllInitialShapeTableEntries(JSTracer *trc); /* Set of default 'new' or lazy types in the compartment. */ js::types::TypeObjectSet newTypeObjects; js::types::TypeObjectSet lazyTypeObjects; void sweepNewTypeObjectTable(js::types::TypeObjectSet &table); js::types::TypeObject *getNewType(JSContext *cx, js::Class *clasp, js::TaggedProto proto, JSFunction *fun = NULL); js::types::TypeObject *getLazyType(JSContext *cx, js::Class *clasp, js::TaggedProto proto); /* * Hash table of all manually call site-cloned functions from within * self-hosted code. Cloning according to call site provides extra * sensitivity for type specialization and inlining. */ js::CallsiteCloneTable callsiteClones; void sweepCallsiteClones(); /* 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; /* Linked list of live array buffers with >1 view. */ JSObject *gcLiveArrayBuffers; /* Linked list of live weakmaps in this compartment. */ js::WeakMapBase *gcWeakMapList; private: enum { DebugFromC = 1, DebugFromJS = 2 }; unsigned debugModeBits; // see debugMode() below public: JSCompartment(JS::Zone *zone, const JS::CompartmentOptions &options); ~JSCompartment(); bool init(JSContext *cx); /* Mark cross-compartment wrappers. */ void markCrossCompartmentWrappers(JSTracer *trc); void markAllCrossCompartmentWrappers(JSTracer *trc); bool wrap(JSContext *cx, JS::MutableHandleValue vp, JS::HandleObject existing = js::NullPtr()); bool wrap(JSContext *cx, JSString **strp); bool wrap(JSContext *cx, js::HeapPtrString *strp); bool wrap(JSContext *cx, JSObject **objp, JSObject *existing = NULL); bool wrapId(JSContext *cx, jsid *idp); bool wrap(JSContext *cx, js::PropertyOp *op); bool wrap(JSContext *cx, js::StrictPropertyOp *op); bool wrap(JSContext *cx, js::PropertyDescriptor *desc); bool wrap(JSContext *cx, js::AutoIdVector &props); bool putWrapper(const js::CrossCompartmentKey& wrapped, const js::Value& wrapper); js::WrapperMap::Ptr lookupWrapper(const js::Value& wrapped) { return crossCompartmentWrappers.lookup(wrapped); } void removeWrapper(js::WrapperMap::Ptr p) { crossCompartmentWrappers.remove(p); } struct WrapperEnum : public js::WrapperMap::Enum { WrapperEnum(JSCompartment *c) : js::WrapperMap::Enum(c->crossCompartmentWrappers) {} }; void mark(JSTracer *trc); bool isDiscardingJitCode(JSTracer *trc); void sweep(js::FreeOp *fop, bool releaseTypes); void sweepCrossCompartmentWrappers(); void purge(); void findOutgoingEdges(js::gc::ComponentFinder &finder); js::DtoaCache dtoaCache; /* Random number generator state, used by jsmath.cpp. */ uint64_t rngState; private: /* * Weak reference to each global in this compartment that is a debuggee. * Each global has its own list of debuggers. */ js::GlobalObjectSet debuggees; private: JSCompartment *thisForCtor() { return this; } public: /* * There are dueling APIs for debug mode. It can be enabled or disabled via * JS_SetDebugModeForCompartment. It is automatically enabled and disabled * by Debugger objects. Therefore debugModeBits has the DebugFromC bit set * if the C API wants debug mode and the DebugFromJS bit set if debuggees * is non-empty. */ bool debugMode() const { return !!debugModeBits; } /* True if any scripts from this compartment are on the JS stack. */ bool hasScriptsOnStack(); private: /* This is called only when debugMode() has just toggled. */ void updateForDebugMode(js::FreeOp *fop, js::AutoDebugModeGC &dmgc); public: js::GlobalObjectSet &getDebuggees() { return debuggees; } bool addDebuggee(JSContext *cx, js::GlobalObject *global); bool addDebuggee(JSContext *cx, js::GlobalObject *global, js::AutoDebugModeGC &dmgc); void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global, js::GlobalObjectSet::Enum *debuggeesEnum = NULL); void removeDebuggee(js::FreeOp *fop, js::GlobalObject *global, js::AutoDebugModeGC &dmgc, js::GlobalObjectSet::Enum *debuggeesEnum = NULL); bool setDebugModeFromC(JSContext *cx, bool b, js::AutoDebugModeGC &dmgc); void clearBreakpointsIn(js::FreeOp *fop, js::Debugger *dbg, JSObject *handler); void clearTraps(js::FreeOp *fop); private: void sweepBreakpoints(js::FreeOp *fop); public: js::WatchpointMap *watchpointMap; js::ScriptCountsMap *scriptCountsMap; js::DebugScriptMap *debugScriptMap; /* Bookkeeping information for debug scope objects. */ js::DebugScopes *debugScopes; /* * List of potentially active iterators that may need deleted property * suppression. */ js::NativeIterator *enumerators; /* Used by memory reporters and invalid otherwise. */ void *compartmentStats; #ifdef JS_ION private: js::jit::IonCompartment *ionCompartment_; public: bool ensureIonCompartmentExists(JSContext *cx); js::jit::IonCompartment *ionCompartment() { return ionCompartment_; } #endif }; // For use when changing the debug mode flag on one or more compartments. // Do not run scripts in any compartment that is scheduled for GC using this // object. See comment in updateForDebugMode. // class js::AutoDebugModeGC { JSRuntime *rt; bool needGC; public: explicit AutoDebugModeGC(JSRuntime *rt) : rt(rt), needGC(false) {} ~AutoDebugModeGC() { // Under some circumstances (say, in the midst of an animation), // the garbage collector may try to retain JIT code and analyses. // The DEBUG_MODE_GC reason forces the collector to always throw // everything away, as required for debug mode transitions. if (needGC) GC(rt, GC_NORMAL, JS::gcreason::DEBUG_MODE_GC); } void scheduleGC(Zone *zone) { JS_ASSERT(!rt->isHeapBusy()); PrepareZoneForGC(zone); needGC = true; } }; inline bool JSContext::typeInferenceEnabled() const { return compartment()->zone()->types.inferenceEnabled; } inline js::Handle JSContext::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. */ return js::Handle::fromMarkedLocation(compartment()->global_.unsafeGet()); } namespace js { class AssertCompartmentUnchanged { public: AssertCompartmentUnchanged(JSContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : cx(cx), oldCompartment(cx->compartment()) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ~AssertCompartmentUnchanged() { JS_ASSERT(cx->compartment() == oldCompartment); } protected: JSContext * const cx; JSCompartment * const oldCompartment; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; class AutoCompartment { JSContext * const cx_; JSCompartment * const origin_; public: inline AutoCompartment(JSContext *cx, JSObject *target); inline AutoCompartment(JSContext *cx, JSCompartment *target); inline ~AutoCompartment(); JSContext *context() const { return cx_; } JSCompartment *origin() const { return origin_; } private: AutoCompartment(const AutoCompartment &) MOZ_DELETE; AutoCompartment & operator=(const AutoCompartment &) MOZ_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 ∾ RootedObject scope; public: ErrorCopier(mozilla::Maybe &ac, JSObject *scope) : ac(ac), scope(ac.ref().context(), scope) {} ~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 AutoWrapperVector : public AutoVectorRooter { public: explicit AutoWrapperVector(JSContext *cx MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : AutoVectorRooter(cx, WRAPVECTOR) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; class AutoWrapperRooter : private AutoGCRooter { public: AutoWrapperRooter(JSContext *cx, WrapperValue v MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : AutoGCRooter(cx, WRAPPER), value(v) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } operator JSObject *() const { return value.get().toObjectOrNull(); } friend void AutoGCRooter::trace(JSTracer *trc); private: WrapperValue value; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; } /* namespace js */ #endif /* jscompartment_h */