Bug 1708469 - Split realms, compartments, zones, context and runtime from JSApi; r=jonco

Differential Revision: https://phabricator.services.mozilla.com/D113813
This commit is contained in:
Yulia Startsev 2021-05-04 16:53:05 +00:00
parent 0951f34344
commit d15f5ac9b6
5 changed files with 268 additions and 230 deletions

104
js/public/Context.h Normal file
View File

@ -0,0 +1,104 @@
/* -*- 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/. */
/* JavaScript API. */
#ifndef js_Context_h
#define js_Context_h
#include "jspubtd.h"
// [SMDOC] Nested Thread Data Structures (JSContext, JSRuntime)
//
// Spidermonkey has two nested data structures for representing threads,
// JSContext and JSRuntime. All JS threads are represented by a context.
// Contexts can contain runtimes. A runtime however is not present for
// all threads. Threads also interact with the GC. See "Nested GC
// DataStructures" for more info.
//
// Context
// -------
// JSContext represents a thread: there must be exactly one JSContext for each
// thread running JS/Wasm.
//
// Internally, helper threads can also have a JSContext. They do not always have
// an active context, but one may be requested by AutoSetHelperThreadContext,
// which activates a pre-allocated JSContext for the duration of its lifetime.
//
// Runtime
// -------
// JSRuntime is very similar to JSContext: each runtime belongs to one context
// (thread), but helper threads don't have their own runtimes (they're shared by
// all runtimes in the process and use the runtime of the task they're
// executing).
//
// Note:
// Locking, contexts, and memory allocation.
//
// It is important that SpiderMonkey be initialized, and the first context
// be created, in a single-threaded fashion. Otherwise the behavior of the
// library is undefined.
// See:
// https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference
// Create a new context (and runtime) for this thread.
extern JS_PUBLIC_API JSContext* JS_NewContext(
uint32_t maxbytes, JSRuntime* parentRuntime = nullptr);
// Destroy a context allocated with JS_NewContext. Must be called on the thread
// that called JS_NewContext.
extern JS_PUBLIC_API void JS_DestroyContext(JSContext* cx);
JS_PUBLIC_API void* JS_GetContextPrivate(JSContext* cx);
JS_PUBLIC_API void JS_SetContextPrivate(JSContext* cx, void* data);
extern JS_PUBLIC_API JSRuntime* JS_GetParentRuntime(JSContext* cx);
extern JS_PUBLIC_API JSRuntime* JS_GetRuntime(JSContext* cx);
extern JS_PUBLIC_API void JS_SetFutexCanWait(JSContext* cx);
namespace js {
void AssertHeapIsIdle();
} /* namespace js */
namespace JS {
/**
* Initialize the runtime's self-hosted code. Embeddings should call this
* exactly once per runtime/context, before the first JS_NewGlobalObject
* call.
*
* NOTE: This may not set a pending exception in the case of OOM since this
* runs very early in startup.
*/
JS_PUBLIC_API bool InitSelfHostedCode(JSContext* cx);
/**
* Asserts (in debug and release builds) that `obj` belongs to the current
* thread's context.
*/
JS_PUBLIC_API void AssertObjectBelongsToCurrentThread(JSObject* obj);
/**
* Install a process-wide callback to validate script filenames. The JS engine
* will invoke this callback for each JS script it parses or XDR decodes.
*
* If the callback returns |false|, an exception is thrown and parsing/decoding
* will be aborted.
*
* See also CompileOptions::setSkipFilenameValidation to opt-out of the callback
* for specific parse jobs.
*/
using FilenameValidationCallback = bool (*)(const char* filename,
bool isSystemRealm);
JS_PUBLIC_API void SetFilenameValidationCallback(FilenameValidationCallback cb);
} /* namespace JS */
#endif // js_Context_h

View File

@ -12,6 +12,13 @@
#include "js/GCPolicyAPI.h"
#include "js/TypeDecls.h" // forward-declaration of JS::Realm
/************************************************************************/
// [SMDOC] Realms
//
// Data associated with a global object. In the browser each frame has its
// own global/realm.
namespace js {
namespace gc {
JS_PUBLIC_API void TraceRealm(JSTracer* trc, JS::Realm* realm,
@ -116,6 +123,78 @@ extern JS_PUBLIC_API JSObject* GetRealmWeakMapKey(JSContext* cx);
extern JS_PUBLIC_API Realm* GetFunctionRealm(JSContext* cx,
HandleObject objArg);
/** NB: This API is infallible; a nullptr return value does not indicate error.
*
* |target| must not be a cross-compartment wrapper because CCWs are not
* associated with a single realm.
*
* Entering a realm roots the realm and its global object until the matching
* JS::LeaveRealm() call.
*/
extern JS_PUBLIC_API JS::Realm* EnterRealm(JSContext* cx, JSObject* target);
extern JS_PUBLIC_API void LeaveRealm(JSContext* cx, JS::Realm* oldRealm);
} // namespace JS
/*
* At any time, a JSContext has a current (possibly-nullptr) realm.
* Realms are described in:
*
* developer.mozilla.org/en-US/docs/SpiderMonkey/SpiderMonkey_compartments
*
* The current realm of a context may be changed. The preferred way to do
* this is with JSAutoRealm:
*
* void foo(JSContext* cx, JSObject* obj) {
* // in some realm 'r'
* {
* JSAutoRealm ar(cx, obj); // constructor enters
* // in the realm of 'obj'
* } // destructor leaves
* // back in realm 'r'
* }
*
* The object passed to JSAutoRealm must *not* be a cross-compartment wrapper,
* because CCWs are not associated with a single realm.
*
* For more complicated uses that don't neatly fit in a C++ stack frame, the
* realm can be entered and left using separate function calls:
*
* void foo(JSContext* cx, JSObject* obj) {
* // in 'oldRealm'
* JS::Realm* oldRealm = JS::EnterRealm(cx, obj);
* // in the realm of 'obj'
* JS::LeaveRealm(cx, oldRealm);
* // back in 'oldRealm'
* }
*
* Note: these calls must still execute in a LIFO manner w.r.t all other
* enter/leave calls on the context. Furthermore, only the return value of a
* JS::EnterRealm call may be passed as the 'oldRealm' argument of
* the corresponding JS::LeaveRealm call.
*
* Entering a realm roots the realm and its global object for the lifetime of
* the JSAutoRealm.
*/
class MOZ_RAII JS_PUBLIC_API JSAutoRealm {
JSContext* cx_;
JS::Realm* oldRealm_;
public:
JSAutoRealm(JSContext* cx, JSObject* target);
JSAutoRealm(JSContext* cx, JSScript* target);
~JSAutoRealm();
};
class MOZ_RAII JS_PUBLIC_API JSAutoNullableRealm {
JSContext* cx_;
JS::Realm* oldRealm_;
public:
explicit JSAutoNullableRealm(JSContext* cx, JSObject* targetOrNull);
~JSAutoNullableRealm();
};
#endif // js_Realm_h

81
js/public/Zone.h Normal file
View File

@ -0,0 +1,81 @@
/* -*- 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/. */
/* JavaScript API. */
#ifndef js_Zone_h
#define js_Zone_h
#include "jspubtd.h"
#include "js/Context.h"
// [SMDOC] Nested GC Data Structures (Compartments and Zones)
//
// The GC has two nested data structures, Zones and Compartents. Each has
// distint responsibilities. Zones contain compartments, along with other GC
// resources used by a tab. Compartments contain realms, which are specification
// defined.
//
// See also the SMDoc on Realms.
//
// Compartment
// -----------
// Security membrane; when an object from compartment A is used in compartment
// B, a cross-compartment wrapper (a kind of proxy) is used. In the browser,
// same-origin realms can share a compartment.
//
// Zone
// ----
// A Zone is a group of compartments that share GC resources (arenas, strings,
// etc) for memory usage and performance reasons. Zone is the GC unit: the GC
// can operate on one or more zones at a time. The browser uses roughly one zone
// per tab.
using JSDestroyZoneCallback = void (*)(JSFreeOp*, JS::Zone*);
using JSDestroyCompartmentCallback = void (*)(JSFreeOp*, JS::Compartment*);
using JSSizeOfIncludingThisCompartmentCallback =
size_t (*)(mozilla::MallocSizeOf, JS::Compartment*);
extern JS_PUBLIC_API void JS_SetDestroyZoneCallback(
JSContext* cx, JSDestroyZoneCallback callback);
extern JS_PUBLIC_API void JS_SetDestroyCompartmentCallback(
JSContext* cx, JSDestroyCompartmentCallback callback);
extern JS_PUBLIC_API void JS_SetSizeOfIncludingThisCompartmentCallback(
JSContext* cx, JSSizeOfIncludingThisCompartmentCallback callback);
extern JS_PUBLIC_API void JS_SetCompartmentPrivate(JS::Compartment* compartment,
void* data);
extern JS_PUBLIC_API void* JS_GetCompartmentPrivate(
JS::Compartment* compartment);
extern JS_PUBLIC_API void JS_SetZoneUserData(JS::Zone* zone, void* data);
extern JS_PUBLIC_API void* JS_GetZoneUserData(JS::Zone* zone);
extern JS_PUBLIC_API bool JS_RefreshCrossCompartmentWrappers(
JSContext* cx, JS::Handle<JSObject*> obj);
/**
* Mark a jsid after entering a new compartment. Different zones separately
* mark the ids in a runtime, and this must be used any time an id is obtained
* from one compartment and then used in another compartment, unless the two
* compartments are guaranteed to be in the same zone.
*/
extern JS_PUBLIC_API void JS_MarkCrossZoneId(JSContext* cx, jsid id);
/**
* If value stores a jsid (an atomized string or symbol), mark that id as for
* JS_MarkCrossZoneId.
*/
extern JS_PUBLIC_API void JS_MarkCrossZoneIdValue(JSContext* cx,
const JS::Value& value);
#endif // js_Zone_h

View File

@ -31,6 +31,7 @@
#include "js/CharacterEncoding.h"
#include "js/Class.h"
#include "js/CompileOptions.h"
#include "js/Context.h"
#include "js/ErrorReport.h"
#include "js/Exception.h"
#include "js/GCVector.h"
@ -56,6 +57,7 @@
#include "js/ValueArray.h"
#include "js/Vector.h"
#include "js/WeakMap.h"
#include "js/Zone.h"
/************************************************************************/
@ -135,13 +137,6 @@ struct JSWrapObjectCallbacks {
JSPreWrapCallback preWrap;
};
using JSDestroyZoneCallback = void (*)(JSFreeOp*, JS::Zone*);
using JSDestroyCompartmentCallback = void (*)(JSFreeOp*, JS::Compartment*);
using JSSizeOfIncludingThisCompartmentCallback =
size_t (*)(mozilla::MallocSizeOf, JS::Compartment*);
/**
* Callback used to intercept JavaScript errors.
*/
@ -214,116 +209,6 @@ extern JS_PUBLIC_API bool JS_IsBuiltinEvalFunction(JSFunction* fun);
/** True iff fun is the Function constructor. */
extern JS_PUBLIC_API bool JS_IsBuiltinFunctionConstructor(JSFunction* fun);
/************************************************************************/
// [SMDOC] Data Structures (JSContext, JSRuntime, Realm, Compartment, Zone)
//
// SpiderMonkey uses some data structures that behave a lot like Russian dolls:
// runtimes contain zones, zones contain compartments, compartments contain
// realms. Each layer has its own purpose.
//
// Realm
// -----
// Data associated with a global object. In the browser each frame has its
// own global/realm.
//
// Compartment
// -----------
// Security membrane; when an object from compartment A is used in compartment
// B, a cross-compartment wrapper (a kind of proxy) is used. In the browser,
// same-origin realms can share a compartment.
//
// Zone
// ----
// A Zone is a group of compartments that share GC resources (arenas, strings,
// etc) for memory usage and performance reasons. Zone is the GC unit: the GC
// can operate on one or more zones at a time. The browser uses roughly one zone
// per tab.
//
// Context
// -------
// JSContext represents a thread: there must be exactly one JSContext for each
// thread running JS/Wasm.
//
// Internally, helper threads can also have a JSContext. They do not always have
// an active context, but one may be requested by AutoSetHelperThreadContext,
// which activates a pre-allocated JSContext for the duration of its lifetime.
//
// Runtime
// -------
// JSRuntime is very similar to JSContext: each runtime belongs to one context
// (thread), but helper threads don't have their own runtimes (they're shared by
// all runtimes in the process and use the runtime of the task they're
// executing).
/*
* Locking, contexts, and memory allocation.
*
* It is important that SpiderMonkey be initialized, and the first context
* be created, in a single-threaded fashion. Otherwise the behavior of the
* library is undefined.
* See:
* https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JSAPI_reference
*/
// Create a new context (and runtime) for this thread.
extern JS_PUBLIC_API JSContext* JS_NewContext(
uint32_t maxbytes, JSRuntime* parentRuntime = nullptr);
// Destroy a context allocated with JS_NewContext. Must be called on the thread
// that called JS_NewContext.
extern JS_PUBLIC_API void JS_DestroyContext(JSContext* cx);
JS_PUBLIC_API void* JS_GetContextPrivate(JSContext* cx);
JS_PUBLIC_API void JS_SetContextPrivate(JSContext* cx, void* data);
extern JS_PUBLIC_API JSRuntime* JS_GetParentRuntime(JSContext* cx);
extern JS_PUBLIC_API JSRuntime* JS_GetRuntime(JSContext* cx);
extern JS_PUBLIC_API void JS_SetFutexCanWait(JSContext* cx);
namespace js {
void AssertHeapIsIdle();
} /* namespace js */
namespace JS {
/**
* Initialize the runtime's self-hosted code. Embeddings should call this
* exactly once per runtime/context, before the first JS_NewGlobalObject
* call.
*
* NOTE: This may not set a pending exception in the case of OOM since this
* runs very early in startup.
*/
JS_PUBLIC_API bool InitSelfHostedCode(JSContext* cx);
/**
* Asserts (in debug and release builds) that `obj` belongs to the current
* thread's context.
*/
JS_PUBLIC_API void AssertObjectBelongsToCurrentThread(JSObject* obj);
/**
* Install a process-wide callback to validate script filenames. The JS engine
* will invoke this callback for each JS script it parses or XDR decodes.
*
* If the callback returns |false|, an exception is thrown and parsing/decoding
* will be aborted.
*
* See also CompileOptions::setSkipFilenameValidation to opt-out of the callback
* for specific parse jobs.
*/
using FilenameValidationCallback = bool (*)(const char* filename,
bool isSystemRealm);
JS_PUBLIC_API void SetFilenameValidationCallback(FilenameValidationCallback cb);
} /* namespace JS */
/**
* Set callback to send tasks to XPCOM thread pools
*/
@ -332,15 +217,6 @@ JS_PUBLIC_API void SetHelperThreadTaskCallback(
extern JS_PUBLIC_API const char* JS_GetImplementationVersion(void);
extern JS_PUBLIC_API void JS_SetDestroyZoneCallback(
JSContext* cx, JSDestroyZoneCallback callback);
extern JS_PUBLIC_API void JS_SetDestroyCompartmentCallback(
JSContext* cx, JSDestroyCompartmentCallback callback);
extern JS_PUBLIC_API void JS_SetSizeOfIncludingThisCompartmentCallback(
JSContext* cx, JSSizeOfIncludingThisCompartmentCallback callback);
extern JS_PUBLIC_API void JS_SetWrapObjectCallbacks(
JSContext* cx, const JSWrapObjectCallbacks* callbacks);
@ -369,16 +245,6 @@ extern JS_PUBLIC_API JSErrorInterceptor* JS_GetErrorInterceptorCallback(
extern JS_PUBLIC_API mozilla::Maybe<JSExnType> JS_GetErrorType(
const JS::Value& val);
extern JS_PUBLIC_API void JS_SetCompartmentPrivate(JS::Compartment* compartment,
void* data);
extern JS_PUBLIC_API void* JS_GetCompartmentPrivate(
JS::Compartment* compartment);
extern JS_PUBLIC_API void JS_SetZoneUserData(JS::Zone* zone, void* data);
extern JS_PUBLIC_API void* JS_GetZoneUserData(JS::Zone* zone);
extern JS_PUBLIC_API bool JS_WrapObject(JSContext* cx,
JS::MutableHandleObject objp);
@ -389,100 +255,6 @@ extern JS_PUBLIC_API JSObject* JS_TransplantObject(JSContext* cx,
JS::HandleObject origobj,
JS::HandleObject target);
extern JS_PUBLIC_API bool JS_RefreshCrossCompartmentWrappers(
JSContext* cx, JS::Handle<JSObject*> obj);
/*
* At any time, a JSContext has a current (possibly-nullptr) realm.
* Realms are described in:
*
* developer.mozilla.org/en-US/docs/SpiderMonkey/SpiderMonkey_compartments
*
* The current realm of a context may be changed. The preferred way to do
* this is with JSAutoRealm:
*
* void foo(JSContext* cx, JSObject* obj) {
* // in some realm 'r'
* {
* JSAutoRealm ar(cx, obj); // constructor enters
* // in the realm of 'obj'
* } // destructor leaves
* // back in realm 'r'
* }
*
* The object passed to JSAutoRealm must *not* be a cross-compartment wrapper,
* because CCWs are not associated with a single realm.
*
* For more complicated uses that don't neatly fit in a C++ stack frame, the
* realm can be entered and left using separate function calls:
*
* void foo(JSContext* cx, JSObject* obj) {
* // in 'oldRealm'
* JS::Realm* oldRealm = JS::EnterRealm(cx, obj);
* // in the realm of 'obj'
* JS::LeaveRealm(cx, oldRealm);
* // back in 'oldRealm'
* }
*
* Note: these calls must still execute in a LIFO manner w.r.t all other
* enter/leave calls on the context. Furthermore, only the return value of a
* JS::EnterRealm call may be passed as the 'oldRealm' argument of
* the corresponding JS::LeaveRealm call.
*
* Entering a realm roots the realm and its global object for the lifetime of
* the JSAutoRealm.
*/
class MOZ_RAII JS_PUBLIC_API JSAutoRealm {
JSContext* cx_;
JS::Realm* oldRealm_;
public:
JSAutoRealm(JSContext* cx, JSObject* target);
JSAutoRealm(JSContext* cx, JSScript* target);
~JSAutoRealm();
};
class MOZ_RAII JS_PUBLIC_API JSAutoNullableRealm {
JSContext* cx_;
JS::Realm* oldRealm_;
public:
explicit JSAutoNullableRealm(JSContext* cx, JSObject* targetOrNull);
~JSAutoNullableRealm();
};
namespace JS {
/** NB: This API is infallible; a nullptr return value does not indicate error.
*
* |target| must not be a cross-compartment wrapper because CCWs are not
* associated with a single realm.
*
* Entering a realm roots the realm and its global object until the matching
* JS::LeaveRealm() call.
*/
extern JS_PUBLIC_API JS::Realm* EnterRealm(JSContext* cx, JSObject* target);
extern JS_PUBLIC_API void LeaveRealm(JSContext* cx, JS::Realm* oldRealm);
} // namespace JS
/**
* Mark a jsid after entering a new compartment. Different zones separately
* mark the ids in a runtime, and this must be used any time an id is obtained
* from one compartment and then used in another compartment, unless the two
* compartments are guaranteed to be in the same zone.
*/
extern JS_PUBLIC_API void JS_MarkCrossZoneId(JSContext* cx, jsid id);
/**
* If value stores a jsid (an atomized string or symbol), mark that id as for
* JS_MarkCrossZoneId.
*/
extern JS_PUBLIC_API void JS_MarkCrossZoneIdValue(JSContext* cx,
const JS::Value& value);
/**
* Resolve id, which must contain either a string or an int, to a standard
* class name in obj if possible, defining the class's constructor and/or

View File

@ -140,6 +140,7 @@ EXPORTS.js += [
"../public/ComparisonOperators.h",
"../public/CompilationAndEvaluation.h",
"../public/CompileOptions.h",
"../public/Context.h",
"../public/ContextOptions.h",
"../public/Conversions.h",
"../public/Date.h",
@ -219,6 +220,7 @@ EXPORTS.js += [
"../public/WeakMap.h",
"../public/WeakMapPtr.h",
"../public/Wrapper.h",
"../public/Zone.h",
]
# This lives in mozglue/baseprofiler but we re-publish ourselves to support