Bug 1055472 - Part 5: Make the various Error constructors properly subclassable. (r=Waldo)
☠☠ backed out by 0f9b54b8ed53 ☠ ☠
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Nov 2015 18:22:21 -0800
changeset 308506 5ec1640cdfd20158850f414877d7eb3e6af4dc52
parent 308505 5e2b91587001834935408811df288112c657f3cc
child 308507 03d708347ebba969cdb94bdf3a9abf30c86e126b
push id1040
push userraliiev@mozilla.com
push dateMon, 29 Feb 2016 17:11:22 +0000
treeherdermozilla-release@8c3167321162 [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
@@ -1099,16 +1099,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);