Bug 1529757 - Add JS::GetFunctionRealm and use it in GetPrototypeFromConstructor. r=jorendorff,bzbarsky
authorJan de Mooij <jdemooij@mozilla.com>
Fri, 26 Apr 2019 07:46:04 +0000
changeset 530278 99b3585a6840e5b74bb827ce07d7ac8a24a6ca42
parent 530277 0c643e1215c76d16f9efb5b7255f7051ffc75597
child 530279 f4188146536b4c05b79ab3440854acfc461c9f76
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjorendorff, bzbarsky
bugs1529757
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1529757 - Add JS::GetFunctionRealm and use it in GetPrototypeFromConstructor. r=jorendorff,bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D27867
js/public/Realm.h
js/src/tests/jstests.list
js/src/tests/non262/Function/get-function-realm.js
js/src/vm/JSObject.cpp
js/src/vm/Realm.cpp
--- a/js/public/Realm.h
+++ b/js/public/Realm.h
@@ -111,11 +111,21 @@ extern JS_PUBLIC_API JSObject* GetRealmO
 extern JS_PUBLIC_API JSObject* GetRealmFunctionPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API JSObject* GetRealmArrayPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API JSObject* GetRealmErrorPrototype(JSContext* cx);
 
 extern JS_PUBLIC_API JSObject* GetRealmIteratorPrototype(JSContext* cx);
 
+// Implements https://tc39.github.io/ecma262/#sec-getfunctionrealm
+// 7.3.22 GetFunctionRealm ( obj )
+//
+// WARNING: may return a realm in a different compartment!
+//
+// Will throw an exception and return nullptr when a security wrapper or revoked
+// proxy is encountered.
+extern JS_PUBLIC_API Realm* GetFunctionRealm(JSContext* cx,
+                                             HandleObject objArg);
+
 }  // namespace JS
 
 #endif  // js_Realm_h
--- a/js/src/tests/jstests.list
+++ b/js/src/tests/jstests.list
@@ -350,28 +350,22 @@ skip script test262/language/expressions
 skip script test262/language/expressions/postfix-increment/S11.3.1_A6_T2.js
 skip script test262/language/expressions/prefix-decrement/S11.4.5_A5_T5.js
 skip script test262/language/expressions/prefix-decrement/S11.4.5_A6_T1.js
 skip script test262/language/expressions/prefix-decrement/S11.4.5_A6_T2.js
 skip script test262/language/expressions/prefix-increment/S11.4.4_A5_T5.js
 skip script test262/language/expressions/prefix-increment/S11.4.4_A6_T1.js
 skip script test262/language/expressions/prefix-increment/S11.4.4_A6_T2.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1288457
-skip script test262/built-ins/Function/internals/Construct/base-ctor-revoked-proxy.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=944846
 skip script test262/built-ins/Number/prototype/toExponential/return-values.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1225839
 skip script test262/built-ins/Function/internals/Call/class-ctor-realm.js
 
-# https://bugzilla.mozilla.org/show_bug.cgi?id=1288457
-skip script test262/built-ins/Function/internals/Construct/base-ctor-revoked-proxy-realm.js
-
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1297179
 skip script test262/built-ins/Proxy/apply/arguments-realm.js
 skip script test262/built-ins/Proxy/apply/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/construct/arguments-realm.js
 skip script test262/built-ins/Proxy/construct/trap-is-not-callable-realm.js
 skip script test262/built-ins/Proxy/defineProperty/desc-realm.js
 skip script test262/built-ins/Proxy/defineProperty/null-handler-realm.js
 skip script test262/built-ins/Proxy/defineProperty/targetdesc-configurable-desc-not-configurable-realm.js
@@ -394,19 +388,17 @@ skip script test262/built-ins/Proxy/set/
 skip script test262/built-ins/Proxy/setPrototypeOf/trap-is-not-callable-realm.js
 
 # Errors thrown from wrong realm, similar to 1225839, 1288457, and 1297179.
 skip script test262/built-ins/Array/length/define-own-prop-length-overflow-realm.js
 skip script test262/built-ins/Function/internals/Construct/derived-return-val-realm.js
 skip script test262/built-ins/Function/internals/Construct/derived-this-uninitialized-realm.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317416
-skip script test262/language/expressions/super/realm.js
 skip script test262/built-ins/GeneratorFunction/proto-from-ctor-realm.js
-skip script test262/built-ins/Function/prototype/bind/proto-from-ctor-realm.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317395
 skip script test262/built-ins/ArrayBuffer/prototype/byteLength/detached-buffer.js
 
 # https://bugzilla.mozilla.org/show_bug.cgi?id=1317378
 skip script test262/language/statements/do-while/cptn-abrupt-empty.js
 skip script test262/language/statements/do-while/cptn-normal.js
 skip script test262/language/statements/for-in/cptn-decl-abrupt-empty.js
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/Function/get-function-realm.js
@@ -0,0 +1,45 @@
+var g1 = newGlobal();
+var g1Fun = g1.eval("function Fun() {}; Fun");
+
+// Bound function => cross-realm function.
+var bound1 = Function.prototype.bind.call(g1Fun);
+assertEq(Object.getPrototypeOf(new bound1()), g1.Fun.prototype);
+
+// Proxy => cross-realm function.
+var proxy1 = new Proxy(g1Fun, {
+    get: function() {} // Ensure "prototype" is |undefined|.
+});
+assertEq(Object.getPrototypeOf(new proxy1()), g1.Object.prototype);
+
+// Proxy => bound function => cross-realm function.
+var proxy2 = new Proxy(bound1, {
+    get: function() {}
+});
+assertEq(Object.getPrototypeOf(new proxy2()), g1.Object.prototype);
+
+// Revoked proxy => cross-realm function.
+var r1 = Proxy.revocable(g1Fun, {
+    get: function(t, name) {
+        assertEq(name, "prototype");
+        r1.revoke();
+    }
+});
+assertThrowsInstanceOf(() => new r1.proxy(), g1.TypeError);
+
+// Bound function => proxy => bound function => cross-realm function.
+var bound2 = Function.prototype.bind.call(proxy2);
+assertEq(Object.getPrototypeOf(new bound2()), g1.Object.prototype);
+
+// Proxy => cross-realm revoked proxy => cross-realm function.
+var r2 = Proxy.revocable(g1Fun, {
+    get: function(t, name) {
+        assertEq(name, "prototype");
+        r2.revoke();
+    }
+});
+var g2 = newGlobal();
+var proxy3 = new g2.Proxy(r2.proxy, {});
+assertThrowsInstanceOf(() => new proxy3(), g1.TypeError);
+
+if (typeof reportCompare === "function")
+    reportCompare(true, true);
--- a/js/src/vm/JSObject.cpp
+++ b/js/src/vm/JSObject.cpp
@@ -1178,43 +1178,45 @@ bool js::GetPrototypeFromConstructor(JSC
     proto.set(nullptr);
   } else if (intrinsicDefaultProto == JSProto_Null) {
     // Bug 1317416. The caller did not pass a reasonable JSProtoKey, so let the
     // caller select a prototype object. Most likely they will choose one from
     // the wrong realm.
     proto.set(nullptr);
   } else {
     // Step 4.a: Let realm be ? GetFunctionRealm(constructor);
-    JSObject* unwrappedConstructor = CheckedUnwrapStatic(newTarget);
-    if (!unwrappedConstructor) {
-      ReportAccessDenied(cx);
+    Realm* realm = JS::GetFunctionRealm(cx, newTarget);
+    if (!realm) {
       return false;
     }
 
     // Step 4.b: Set proto to realm's intrinsic object named
     //           intrinsicDefaultProto.
     {
-      AutoRealm ar(cx, unwrappedConstructor);
+      mozilla::Maybe<AutoRealm> ar;
+      if (cx->realm() != realm) {
+        ar.emplace(cx, realm->maybeGlobal());
+      }
       proto.set(GlobalObject::getOrCreatePrototype(cx, intrinsicDefaultProto));
     }
     if (!proto) {
       return false;
     }
     if (!cx->compartment()->wrap(cx, proto)) {
       return false;
     }
   }
   return true;
 }
 
 JSObject* js::CreateThisForFunction(JSContext* cx, HandleFunction callee,
                                     HandleObject newTarget,
                                     NewObjectKind newKind) {
   RootedObject proto(cx);
-  if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Null, &proto)) {
+  if (!GetPrototypeFromConstructor(cx, newTarget, JSProto_Object, &proto)) {
     return nullptr;
   }
 
   JSObject* obj =
       CreateThisForFunctionWithProto(cx, callee, newTarget, proto, newKind);
 
   if (obj && newKind == SingletonObject) {
     RootedPlainObject nobj(cx, &obj->as<PlainObject>());
--- a/js/src/vm/Realm.cpp
+++ b/js/src/vm/Realm.cpp
@@ -1032,8 +1032,58 @@ JS_PUBLIC_API JSObject* JS::GetRealmErro
   return GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(),
                                                        JSEXN_ERR);
 }
 
 JS_PUBLIC_API JSObject* JS::GetRealmIteratorPrototype(JSContext* cx) {
   CHECK_THREAD(cx);
   return GlobalObject::getOrCreateIteratorPrototype(cx, cx->global());
 }
+
+JS_PUBLIC_API Realm* JS::GetFunctionRealm(JSContext* cx, HandleObject objArg) {
+  // https://tc39.github.io/ecma262/#sec-getfunctionrealm
+  // 7.3.22 GetFunctionRealm ( obj )
+
+  CHECK_THREAD(cx);
+  cx->check(objArg);
+
+  RootedObject obj(cx, objArg);
+  while (true) {
+    obj = CheckedUnwrapStatic(obj);
+    if (!obj) {
+      ReportAccessDenied(cx);
+      return nullptr;
+    }
+
+    // Step 1.
+    MOZ_ASSERT(IsCallable(obj));
+
+    // Steps 2 and 3. We use a loop instead of recursion to unwrap bound
+    // functions.
+    if (obj->is<JSFunction>()) {
+      JSFunction* fun = &obj->as<JSFunction>();
+      if (!fun->isBoundFunction()) {
+        return fun->realm();
+      }
+
+      obj = fun->getBoundFunctionTarget();
+      continue;
+    }
+
+    // Step 4.
+    if (IsScriptedProxy(obj)) {
+      // Steps 4.a-b.
+      JSObject* proxyTarget = GetProxyTargetObject(obj);
+      if (!proxyTarget) {
+        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
+                                  JSMSG_PROXY_REVOKED);
+        return nullptr;
+      }
+
+      // Step 4.c.
+      obj = proxyTarget;
+      continue;
+    }
+
+    // Step 5.
+    return cx->realm();
+  }
+}