Commit e8f8421e authored by Jan de Mooij's avatar Jan de Mooij
Browse files

Bug 1771084 part 2 - Add Realm option to freeze builtins. r=tcampbell,nika a=RyanVM

parent 243ddbd5
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -94,6 +94,12 @@ extern JS_PUBLIC_API JSObject* GetRealmGlobalOrNull(Realm* realm);
// 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.
+10 −0
Original line number Diff line number Diff line
@@ -252,6 +252,15 @@ class JS_PUBLIC_API RealmCreationOptions {
    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;
@@ -282,6 +291,7 @@ class JS_PUBLIC_API RealmCreationOptions {
  bool propertyErrorMessageFix_ = false;
  bool iteratorHelpers_ = false;
  bool secureContext_ = false;
  bool freezeBuiltins_ = false;
};

/**
+21 −0
Original line number Diff line number Diff line
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");
+10 −0
Original line number Diff line number Diff line
@@ -7344,6 +7344,13 @@ static bool NewGlobal(JSContext* cx, unsigned argc, Value* vp) {
      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
@@ -9790,6 +9797,9 @@ static const JSFunctionSpecWithHelp shell_functions[] = {
"         (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"
+31 −2
Original line number Diff line number Diff line
@@ -209,6 +209,27 @@ bool GlobalObject::skipDeselectedConstructor(JSContext* cx, JSProtoKey key) {
  }
}

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,
@@ -326,7 +347,8 @@ bool GlobalObject::resolveConstructor(JSContext* 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;
      }
    }
@@ -371,6 +393,12 @@ bool GlobalObject::resolveConstructor(JSContext* cx,
    }
  }

  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.
@@ -396,7 +424,8 @@ bool GlobalObject::resolveConstructor(JSContext* cx,

      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;
        }
      }
Loading