Bug 1055472 - Part 5: Make the various Error constructors properly subclassable. (r=Waldo)
☠☠ backed out by 652bd59cdb51 ☠ ☠
authorEric Faust <efaustbmo@gmail.com>
Fri, 13 Nov 2015 18:22:21 -0800
changeset 307545 1509efcfa6290ef8926f83ec8b04b945a891ef74
parent 307544 c7180ea9dfa43936cd2089eebb6923b0c5086e9a
child 307546 94135702e1b51d1811b0735c672f60227eac1503
push idunknown
push userunknown
push dateunknown
reviewersWaldo
bugs1055472
milestone45.0a1
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);