Bug 1213341 - Handle the now ordinary error prototype object in stack. r=arai
authorTom Schuster <evilpies@gmail.com>
Tue, 22 Nov 2016 20:53:38 +0100
changeset 371114 4bcfbda6fe3dddbddf64ec65de4ebd6acda2570d
parent 371113 4e6fc50106d39c1c57555f7bdda8e336ba8a91b3
child 371115 d4ec6fd3b9fdc6a3164f21779a33a50136814cb1
push id1419
push userjlund@mozilla.com
push dateMon, 10 Apr 2017 20:44:07 +0000
treeherdermozilla-release@5e6801b73ef6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersarai
bugs1213341
milestone53.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 1213341 - Handle the now ordinary error prototype object in stack. r=arai
js/src/jsexn.h
js/src/vm/ErrorObject.cpp
js/src/vm/ErrorObject.h
--- a/js/src/jsexn.h
+++ b/js/src/jsexn.h
@@ -80,20 +80,27 @@ GetExceptionProtoKey(JSExnType exn)
     return JSProtoKey(JSProto_Error + int(exn));
 }
 
 static inline JSExnType
 ExnTypeFromProtoKey(JSProtoKey key)
 {
     JSExnType type = static_cast<JSExnType>(key - JSProto_Error);
     MOZ_ASSERT(type >= JSEXN_ERR);
-    MOZ_ASSERT(type < JSEXN_WARN);
+    MOZ_ASSERT(type < JSEXN_ERROR_LIMIT);
     return type;
 }
 
+static inline bool
+IsErrorProtoKey(JSProtoKey key)
+{
+    JSExnType type = static_cast<JSExnType>(key - JSProto_Error);
+    return type >= JSEXN_ERR && type < JSEXN_ERROR_LIMIT;
+}
+
 class AutoClearPendingException
 {
     JSContext* cx;
 
   public:
     explicit AutoClearPendingException(JSContext* cxArg)
       : cx(cxArg)
     { }
--- a/js/src/vm/ErrorObject.cpp
+++ b/js/src/vm/ErrorObject.cpp
@@ -159,70 +159,87 @@ js::ErrorObject::getOrCreateErrorReport(
     JSErrorReport* copy = CopyErrorReport(cx, &report);
     if (!copy)
         return nullptr;
     setReservedSlot(ERROR_REPORT_SLOT, PrivateValue(copy));
     return copy;
 }
 
 static bool
-ErrorObject_checkAndUnwrapThis(JSContext* cx, CallArgs& args, const char* fnName,
-                               MutableHandle<ErrorObject*> error)
+FindErrorInstanceOrPrototype(JSContext* cx, HandleObject obj, MutableHandleObject result)
 {
-    const Value& thisValue = args.thisv();
+    // Walk up the prototype chain until we find an error object instance or
+    // prototype object. This allows code like:
+    //  Object.create(Error.prototype).stack
+    // or
+    //   function NYI() { }
+    //   NYI.prototype = new Error;
+    //   (new NYI).stack
+    // to continue returning stacks that are useless, but at least don't throw.
 
-    if (!thisValue.isObject()) {
-        JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_NOT_NONNULL_OBJECT,
-                                  InformalValueTypeName(thisValue));
-        return false;
-    }
-
-    // Walk up the prototype chain until we find the first ErrorObject that has
-    // the slots we need. This allows us to support the poor-man's subclassing
-    // of error: Object.create(Error.prototype).
-
-    RootedObject target(cx, CheckedUnwrap(&thisValue.toObject()));
+    RootedObject target(cx, CheckedUnwrap(obj));
     if (!target) {
         JS_ReportErrorASCII(cx, "Permission denied to access object");
         return false;
     }
 
     RootedObject proto(cx);
-    while (!target->is<ErrorObject>()) {
+    while (!IsErrorProtoKey(StandardProtoKeyOrNull(target))) {
         if (!GetPrototype(cx, target, &proto))
             return false;
 
         if (!proto) {
             // We walked the whole prototype chain and did not find an Error
             // object.
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                                      js_Error_str, fnName, thisValue.toObject().getClass()->name);
+                                      js_Error_str, "(get stack)", obj->getClass()->name);
             return false;
         }
 
         target = CheckedUnwrap(proto);
         if (!target) {
             JS_ReportErrorASCII(cx, "Permission denied to access object");
             return false;
         }
     }
 
-    error.set(&target->as<ErrorObject>());
+    result.set(target);
     return true;
 }
 
+
+static MOZ_ALWAYS_INLINE bool
+IsObject(HandleValue v)
+{
+    return v.isObject();
+}
+
 /* static */ bool
 js::ErrorObject::getStack(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
-    Rooted<ErrorObject*> error(cx);
-    if (!ErrorObject_checkAndUnwrapThis(cx, args, "(get stack)", &error))
+    // We accept any object here, because of poor-man's subclassing of Error.
+    return CallNonGenericMethod<IsObject, getStack_impl>(cx, args);
+}
+
+/* static */ bool
+js::ErrorObject::getStack_impl(JSContext* cx, const CallArgs& args)
+{
+    RootedObject thisObj(cx, &args.thisv().toObject());
+
+    RootedObject obj(cx);
+    if (!FindErrorInstanceOrPrototype(cx, thisObj, &obj))
         return false;
 
-    RootedObject savedFrameObj(cx, error->stack());
+    if (!obj->is<ErrorObject>()) {
+        args.rval().setString(cx->runtime()->emptyString);
+        return true;
+    }
+
+    RootedObject savedFrameObj(cx, obj->as<ErrorObject>().stack());
     RootedString stackString(cx);
     if (!BuildStackString(cx, savedFrameObj, &stackString))
         return false;
 
     if (cx->stackFormat() == js::StackFormat::V8) {
         // When emulating V8 stack frames, we also need to prepend the
         // stringified Error to the stack string.
         HandlePropertyName name = cx->names().ErrorToStringWithTrailingNewline;
@@ -240,35 +257,27 @@ js::ErrorObject::getStack(JSContext* cx,
         RootedString stringified(cx, rval.toString());
         stackString = ConcatStrings<CanGC>(cx, stringified, stackString);
     }
 
     args.rval().setString(stackString);
     return true;
 }
 
-static MOZ_ALWAYS_INLINE bool
-IsObject(HandleValue v)
-{
-    return v.isObject();
-}
-
 /* static */ bool
 js::ErrorObject::setStack(JSContext* cx, unsigned argc, Value* vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
     // We accept any object here, because of poor-man's subclassing of Error.
     return CallNonGenericMethod<IsObject, setStack_impl>(cx, args);
 }
 
 /* static */ bool
 js::ErrorObject::setStack_impl(JSContext* cx, const CallArgs& args)
 {
-    const Value& thisValue = args.thisv();
-    MOZ_ASSERT(thisValue.isObject());
-    RootedObject thisObj(cx, &thisValue.toObject());
+    RootedObject thisObj(cx, &args.thisv().toObject());
 
     if (!args.requireAtLeast(cx, "(set stack)", 1))
         return false;
     RootedValue val(cx, args[0]);
 
     return DefineProperty(cx, thisObj, cx->names().stack, val);
 }
--- a/js/src/vm/ErrorObject.h
+++ b/js/src/vm/ErrorObject.h
@@ -101,16 +101,17 @@ class ErrorObject : public NativeObject
 
     JSString * getMessage() const {
         const HeapSlot& slot = getReservedSlotRef(MESSAGE_SLOT);
         return slot.isString() ? slot.toString() : nullptr;
     }
 
     // Getter and setter for the Error.prototype.stack accessor.
     static bool getStack(JSContext* cx, unsigned argc, Value* vp);
+    static bool getStack_impl(JSContext* cx, const CallArgs& args);
     static bool setStack(JSContext* cx, unsigned argc, Value* vp);
     static bool setStack_impl(JSContext* cx, const CallArgs& args);
 };
 
 } // namespace js
 
 template<>
 inline bool