Bug 1771084 part 2 - Add Realm option to freeze builtins. r=tcampbell,nika a=pascalc
Differential Revision:
https://phabricator.services.mozilla.com/D147281
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -85,16 +85,22 @@ extern JS_PUBLIC_API void SetRealmNameCa
// GC, between collecting the global object and destroying the Realm.
extern JS_PUBLIC_API JSObject* GetRealmGlobalOrNull(Realm* realm);
// Initialize standard JS class constructors, prototypes, and any top-level
// functions and constants associated with the standard classes (e.g. isNaN
// for Number).
extern JS_PUBLIC_API bool InitRealmStandardClasses(JSContext* cx);
+// If the current realm has the non-standard freezeBuiltins option set to true,
+// freeze the constructor object and seal the prototype.
+extern JS_PUBLIC_API bool MaybeFreezeCtorAndPrototype(JSContext* cx,
+ HandleObject ctor,
+ HandleObject maybeProto);
+
/*
* Ways to get various per-Realm objects. All the getters declared below operate
* on the JSContext's current Realm.
*/
extern JS_PUBLIC_API JSObject* GetRealmObjectPrototype(JSContext* cx);
extern JS_PUBLIC_API JSObject* GetRealmFunctionPrototype(JSContext* cx);
--- a/js/public/RealmOptions.h
+++ b/js/public/RealmOptions.h
@@ -229,16 +229,25 @@ class JS_PUBLIC_API RealmCreationOptions
// https://w3c.github.io/webappsec-secure-contexts/
// https://bugzilla.mozilla.org/show_bug.cgi?id=1162772#c34
bool secureContext() const { return secureContext_; }
RealmCreationOptions& setSecureContext(bool flag) {
secureContext_ = flag;
return *this;
}
+ // Non-standard option to freeze certain builtin constructors and seal their
+ // prototypes. Also defines these constructors on the global as non-writable
+ // and non-configurable.
+ bool freezeBuiltins() const { return freezeBuiltins_; }
+ RealmCreationOptions& setFreezeBuiltins(bool flag) {
+ freezeBuiltins_ = flag;
+ return *this;
+ }
+
uint64_t profilerRealmID() const { return profilerRealmID_; }
RealmCreationOptions& setProfilerRealmID(uint64_t id) {
profilerRealmID_ = id;
return *this;
}
private:
JSTraceOp traceGlobal_ = nullptr;
@@ -260,16 +269,17 @@ class JS_PUBLIC_API RealmCreationOptions
bool iteratorHelpers_ = false;
#ifdef NIGHTLY_BUILD
bool arrayGrouping_ = true;
#endif
#ifdef ENABLE_NEW_SET_METHODS
bool newSetMethods_ = false;
#endif
bool secureContext_ = false;
+ bool freezeBuiltins_ = false;
};
/**
* RealmBehaviors specifies behaviors of a realm that can be changed after the
* realm's been created.
*/
class JS_PUBLIC_API RealmBehaviors {
public:
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/basic/freeze-builtins.js
@@ -0,0 +1,21 @@
+var g = newGlobal({freezeBuiltins: true});
+
+g.evaluate("" + function checkFrozen(name) {
+ // Check constructor on the global is non-writable/non-configurable.
+ let desc = Object.getOwnPropertyDescriptor(this, name);
+ assertEq(desc.writable, false);
+ assertEq(desc.configurable, false);
+
+ // Constructor must be frozen.
+ let ctor = desc.value;
+ assertEq(Object.isFrozen(ctor), true);
+
+ // Prototype must be sealed.
+ if (ctor.prototype) {
+ assertEq(Object.isSealed(ctor.prototype), true);
+ }
+});
+
+g.checkFrozen("Object");
+g.checkFrozen("Array");
+g.checkFrozen("Function");
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -7025,16 +7025,23 @@ static bool NewGlobal(JSContext* cx, uns
if (!JS_GetProperty(cx, opts, "enableCoopAndCoep", &v)) {
return false;
}
if (v.isBoolean()) {
creationOptions.setCoopAndCoepEnabled(v.toBoolean());
}
+ if (!JS_GetProperty(cx, opts, "freezeBuiltins", &v)) {
+ return false;
+ }
+ if (v.isBoolean()) {
+ creationOptions.setFreezeBuiltins(v.toBoolean());
+ }
+
// On the web, the SharedArrayBuffer constructor is not installed as a
// global property in pages that aren't isolated in a separate process (and
// thus can't allow the structured cloning of shared memory). Specify false
// for this option to reproduce this behavior.
if (!JS_GetProperty(cx, opts, "defineSharedArrayBufferConstructor", &v)) {
return false;
}
if (v.isBoolean()) {
@@ -9503,16 +9510,19 @@ static const JSFunctionSpecWithHelp shel
" newCompartment: If true, the global will always be created in a new\n"
" compartment and zone.\n"
" invisibleToDebugger: If true, the global will be invisible to the\n"
" debugger (default false)\n"
" discardSource: If true, discard source after compiling a script\n"
" (default false).\n"
" useWindowProxy: the global will be created with a WindowProxy attached. In this\n"
" case, the WindowProxy will be returned.\n"
+" freezeBuiltins: certain builtin constructors will be frozen when created and\n"
+" their prototypes will be sealed. These constructors will be defined on the\n"
+" global as non-configurable and non-writable.\n"
" immutablePrototype: whether the global's prototype is immutable.\n"
" principal: if present, its value converted to a number must be an\n"
" integer that fits in 32 bits; use that as the new realm's\n"
" principal. Shell principals are toys, meant only for testing; one\n"
" shell principal subsumes another if its set bits are a superset of\n"
" the other's. Thus, a principal of 0 subsumes nothing, while a\n"
" principals of ~0 subsumes all other principals. The absence of a\n"
" principal is treated as if its bits were 0xffff, for subsumption\n"
--- a/js/src/vm/GlobalObject.cpp
+++ b/js/src/vm/GlobalObject.cpp
@@ -209,16 +209,37 @@ bool GlobalObject::skipDeselectedConstru
case JSProto_AsyncIterator:
return !cx->realm()->creationOptions().getIteratorHelpersEnabled();
default:
MOZ_CRASH("unexpected JSProtoKey");
}
}
+static bool ShouldFreezeBuiltin(JSProtoKey key) {
+ switch (key) {
+ case JSProto_Object:
+ case JSProto_Array:
+ case JSProto_Function:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static unsigned GetAttrsForResolvedGlobal(GlobalObject* global,
+ JSProtoKey key) {
+ unsigned attrs = JSPROP_RESOLVING;
+ if (global->realm()->creationOptions().freezeBuiltins() &&
+ ShouldFreezeBuiltin(key)) {
+ attrs |= JSPROP_PERMANENT | JSPROP_READONLY;
+ }
+ return attrs;
+}
+
/* static*/
bool GlobalObject::resolveConstructor(JSContext* cx,
Handle<GlobalObject*> global,
JSProtoKey key, IfClassIsDisabled mode) {
MOZ_ASSERT(key != JSProto_Null);
MOZ_ASSERT(!global->isStandardClassResolved(key));
MOZ_ASSERT(cx->compartment() == global->compartment());
@@ -321,17 +342,18 @@ bool GlobalObject::resolveConstructor(JS
if (!ctor) {
return false;
}
RootedId id(cx, NameToId(ClassName(key, cx)));
if (isObjectOrFunction) {
if (clasp->specShouldDefineConstructor()) {
RootedValue ctorValue(cx, ObjectValue(*ctor));
- if (!DefineDataProperty(cx, global, id, ctorValue, JSPROP_RESOLVING)) {
+ unsigned attrs = GetAttrsForResolvedGlobal(global, key);
+ if (!DefineDataProperty(cx, global, id, ctorValue, attrs)) {
return false;
}
}
global->setConstructor(key, ctor);
}
if (const JSFunctionSpec* funs = clasp->specPrototypeFunctions()) {
@@ -362,16 +384,22 @@ bool GlobalObject::resolveConstructor(JS
// Call the post-initialization hook, if provided.
if (FinishClassInitOp finishInit = clasp->specFinishInitHook()) {
if (!finishInit(cx, ctor, proto)) {
return false;
}
}
+ if (ShouldFreezeBuiltin(key)) {
+ if (!JS::MaybeFreezeCtorAndPrototype(cx, ctor, proto)) {
+ return false;
+ }
+ }
+
if (!isObjectOrFunction) {
// Any operations that modifies the global object should be placed
// after any other fallible operations.
// Fallible operation that modifies the global object.
if (clasp->specShouldDefineConstructor()) {
bool shouldReallyDefine = true;
@@ -387,17 +415,18 @@ bool GlobalObject::resolveConstructor(JS
"shouldn't be defining SharedArrayBuffer if shared memory "
"is disabled");
shouldReallyDefine = options.defineSharedArrayBufferConstructor();
}
if (shouldReallyDefine) {
RootedValue ctorValue(cx, ObjectValue(*ctor));
- if (!DefineDataProperty(cx, global, id, ctorValue, JSPROP_RESOLVING)) {
+ unsigned attrs = GetAttrsForResolvedGlobal(global, key);
+ if (!DefineDataProperty(cx, global, id, ctorValue, attrs)) {
return false;
}
}
}
// Infallible operations that modify the global object.
global->setConstructor(key, ctor);
if (proto) {
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -695,16 +695,33 @@ JS_PUBLIC_API JSObject* JS::GetRealmGlob
JS_PUBLIC_API bool JS::InitRealmStandardClasses(JSContext* cx) {
MOZ_ASSERT(!cx->zone()->isAtomsZone());
AssertHeapIsIdle();
CHECK_THREAD(cx);
return GlobalObject::initStandardClasses(cx, cx->global());
}
+JS_PUBLIC_API bool JS::MaybeFreezeCtorAndPrototype(JSContext* cx,
+ HandleObject ctor,
+ HandleObject maybeProto) {
+ if (MOZ_LIKELY(!cx->realm()->creationOptions().freezeBuiltins())) {
+ return true;
+ }
+ if (!SetIntegrityLevel(cx, ctor, IntegrityLevel::Frozen)) {
+ return false;
+ }
+ if (maybeProto) {
+ if (!SetIntegrityLevel(cx, maybeProto, IntegrityLevel::Sealed)) {
+ return false;
+ }
+ }
+ return true;
+}
+
JS_PUBLIC_API JSObject* JS::GetRealmObjectPrototype(JSContext* cx) {
CHECK_THREAD(cx);
return GlobalObject::getOrCreateObjectPrototype(cx, cx->global());
}
JS_PUBLIC_API JSObject* JS::GetRealmFunctionPrototype(JSContext* cx) {
CHECK_THREAD(cx);
return GlobalObject::getOrCreateFunctionPrototype(cx, cx->global());