Bug 1055472 - Part 5: Make the various Error constructors properly subclassable. (r=Waldo)
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Nov 2015 18:22:21 -0800
changeset 309833 b6d87e6e1064ff6d2ad3933c6aee7fac62d87cce
parent 309832 e0c0779e0eccfdcd1b9f4419bb17fa9f7bb5f59f
child 309834 628c1d07a0e985b01b5342473ef3d0af7a44701d
push id5513
push userraliiev@mozilla.com
push dateMon, 25 Jan 2016 13:55:34 +0000
treeherdermozilla-beta@5ee97dd05b5c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersWaldo
bugs1055472
milestone45.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 1055472 - Part 5: Make the various Error constructors properly subclassable. (r=Waldo)
js/src/jsexn.cpp
js/src/jsfun.cpp
js/src/jsobj.cpp
js/src/jsobj.h
js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
js/src/tests/js1_5/Error/constructor-ordering.js
js/src/vm/ErrorObject.cpp
js/src/vm/ErrorObject.h
--- a/js/src/jsexn.cpp
+++ b/js/src/jsexn.cpp
@@ -333,16 +333,21 @@ ExceptionStackOrNull(JSContext* cx, Hand
     return obj->as<ErrorObject>().stack();
 }
 
 bool
 Error(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
+    // ES6 19.5.1.1 mandates the .prototype lookup happens before the toString
+    RootedObject proto(cx);
+    if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
+        return false;
+
     /* Compute the error message, if any. */
     RootedString message(cx, nullptr);
     if (args.hasDefined(0)) {
         message = ToString<CanGC>(cx, args[0]);
         if (!message)
             return false;
     }
 
@@ -384,17 +389,17 @@ Error(JSContext* cx, unsigned argc, Valu
      * ECMA ed. 3, 15.11.1 requires Error, etc., to construct even when
      * called as functions, without operator new.  But as we do not give
      * each constructor a distinct JSClass, we must get the exception type
      * ourselves.
      */
     JSExnType exnType = JSExnType(args.callee().as<JSFunction>().getExtendedSlot(0).toInt32());
 
     RootedObject obj(cx, ErrorObject::create(cx, exnType, stack, fileName,
-                                             lineNumber, columnNumber, nullptr, message));
+                                             lineNumber, columnNumber, nullptr, message, proto));
     if (!obj)
         return false;
 
     args.rval().setObject(*obj);
     return true;
 }
 
 #if JS_HAS_TOSOURCE
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -1858,24 +1858,17 @@ FunctionConstructor(JSContext* cx, unsig
      */
     RootedAtom anonymousAtom(cx, cx->names().anonymous);
     RootedObject proto(cx);
     if (isStarGenerator) {
         proto = GlobalObject::getOrCreateStarGeneratorFunctionPrototype(cx, global);
         if (!proto)
             return false;
     } else {
-        RootedObject toTest(cx);
-        // If we are invoked without |new|, then just use Function.prototype
-        if (args.isConstructing())
-            toTest = &args.newTarget().toObject();
-        else
-            toTest = &args.callee();
-
-        if (!GetPrototypeFromConstructor(cx, toTest, &proto))
+        if (!GetPrototypeFromCallableConstructor(cx, args, &proto))
             return false;
     }
 
     RootedObject globalLexical(cx, &global->lexicalScope());
     RootedFunction fun(cx, NewFunctionWithProto(cx, nullptr, 0,
                                                 JSFunction::INTERPRETED_LAMBDA, globalLexical,
                                                 anonymousAtom, proto,
                                                 AllocKind::FUNCTION, TenuredObject));
--- a/js/src/jsobj.cpp
+++ b/js/src/jsobj.cpp
@@ -992,16 +992,27 @@ js::GetPrototypeFromConstructor(JSContex
 {
     RootedValue protov(cx);
     if (!GetProperty(cx, newTarget, newTarget, cx->names().prototype, &protov))
         return false;
     proto.set(protov.isObject() ? &protov.toObject() : nullptr);
     return true;
 }
 
+bool
+js::GetPrototypeFromCallableConstructor(JSContext* cx, const CallArgs& args, MutableHandleObject proto)
+{
+    RootedObject newTarget(cx);
+    if (args.isConstructing())
+        newTarget = &args.newTarget().toObject();
+    else
+        newTarget = &args.callee();
+    return GetPrototypeFromConstructor(cx, newTarget, proto);
+}
+
 JSObject*
 js::CreateThisForFunction(JSContext* cx, HandleObject callee, HandleObject newTarget,
                           NewObjectKind newKind)
 {
     RootedObject proto(cx);
     if (!GetPrototypeFromConstructor(cx, newTarget, &proto))
         return nullptr;
 
--- a/js/src/jsobj.h
+++ b/js/src/jsobj.h
@@ -1088,16 +1088,19 @@ GetInitialHeap(NewObjectKind newKind, co
         return gc::TenuredHeap;
     return gc::DefaultHeap;
 }
 
 // ES6 9.1.15 GetPrototypeFromConstructor.
 extern bool
 GetPrototypeFromConstructor(JSContext* cx, js::HandleObject newTarget, js::MutableHandleObject proto);
 
+extern bool
+GetPrototypeFromCallableConstructor(JSContext* cx, const CallArgs& args, js::MutableHandleObject proto);
+
 // Specialized call for constructing |this| with a known function callee,
 // and a known prototype.
 extern JSObject*
 CreateThisForFunctionWithProto(JSContext* cx, js::HandleObject callee, HandleObject newTarget,
                                HandleObject proto, NewObjectKind newKind = GenericObject);
 
 // Specialized call for constructing |this| with a known function callee.
 extern JSObject*
--- a/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
+++ b/js/src/tests/ecma_6/Class/extendBuiltinConstructors.js
@@ -13,16 +13,23 @@ function testBuiltin(builtin) {
     assertEq(instance instanceof builtin, true);
     assertEq(instance.called, true);
 }
 
 
 testBuiltin(Function);
 testBuiltin(Object);
 testBuiltin(Boolean);
+testBuiltin(Error);
+testBuiltin(EvalError);
+testBuiltin(RangeError);
+testBuiltin(ReferenceError);
+testBuiltin(SyntaxError);
+testBuiltin(TypeError);
+testBuiltin(URIError);
 
 `;
 
 if (classesEnabled())
     eval(test);
 
 if (typeof reportCompare === 'function')
     reportCompare(0,0,"OK");
new file mode 100644
--- /dev/null
+++ b/js/src/tests/js1_5/Error/constructor-ordering.js
@@ -0,0 +1,17 @@
+var order = 0;
+function assertOrdering(ordering) {
+    assertEq(order, ordering);
+    order++;
+}
+
+// Spec mandates that the prototype is looked up /before/ we toString the
+// argument.
+var handler = { get() { assertOrdering(0); return Error.prototype } };
+var errorProxy = new Proxy(Error, handler);
+
+var toStringable = { toString() { assertOrdering(1); return "Argument"; } };
+
+new errorProxy(toStringable);
+
+if (typeof reportCompare === 'function')
+    reportCompare(0,0,"OK");
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -79,23 +79,27 @@ js::ErrorObject::init(JSContext* cx, Han
         obj->setSlotWithType(cx, messageShape, StringValue(message));
 
     return true;
 }
 
 /* static */ ErrorObject*
 js::ErrorObject::create(JSContext* cx, JSExnType errorType, HandleObject stack,
                         HandleString fileName, uint32_t lineNumber, uint32_t columnNumber,
-                        ScopedJSFreePtr<JSErrorReport>* report, HandleString message)
+                        ScopedJSFreePtr<JSErrorReport>* report, HandleString message,
+                        HandleObject protoArg /* = nullptr */)
 {
     AssertObjectIsSavedFrameOrWrapper(cx, stack);
 
-    Rooted<JSObject*> proto(cx, GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(), errorType));
-    if (!proto)
-        return nullptr;
+    RootedObject proto(cx, protoArg);
+    if (!proto) {
+        proto = GlobalObject::getOrCreateCustomErrorPrototype(cx, cx->global(), errorType);
+        if (!proto)
+            return nullptr;
+    }
 
     Rooted<ErrorObject*> errObject(cx);
     {
         const Class* clasp = ErrorObject::classForType(errorType);
         JSObject* obj = NewObjectWithGivenProto(cx, clasp, proto);
         if (!obj)
             return nullptr;
         errObject = &obj->as<ErrorObject>();
--- a/js/src/vm/ErrorObject.h
+++ b/js/src/vm/ErrorObject.h
@@ -67,17 +67,17 @@ class ErrorObject : public NativeObject
 
     // Create an error of the given type corresponding to the provided location
     // info.  If |message| is non-null, then the error will have a .message
     // property with that value; otherwise the error will have no .message
     // property.
     static ErrorObject*
     create(JSContext* cx, JSExnType type, HandleObject stack, HandleString fileName,
            uint32_t lineNumber, uint32_t columnNumber, ScopedJSFreePtr<JSErrorReport>* report,
-           HandleString message);
+           HandleString message, HandleObject proto = nullptr);
 
     /*
      * Assign the initial error shape to the empty object.  (This shape does
      * *not* include .message, which must be added separately if needed; see
      * ErrorObject::init.)
      */
     static Shape*
     assignInitialShape(ExclusiveContext* cx, Handle<ErrorObject*> obj);